From bf6bdf5bd93b2d71920803271d46e99699948558 Mon Sep 17 00:00:00 2001 From: box-sdk-build Date: Mon, 16 Feb 2026 02:43:40 -0800 Subject: [PATCH 1/5] test: assume that exception is thrown from delete archive (box/box-codegen#927) --- .codegen.json | 2 +- test/box_sdk_gen/test/archives.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.codegen.json b/.codegen.json index 240d0db58..cd13e9974 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "bfb97cc", "specHash": "77eac4b", "version": "4.4.0" } +{ "engineHash": "f36ed52", "specHash": "77eac4b", "version": "4.4.0" } diff --git a/test/box_sdk_gen/test/archives.py b/test/box_sdk_gen/test/archives.py index df6b2c710..9400f1a1d 100644 --- a/test/box_sdk_gen/test/archives.py +++ b/test/box_sdk_gen/test/archives.py @@ -37,6 +37,5 @@ def testArchivesCreateListDelete(): assert updated_archive.description == new_archive_description archives: ArchivesV2025R0 = client.archives.get_archives_v2025_r0(limit=100) assert len(archives.entries) > 0 - client.archives.delete_archive_by_id_v2025_r0(archive.id) with pytest.raises(Exception): client.archives.delete_archive_by_id_v2025_r0(archive.id) From 057ba9a969614e58aeb1669729275b43385315d0 Mon Sep 17 00:00:00 2001 From: box-sdk-build Date: Mon, 16 Feb 2026 06:43:54 -0800 Subject: [PATCH 2/5] chore: Update `.codegen.json` with commit hash of `codegen` and `openapi` spec [skip ci] --- .codegen.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.codegen.json b/.codegen.json index cd13e9974..b8b4d16bc 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "f36ed52", "specHash": "77eac4b", "version": "4.4.0" } +{ "engineHash": "9dcb945", "specHash": "77eac4b", "version": "4.4.0" } From 4441158de2c1702c5c041d6637687fbaefec1ead Mon Sep 17 00:00:00 2001 From: box-sdk-build Date: Mon, 16 Feb 2026 09:43:50 -0800 Subject: [PATCH 3/5] docs: Improve documentation for retry strategies (box/box-codegen#925) --- .codegen.json | 2 +- docs/box_sdk_gen/configuration.md | 165 +++++++++++++++++++++++++++--- 2 files changed, 153 insertions(+), 14 deletions(-) diff --git a/.codegen.json b/.codegen.json index b8b4d16bc..c8bf4451d 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "9dcb945", "specHash": "77eac4b", "version": "4.4.0" } +{ "engineHash": "482939a", "specHash": "77eac4b", "version": "4.4.0" } diff --git a/docs/box_sdk_gen/configuration.md b/docs/box_sdk_gen/configuration.md index cfd519872..9bb75b167 100644 --- a/docs/box_sdk_gen/configuration.md +++ b/docs/box_sdk_gen/configuration.md @@ -3,36 +3,175 @@ -- [Max retry attempts](#max-retry-attempts) -- [Custom retry strategy](#custom-retry-strategy) +- [Retry Strategy](#retry-strategy) + - [Overview](#overview) + - [Default Configuration](#default-configuration) + - [Retry Decision Flow](#retry-decision-flow) + - [Exponential Backoff Algorithm](#exponential-backoff-algorithm) + - [Example Delays (with default settings)](#example-delays-with-default-settings) + - [Retry-After Header](#retry-after-header) + - [Network Exception Handling](#network-exception-handling) + - [Customizing Retry Parameters](#customizing-retry-parameters) + - [Custom Retry Strategy](#custom-retry-strategy) -## Max retry attempts +## Retry Strategy -The default maximum number of retries in case of failed API call is 5. -To change this number you should initialize `BoxRetryStrategy` with the new value and pass it to `NetworkSession`. +### Overview + +The SDK ships with a built-in retry strategy (`BoxRetryStrategy`) that implements the `RetryStrategy` interface. The `BoxNetworkClient`, which serves as the default network client, uses this strategy to automatically retry failed API requests with exponential backoff. + +The retry strategy exposes two methods: + +- **`should_retry`** — Determines whether a failed request should be retried based on the HTTP status code, response headers, attempt count, and authentication state. +- **`retry_after`** — Computes the delay (in seconds) before the next retry attempt, using either the server-provided `Retry-After` header or an exponential backoff formula. + +### Default Configuration + +| Parameter | Default | Description | +| ---------------------------- | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `max_attempts` | `5` | Maximum number of retry attempts for HTTP error responses (status 4xx/5xx). | +| `retry_base_interval` | `1` (second) | Base interval used in the exponential backoff calculation. | +| `retry_randomization_factor` | `0.5` | Jitter factor applied to the backoff delay. The actual delay is multiplied by a random value between `1 - factor` and `1 + factor`. | +| `max_retries_on_exception` | `2` | Maximum number of retries for network-level exceptions (connection failures, timeouts). These are tracked by a separate counter from HTTP error retries. | + +### Retry Decision Flow + +The following diagram shows how `BoxRetryStrategy.should_retry` decides whether to retry a request: + +``` + should_retry(fetch_options, fetch_response, attempt_number) + | + v + +-----------------------+ + | status == 0 | Yes + | (network exception)? |----------> attempt_number <= max_retries_on_exception? + +-----------------------+ | | + | No Yes No + v | | + +-----------------------+ [RETRY] [NO RETRY] + | attempt_number >= | + | max_attempts? | + +-----------------------+ + | | + Yes No + | | + [NO RETRY] v + +-----------------------+ + | status == 202 AND | Yes + | Retry-After header? |----------> [RETRY] + +-----------------------+ + | No + v + +-----------------------+ + | status >= 500 | Yes + | (server error)? |----------> [RETRY] + +-----------------------+ + | No + v + +-----------------------+ + | status == 429 | Yes + | (rate limited)? |----------> [RETRY] + +-----------------------+ + | No + v + +-----------------------+ + | status == 401 AND | Yes + | auth available? |----------> Refresh token, then [RETRY] + +-----------------------+ + | No + v + [NO RETRY] +``` + +### Exponential Backoff Algorithm + +When the response does not include a `Retry-After` header, the retry delay is computed using exponential backoff with randomized jitter: + +``` +delay = 2^attempt_number * retry_base_interval * random(1 - factor, 1 + factor) +``` + +Where: + +- `attempt_number` is the current attempt (1-based) +- `retry_base_interval` defaults to `1` second +- `factor` is `retry_randomization_factor` (default `0.5`) +- `random(min, max)` returns a uniformly distributed value in `[min, max]` + +#### Example Delays (with default settings) + +| Attempt | Base Delay | Min Delay (factor=0.5) | Max Delay (factor=0.5) | +| ------- | ---------- | ---------------------- | ---------------------- | +| 1 | 2s | 1.0s | 3.0s | +| 2 | 4s | 2.0s | 6.0s | +| 3 | 8s | 4.0s | 12.0s | +| 4 | 16s | 8.0s | 24.0s | + +### Retry-After Header + +When the server includes a `Retry-After` header in the response, the SDK uses the header value directly as the delay in seconds instead of computing an exponential backoff delay. This applies to any retryable response that includes the header, including: + +- `202 Accepted` with `Retry-After` (long-running operations) +- `429 Too Many Requests` with `Retry-After` +- `5xx` server errors with `Retry-After` + +The header value is parsed as a floating-point number representing seconds. + +### Network Exception Handling + +Network-level failures (connection refused, DNS resolution errors, timeouts, TLS errors) are represented internally as responses with status `0`. These exceptions are tracked by a **separate counter** (`max_retries_on_exception`, default `2`) from the regular HTTP error retry counter (`max_attempts`). + +This means: + +- Network exception retries are tracked independently from HTTP error retries, each with their own counter and backoff progression. +- A request can fail up to `max_retries_on_exception` times due to network exceptions, but each exception retry also increments the overall attempt counter, so the total number of retries across both exception and HTTP error types is bounded by `max_attempts`. + +### Customizing Retry Parameters + +You can customize all retry parameters by initializing `BoxRetryStrategy` with the desired values and passing it to `NetworkSession`: ```python from box_sdk_gen import BoxClient, BoxDeveloperTokenAuth, NetworkSession, BoxRetryStrategy auth = BoxDeveloperTokenAuth(token='DEVELOPER_TOKEN_GOES_HERE') -network_session = NetworkSession(retry_strategy=BoxRetryStrategy(max_attempts=6)) +network_session = NetworkSession( + retry_strategy=BoxRetryStrategy( + max_attempts=3, + retry_base_interval=2, + retry_randomization_factor=0.3, + max_retries_on_exception=1, + ) +) client = BoxClient(auth=auth, network_session=network_session) ``` -## Custom retry strategy +### Custom Retry Strategy -You can also implement your own retry strategy by subclassing `RetryStrategy` and overriding `should_retry` and `retry_after` methods. -This example shows how to set custom strategy that retries on 5xx status codes and waits 1 second between retries. +You can implement your own retry strategy by subclassing `RetryStrategy` and overriding the `should_retry` and `retry_after` methods: ```python -from box_sdk_gen import BoxClient, BoxDeveloperTokenAuth, NetworkSession, RetryStrategy, FetchOptions, FetchResponse +from box_sdk_gen import ( + BoxClient, BoxDeveloperTokenAuth, NetworkSession, + RetryStrategy, FetchOptions, FetchResponse, +) class CustomRetryStrategy(RetryStrategy): - def should_retry(self, fetch_options: FetchOptions, fetch_response: FetchResponse, attempt_number: int) -> bool: - return fetch_response.status_code >= 500 - def retry_after(self, fetch_options: FetchOptions, fetch_response: FetchResponse, attempt_number: int) -> float: + def should_retry( + self, + fetch_options: FetchOptions, + fetch_response: FetchResponse, + attempt_number: int, + ) -> bool: + return fetch_response.status >= 500 and attempt_number < 3 + + def retry_after( + self, + fetch_options: FetchOptions, + fetch_response: FetchResponse, + attempt_number: int, + ) -> float: return 1.0 auth = BoxDeveloperTokenAuth(token='DEVELOPER_TOKEN_GOES_HERE') From a952077100f6f979673d7a6b5adba82505e6dcc4 Mon Sep 17 00:00:00 2001 From: box-sdk-build Date: Wed, 18 Feb 2026 09:38:06 -0800 Subject: [PATCH 4/5] feat: Add customizable timeouts for SDKs (box/box-codegen#924) --- .codegen.json | 2 +- box_sdk_gen/client.py | 13 ++++++ box_sdk_gen/networking/__init__.py | 2 + box_sdk_gen/networking/box_network_client.py | 46 ++++++++++++++++++-- box_sdk_gen/networking/network.py | 31 +++++++++++++ box_sdk_gen/networking/timeout_config.py | 12 +++++ docs/box_sdk_gen/client.md | 13 ++++++ docs/box_sdk_gen/configuration.md | 28 ++++++++++++ test/box_sdk_gen/test/box_network_client.py | 25 ++++++++++- test/box_sdk_gen/test/client.py | 19 ++++++++ 10 files changed, 184 insertions(+), 7 deletions(-) create mode 100644 box_sdk_gen/networking/timeout_config.py diff --git a/.codegen.json b/.codegen.json index c8bf4451d..06ea9b6ce 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "482939a", "specHash": "77eac4b", "version": "4.4.0" } +{ "engineHash": "bc04b80", "specHash": "77eac4b", "version": "4.4.0" } diff --git a/box_sdk_gen/client.py b/box_sdk_gen/client.py index 155301d2e..f141a1306 100644 --- a/box_sdk_gen/client.py +++ b/box_sdk_gen/client.py @@ -202,6 +202,8 @@ from box_sdk_gen.networking.base_urls import BaseUrls +from box_sdk_gen.networking.timeout_config import TimeoutConfig + from box_sdk_gen.networking.proxy_config import ProxyConfig @@ -545,3 +547,14 @@ def with_proxy(self, config: ProxyConfig) -> 'BoxClient': return BoxClient( auth=self.auth, network_session=self.network_session.with_proxy(config) ) + + def with_timeouts(self, config: TimeoutConfig) -> 'BoxClient': + """ + Create a new client with custom timeouts that will be used for every API call + :param config: Timeout configuration. + :type config: TimeoutConfig + """ + return BoxClient( + auth=self.auth, + network_session=self.network_session.with_timeout_config(config), + ) diff --git a/box_sdk_gen/networking/__init__.py b/box_sdk_gen/networking/__init__.py index c12260c17..84da5517f 100644 --- a/box_sdk_gen/networking/__init__.py +++ b/box_sdk_gen/networking/__init__.py @@ -1,5 +1,7 @@ from box_sdk_gen.networking.box_network_client import * +from box_sdk_gen.networking.timeout_config import * + from box_sdk_gen.networking.proxy_config import * from box_sdk_gen.networking.network import * diff --git a/box_sdk_gen/networking/box_network_client.py b/box_sdk_gen/networking/box_network_client.py index 7a4f44647..3edeef995 100644 --- a/box_sdk_gen/networking/box_network_client.py +++ b/box_sdk_gen/networking/box_network_client.py @@ -3,7 +3,7 @@ import time from collections import OrderedDict from dataclasses import dataclass -from typing import Optional, Dict, Union +from typing import Optional, Dict, Union, Tuple from sys import version_info as py_version import requests @@ -17,6 +17,7 @@ from ..box.errors import BoxAPIError, BoxSDKError, RequestInfo, ResponseInfo from ..internal.utils import ByteStream, ResponseByteStream from ..networking.network_client import NetworkClient +from ..networking.timeout_config import TimeoutConfig from ..serialization.json import ( sd_to_json, sd_to_url_params, @@ -40,6 +41,7 @@ class APIRequest: params: Dict[str, str] data: Optional[Union[str, ByteStream, MultipartEncoder]] allow_redirects: bool = True + timeout: Optional[Tuple[Optional[float], Optional[float]]] = None @dataclass @@ -151,6 +153,7 @@ def _prepare_request( options.content_type, options.file_stream or options.data ) allow_redirects = options.follow_redirects + timeout = self._get_request_timeout(options) if options.content_type: if options.content_type == 'multipart/form-data': @@ -178,8 +181,43 @@ def _prepare_request( params=params, data=data, allow_redirects=allow_redirects, + timeout=timeout, ) + @staticmethod + def _get_request_timeout( + options: 'FetchOptions', + ) -> Optional[Tuple[Optional[float], Optional[float]]]: + """ + Derive requests timeout tuple (connect, read) in seconds. + + Uses `options.network_session.timeout_config` when present. + The timeout config values are expected to be in milliseconds. + """ + network_session = options.network_session + timeout_config = network_session.timeout_config if network_session else None + if timeout_config is None: + return None + + connection_timeout_ms, read_timeout_ms = ( + timeout_config.connection_timeout_ms, + timeout_config.read_timeout_ms, + ) + + if connection_timeout_ms is None and read_timeout_ms is None: + return None + + connection_timeout_sec = ( + connection_timeout_ms / 1000.0 + if connection_timeout_ms is not None + else None + ) + read_timeout_sec = ( + read_timeout_ms / 1000.0 if read_timeout_ms is not None else None + ) + + return (connection_timeout_sec, read_timeout_sec) + @staticmethod def _prepare_headers( options: 'FetchOptions', reauthenticate: bool = False @@ -216,12 +254,12 @@ def _prepare_body( or content_type == 'application/octet-stream' ): return data - raise + raise ValueError(f'Unsupported content type: {content_type}') def _make_request(self, request: APIRequest) -> APIResponse: raised_exception = None reauthentication_needed = False - default_timeout = (5, 60) # connect, read timeout + timeout = request.timeout try: network_response = self.requests_session.request( method=request.method, @@ -231,7 +269,7 @@ def _make_request(self, request: APIRequest) -> APIResponse: params=request.params, allow_redirects=request.allow_redirects, stream=True, - timeout=default_timeout, + timeout=timeout, ) except RequestException as request_exc: raised_exception = request_exc diff --git a/box_sdk_gen/networking/network.py b/box_sdk_gen/networking/network.py index c51f8722e..a3ed2fb23 100644 --- a/box_sdk_gen/networking/network.py +++ b/box_sdk_gen/networking/network.py @@ -6,6 +6,7 @@ from .proxy_config import ProxyConfig from .base_urls import BaseUrls from .retries import RetryStrategy, BoxRetryStrategy +from .timeout_config import TimeoutConfig class NetworkSession: @@ -18,6 +19,7 @@ def __init__( base_urls: BaseUrls = None, proxy_url: str = None, data_sanitizer: DataSanitizer = None, + timeout_config: TimeoutConfig = None, ): if additional_headers is None: additional_headers = {} @@ -38,12 +40,18 @@ def __init__( } if data_sanitizer is None: data_sanitizer = DataSanitizer() + if timeout_config is None: + timeout_config = TimeoutConfig( + connection_timeout_ms=5000, + read_timeout_ms=60000, + ) self.additional_headers = additional_headers self.base_urls = base_urls self.proxy_url = proxy_url self.network_client = network_client self.retry_strategy = retry_strategy self.data_sanitizer = data_sanitizer + self.timeout_config = timeout_config def with_additional_headers( self, additional_headers: Dict[str, str] = None @@ -61,6 +69,7 @@ def with_additional_headers( proxy_url=self.proxy_url, retry_strategy=self.retry_strategy, data_sanitizer=self.data_sanitizer, + timeout_config=self.timeout_config, ) def with_custom_base_urls(self, base_urls: BaseUrls) -> 'NetworkSession': @@ -77,6 +86,7 @@ def with_custom_base_urls(self, base_urls: BaseUrls) -> 'NetworkSession': proxy_url=self.proxy_url, retry_strategy=self.retry_strategy, data_sanitizer=self.data_sanitizer, + timeout_config=self.timeout_config, ) def with_proxy(self, config: ProxyConfig) -> 'NetworkSession': @@ -103,6 +113,7 @@ def with_proxy(self, config: ProxyConfig) -> 'NetworkSession': proxy_url=proxy_url, retry_strategy=self.retry_strategy, data_sanitizer=self.data_sanitizer, + timeout_config=self.timeout_config, ) def with_network_client(self, network_client: NetworkClient) -> 'NetworkSession': @@ -119,6 +130,7 @@ def with_network_client(self, network_client: NetworkClient) -> 'NetworkSession' proxy_url=self.proxy_url, retry_strategy=self.retry_strategy, data_sanitizer=self.data_sanitizer, + timeout_config=self.timeout_config, ) def with_retry_strategy(self, retry_strategy: RetryStrategy) -> 'NetworkSession': @@ -135,6 +147,7 @@ def with_retry_strategy(self, retry_strategy: RetryStrategy) -> 'NetworkSession' proxy_url=self.proxy_url, retry_strategy=retry_strategy, data_sanitizer=self.data_sanitizer, + timeout_config=self.timeout_config, ) def with_data_sanitizer(self, data_sanitizer: DataSanitizer) -> 'NetworkSession': @@ -151,4 +164,22 @@ def with_data_sanitizer(self, data_sanitizer: DataSanitizer) -> 'NetworkSession' proxy_url=self.proxy_url, retry_strategy=self.retry_strategy, data_sanitizer=data_sanitizer, + timeout_config=self.timeout_config, + ) + + def with_timeout_config(self, timeout_config: TimeoutConfig) -> 'NetworkSession': + """ + Generate a fresh network session by duplicating the existing configuration and network parameters, + while also including timeout config to be used for every API call. + :param timeout_config: TimeoutConfig object, which contains the timeout config + :return: a new instance of NetworkSession + """ + return NetworkSession( + network_client=self.network_client, + additional_headers=self.additional_headers, + base_urls=self.base_urls, + proxy_url=self.proxy_url, + retry_strategy=self.retry_strategy, + data_sanitizer=self.data_sanitizer, + timeout_config=timeout_config, ) diff --git a/box_sdk_gen/networking/timeout_config.py b/box_sdk_gen/networking/timeout_config.py new file mode 100644 index 000000000..79772f7b3 --- /dev/null +++ b/box_sdk_gen/networking/timeout_config.py @@ -0,0 +1,12 @@ +from typing import Optional + + +class TimeoutConfig: + def __init__( + self, + *, + connection_timeout_ms: Optional[int] = None, + read_timeout_ms: Optional[int] = None + ): + self.connection_timeout_ms = connection_timeout_ms + self.read_timeout_ms = read_timeout_ms diff --git a/docs/box_sdk_gen/client.md b/docs/box_sdk_gen/client.md index d5e5c437f..4a38797ee 100644 --- a/docs/box_sdk_gen/client.md +++ b/docs/box_sdk_gen/client.md @@ -15,6 +15,7 @@ divided across resource managers. - [Suppress notifications](#suppress-notifications) - [Custom headers](#custom-headers) - [Custom Base URLs](#custom-base-urls) +- [Use Timeouts for API calls](#use-timeouts-for-api-calls) - [Use Proxy for API calls](#use-proxy-for-api-calls) @@ -151,6 +152,18 @@ new_client = client.with_custom_base_urls(base_urls=BaseUrls( )) ``` +# Use Timeouts for API calls + +In order to configure timeout for API calls, calling the `client.with_timeouts(config)` method creates a new client with timeout settings, leaving the original client unmodified. + +```python +timeout_config = TimeoutConfig( + connection_timeout_ms=10000, + read_timeout_ms=30000 +) +new_client = client.with_timeouts(timeout_config) +``` + # Use Proxy for API calls In order to use a proxy for API calls, calling the `client.with_proxy(proxyConfig)` method creates a new client, leaving the original client unmodified, with the username and password being optional. diff --git a/docs/box_sdk_gen/configuration.md b/docs/box_sdk_gen/configuration.md index 9bb75b167..4e02e8cec 100644 --- a/docs/box_sdk_gen/configuration.md +++ b/docs/box_sdk_gen/configuration.md @@ -13,6 +13,7 @@ - [Network Exception Handling](#network-exception-handling) - [Customizing Retry Parameters](#customizing-retry-parameters) - [Custom Retry Strategy](#custom-retry-strategy) +- [Timeouts](#timeouts) @@ -178,3 +179,30 @@ auth = BoxDeveloperTokenAuth(token='DEVELOPER_TOKEN_GOES_HERE') network_session = NetworkSession(retry_strategy=CustomRetryStrategy()) client = BoxClient(auth=auth, network_session=network_session) ``` + +## Timeouts + +You can configure network timeouts with `TimeoutConfig` on `NetworkSession`. +Python SDK supports separate connection and read timeout values in milliseconds. + +```python +from box_sdk_gen import BoxClient, BoxDeveloperTokenAuth, NetworkSession, TimeoutConfig + +auth = BoxDeveloperTokenAuth(token='DEVELOPER_TOKEN_GOES_HERE') +timeout_config = TimeoutConfig( + connection_timeout_ms=10000, + read_timeout_ms=30000, +) +network_session = NetworkSession(timeout_config=timeout_config) +client = BoxClient(auth=auth, network_session=network_session) +``` + +How timeout handling works: + +- Timeout values are configured in milliseconds and converted to seconds internally for HTTP requests. +- The SDK uses default timeouts when timeout config is not provided: `connection_timeout_ms=5000` and `read_timeout_ms=60000`. +- To disable all SDK timeouts, pass `TimeoutConfig(connection_timeout_ms=None, read_timeout_ms=None)` explicitly to `NetworkSession`. +- You can also disable only one timeout by setting one value to `None` (for example, `connection_timeout_ms=None` or `read_timeout_ms=None`). If you provide only the other value (for example, `read_timeout_ms=30000`) and leave one unspecified, the unspecified field remains `None` and that timeout stays disabled. +- Timeout failures are treated as network exceptions, and retry behavior is controlled by the configured retry strategy. +- Timeout applies to a single HTTP request attempt to the Box API (not the total time across all retries). +- If retries are exhausted, the SDK raises `BoxSDKError` with the underlying request exception. diff --git a/test/box_sdk_gen/test/box_network_client.py b/test/box_sdk_gen/test/box_network_client.py index ab95206ee..bfd003846 100644 --- a/test/box_sdk_gen/test/box_network_client.py +++ b/test/box_sdk_gen/test/box_network_client.py @@ -175,6 +175,25 @@ def network_session_mock(): return NetworkSession() +def test_network_session_uses_default_timeout_config_values(): + network_session = NetworkSession() + + assert network_session.timeout_config.connection_timeout_ms == 5000 + assert network_session.timeout_config.read_timeout_ms == 60000 + + +def test_prepare_request_uses_default_network_session_timeouts(network_client): + options = FetchOptions( + url="https://example.com", + method="GET", + network_session=NetworkSession(), + ) + + api_request = network_client._prepare_request(options=options) + + assert api_request.timeout == (5, 60) + + @pytest.fixture def network_client(mock_requests_session): return BoxNetworkClient(mock_requests_session) @@ -295,7 +314,7 @@ def test_prepare_body_invalid_content_type(network_client): network_client._prepare_body("invalid_content_type", {}) -def test_prepare_json_request(network_client): +def test_prepare_json_request(network_client, network_session_mock): options = FetchOptions( url="https://example.com", method="POST", @@ -303,6 +322,7 @@ def test_prepare_json_request(network_client): headers={"header": "test"}, params={"param": "value"}, content_type="application/json", + network_session=network_session_mock, ) api_request = network_client._prepare_request(options=options) @@ -318,6 +338,7 @@ def test_prepare_json_request(network_client): }, params={"param": "value"}, data='{"key": "value"}', + timeout=(5, 60), ) @@ -379,7 +400,7 @@ def test_make_request(network_client, mock_requests_session, response_200): ) assert mock_requests_session.request.call_count == 1 mock_requests_session.request.assert_called_once_with( - **request_params, stream=True, timeout=(5, 60) + **request_params, stream=True, timeout=None ) diff --git a/test/box_sdk_gen/test/client.py b/test/box_sdk_gen/test/client.py index 3924ad9b0..207b80a4a 100644 --- a/test/box_sdk_gen/test/client.py +++ b/test/box_sdk_gen/test/client.py @@ -26,6 +26,8 @@ from box_sdk_gen.schemas.user_full import UserFull +from box_sdk_gen.networking.timeout_config import TimeoutConfig + from box_sdk_gen.internal.utils import get_uuid from box_sdk_gen.internal.utils import generate_byte_stream @@ -218,3 +220,20 @@ def testWithCustomBaseUrls(): custom_base_client: BoxClient = client.with_custom_base_urls(new_base_urls) with pytest.raises(Exception): custom_base_client.users.get_user_me() + + +def testWithTimeoutWhenTimeoutOccurs(): + read_timeout_ms: int = 1 + client_with_timeout: BoxClient = client.with_timeouts( + TimeoutConfig(read_timeout_ms=read_timeout_ms) + ) + with pytest.raises(Exception): + client_with_timeout.users.get_user_me() + + +def testWithTimeoutWhenTimeoutDoesNotOccur(): + read_timeout_ms: int = 10000 + client_with_timeout: BoxClient = client.with_timeouts( + TimeoutConfig(read_timeout_ms=read_timeout_ms) + ) + client_with_timeout.users.get_user_me() From 577a3a478318aaa3cf3ab45571234e23e054dffe Mon Sep 17 00:00:00 2001 From: box-sdk-build Date: Thu, 19 Feb 2026 06:44:15 -0800 Subject: [PATCH 5/5] docs: Update description for delete archives endpoint (box/box-openapi#585) --- .codegen.json | 2 +- box_sdk_gen/managers/archives.py | 9 +++++++++ box_sdk_gen/schemas/__init__.py | 16 ++++++++-------- docs/box_sdk_gen/archives.md | 4 ++++ 4 files changed, 22 insertions(+), 9 deletions(-) diff --git a/.codegen.json b/.codegen.json index 06ea9b6ce..0a1f67b1d 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "bc04b80", "specHash": "77eac4b", "version": "4.4.0" } +{ "engineHash": "bc04b80", "specHash": "f2523d5", "version": "4.4.0" } diff --git a/box_sdk_gen/managers/archives.py b/box_sdk_gen/managers/archives.py index 146b58f82..24c64bd29 100644 --- a/box_sdk_gen/managers/archives.py +++ b/box_sdk_gen/managers/archives.py @@ -158,6 +158,15 @@ def delete_archive_by_id_v2025_r0( To learn more about the archive APIs, see the [Archive API Guide](https://developer.box.com/guides/archives). + + + + + This endpoint is currently unavailable. Please contact support for assistance. + + + + :param archive_id: The ID of the archive. Example: "982312" :type archive_id: str diff --git a/box_sdk_gen/schemas/__init__.py b/box_sdk_gen/schemas/__init__.py index a4132c26b..5ed9f1f5f 100644 --- a/box_sdk_gen/schemas/__init__.py +++ b/box_sdk_gen/schemas/__init__.py @@ -358,10 +358,10 @@ from box_sdk_gen.schemas.upload_part import * -from box_sdk_gen.schemas.uploaded_part import * - from box_sdk_gen.schemas.upload_parts import * +from box_sdk_gen.schemas.uploaded_part import * + from box_sdk_gen.schemas.upload_session import * from box_sdk_gen.schemas.upload_url import * @@ -500,6 +500,12 @@ from box_sdk_gen.schemas.watermark import * +from box_sdk_gen.schemas.webhook_mini import * + +from box_sdk_gen.schemas.webhooks import * + +from box_sdk_gen.schemas.webhook import * + from box_sdk_gen.schemas.web_link_base import * from box_sdk_gen.schemas.web_link_mini import * @@ -568,12 +574,6 @@ from box_sdk_gen.schemas.app_item_associations import * -from box_sdk_gen.schemas.webhook_mini import * - -from box_sdk_gen.schemas.webhooks import * - -from box_sdk_gen.schemas.webhook import * - from box_sdk_gen.schemas.workflow_mini import * from box_sdk_gen.schemas.workflow import * diff --git a/docs/box_sdk_gen/archives.md b/docs/box_sdk_gen/archives.md index 23ef14057..1a4df8895 100644 --- a/docs/box_sdk_gen/archives.md +++ b/docs/box_sdk_gen/archives.md @@ -81,6 +81,10 @@ Permanently deletes an archive. To learn more about the archive APIs, see the [Archive API Guide](https://developer.box.com/guides/archives). + +This endpoint is currently unavailable. Please contact support for assistance. + + This operation is performed by calling function `delete_archive_by_id_v2025_r0`. See the endpoint docs at