Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions cloudsmith_cli/cli/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,12 @@

from cloudsmith_cli.cli import validators

from ..core.api.init import initialise_api as _initialise_api
from ..core.credentials.chain import CredentialProviderChain
from ..core.credentials.models import CredentialContext
from ..core.credentials.oidc.detectors import (
disabled_detectors_from_env,
registered_detectors,
)
from ..core.rest import create_requests_session as _create_session
from . import config, utils


Expand Down Expand Up @@ -354,6 +352,8 @@ def initialise_session(f):
@functools.wraps(f)
def wrapper(ctx, *args, **kwargs):
# pylint: disable=missing-docstring
from ..core.session import create_requests_session as _create_session

opts = config.get_or_create_options(ctx)
host_suffixes = _parse_suffixes(kwargs.pop("allowed_api_host_suffixes"))
proxy_suffixes = _parse_suffixes(kwargs.pop("allowed_api_proxy_suffixes"))
Expand Down Expand Up @@ -575,6 +575,8 @@ def initialise_api(f):
@functools.wraps(f)
def wrapper(ctx, *args, **kwargs):
# pylint: disable=missing-docstring
from ..core.api.init import initialise_api as _initialise_api

opts = config.get_or_create_options(ctx)
opts.rate_limit = _pop_boolean_flag(kwargs, "without_rate_limit", invert=True)
opts.rate_limit_warning = kwargs.pop("rate_limit_warning")
Expand Down
2 changes: 1 addition & 1 deletion cloudsmith_cli/cli/tests/test_startup_imports.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import subprocess
import sys

HEAVY_PREFIXES = ("mcp", "httpx")
HEAVY_PREFIXES = ("mcp", "httpx", "cloudsmith_api", "requests")


def modules_loaded_by_cli_import():
Expand Down
2 changes: 1 addition & 1 deletion cloudsmith_cli/core/api/files.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from requests_toolbelt import MultipartEncoder, MultipartEncoderMonitor

from .. import ratelimits
from ..rest import create_requests_session
from ..session import create_requests_session
from ..utils import calculate_file_md5
from .exceptions import ApiException, catch_raise_api_exception
from .init import get_api_client
Expand Down
5 changes: 3 additions & 2 deletions cloudsmith_cli/core/credentials/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
from __future__ import annotations

from dataclasses import dataclass
from typing import Literal
from typing import TYPE_CHECKING, Literal

import requests
if TYPE_CHECKING:
import requests


@dataclass
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@

import os

from ....rest import create_requests_session as create_session
from .base import EnvironmentDetector

API_VERSION = "7.1"
Expand All @@ -46,6 +45,8 @@ def get_token(self) -> str:
separator = "&" if "?" in request_uri else "?"
url = f"{request_uri}{separator}api-version={API_VERSION}"

from ....session import create_requests_session as create_session

session = self.context.session or create_session()
try:
response = session.post(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
import os
from urllib.parse import quote

from ....rest import create_requests_session as create_session
from .base import EnvironmentDetector

DEFAULT_AUDIENCE = "cloudsmith"
Expand All @@ -42,6 +41,8 @@ def get_token(self) -> str:
separator = "&" if "?" in request_url else "?"
url = f"{request_url}{separator}audience={quote(audience, safe='')}"

from ....session import create_requests_session as create_session

session = self.context.session or create_session()
try:
response = session.get(
Expand Down
2 changes: 1 addition & 1 deletion cloudsmith_cli/core/credentials/oidc/exchange.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

import requests

from ...rest import create_requests_session as create_session
from ...session import create_requests_session as create_session

if TYPE_CHECKING:
from ... import CredentialContext
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

import logging

from ....cli.saml import refresh_access_token
from ....core import keyring
from ..models import CredentialContext, CredentialResult
from ..provider import CredentialProvider
Expand Down Expand Up @@ -34,6 +33,8 @@ def resolve(self, context: CredentialContext) -> CredentialResult | None:
"Session unavailable; skipping token refresh, using existing token"
)
else:
from ....cli.saml import refresh_access_token

refresh_token = keyring.get_refresh_token(api_host)
new_access_token, new_refresh_token = refresh_access_token(
api_host,
Expand Down
2 changes: 1 addition & 1 deletion cloudsmith_cli/core/download.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from . import ratelimits, utils
from .api.exceptions import catch_raise_api_exception
from .api.packages import get_packages_api, list_packages
from .rest import create_requests_session
from .session import create_requests_session


def resolve_auth(
Expand Down
121 changes: 2 additions & 119 deletions cloudsmith_cli/core/rest.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,132 +4,15 @@
import json
import logging
import re
import time
from urllib.parse import urlencode

import requests
import requests.exceptions
from cloudsmith_api.configuration import Configuration
from cloudsmith_api.rest import ApiException, RESTClientObject
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

logger = logging.getLogger(__name__)


class RetryWithCallback(Retry):
"""A urllib3 Retry with a callback on retries."""
from .session import create_requests_session

def __init__(self, *args, **kwargs):
self.error_retry_cb = kwargs.pop("error_retry_cb", None)
super().__init__(*args, **kwargs)

def new(self, **kw):
kw["error_retry_cb"] = self.error_retry_cb
return super().new(**kw)

def sleep_for_retry(self, response=None):
retry_after = self.get_retry_after(response)
if retry_after:
self._sleep_with_callback(retry_after, context="retry-after")
return True

return False

def _sleep_backoff(self):
backoff = self.get_backoff_time()
if backoff <= 0:
return
self._sleep_with_callback(backoff, context="backoff")

def _sleep_with_callback(self, seconds, context=None):
"""Sleep, but generate a callback before it."""
if self.error_retry_cb and callable(self.error_retry_cb):
self.error_retry_cb(seconds, context=context)
return time.sleep(seconds)


def create_requests_session(
retries=None,
backoff_factor=None,
status_forcelist=None,
pools_size=4,
maxsize=4,
ssl_verify=None,
ssl_cert=None,
proxy=None,
session=None,
error_retry_cb=None,
respect_retry_after_header=True,
user_agent=None,
headers=None,
):
"""Create a requests session that retries some errors."""
# pylint: disable=too-many-branches
config = Configuration()

if retries is None:
retry_max = getattr(config, "error_retry_max", None)
retries = retry_max if retry_max is not None else 5

if backoff_factor is None:
retry_backoff = getattr(config, "error_retry_backoff", None)
backoff_factor = retry_backoff if retry_backoff is not None else 0.23

if status_forcelist is None:
retry_codes = getattr(config, "error_retry_codes", None)
status_forcelist = (
retry_codes if retry_codes is not None else [500, 502, 503, 504]
)

if ssl_verify is None:
ssl_verify = config.verify_ssl

if ssl_cert is None:
if config.cert_file and config.key_file:
ssl_cert = (config.cert_file, config.key_file)
elif config.cert_file:
ssl_cert = config.cert_file

if proxy is None:
proxy = Configuration().proxy

session = session or requests.Session()
session.verify = ssl_verify
session.cert = ssl_cert

if proxy:
session.proxies = {"http": proxy, "https": proxy}

retry = RetryWithCallback(
backoff_factor=backoff_factor,
connect=retries,
allowed_methods=False,
read=retries,
status_forcelist=tuple(status_forcelist),
status=retries,
total=retries,
error_retry_cb=error_retry_cb,
respect_retry_after_header=respect_retry_after_header,
)

adapter = HTTPAdapter(
max_retries=retry,
pool_connections=pools_size,
pool_maxsize=maxsize,
pool_block=True,
)

session.mount("http://", adapter)
session.mount("https://", adapter)

if user_agent:
session.headers["User-Agent"] = user_agent

if headers:
session.headers.update(headers)

return session
logger = logging.getLogger(__name__)


class RestResponse(io.IOBase):
Expand Down
Loading
Loading