From 05662ac3700d2db98f896fcecaa8a1547529383f Mon Sep 17 00:00:00 2001 From: Dmytro Kosse <9990225+kossman@users.noreply.github.com> Date: Wed, 23 Jul 2025 18:16:26 +0400 Subject: [PATCH 01/26] feat(DEVC-1752): added requests.Session reuse; replaced third-party tenacity with builtin urllib3 Retry --- src/corva/api.py | 90 +++++++++----------------------------- src/corva/api_utils.py | 44 +++++++++++++++++++ src/corva/configuration.py | 8 ++++ src/corva/handlers.py | 3 -- 4 files changed, 73 insertions(+), 72 deletions(-) create mode 100644 src/corva/api_utils.py diff --git a/src/corva/api.py b/src/corva/api.py index ff3a6e2c..52c8c701 100644 --- a/src/corva/api.py +++ b/src/corva/api.py @@ -1,17 +1,13 @@ import json import posixpath import re -from http import HTTPStatus + from typing import List, Optional, Sequence, Union import requests -from tenacity import ( - RetryError, - retry, - retry_if_result, - stop_after_attempt, - wait_random_exponential, -) + +from corva.api_utils import get_retry_strategy, get_requests_session +from corva.configuration import SETTINGS class Api: @@ -21,9 +17,6 @@ class Api: convenient URL usage and reasonable timeouts to API requests. """ - TIMEOUT_LIMITS = (3, 30) # seconds - DEFAULT_MAX_RETRIES = int(0) - def __init__( self, *, @@ -31,16 +24,24 @@ def __init__( data_api_url: str, api_key: str, app_key: str, - timeout: Optional[int] = None, app_connection_id: Optional[int] = None, + max_retries: Optional[int] = SETTINGS.MAX_RETRY_COUNT, + pool_connections_count: Optional[int] = SETTINGS.POOL_CONNECTIONS_COUNT, + pool_max_size: Optional[int] = SETTINGS.POOL_MAX_SIZE, + pool_block: Optional[bool] = SETTINGS.POOL_BLOCK, ): self.api_url = api_url self.data_api_url = data_api_url self.api_key = api_key self.app_key = app_key self.app_connection_id = app_connection_id - self.timeout = timeout or self.TIMEOUT_LIMITS[1] - self._max_retries = self.DEFAULT_MAX_RETRIES + self._retry_strategy = get_retry_strategy(max_retries) if max_retries not in (None, 0) else None + self._session = get_requests_session( + retry_strategy=self._retry_strategy, + pool_connections_count=pool_connections_count, + pool_max_size=pool_max_size, + pool_block=pool_block, + ) @property def default_headers(self): @@ -99,15 +100,14 @@ def _get_url(self, path: str): return posixpath.join(self.api_url, path) - @staticmethod def _execute_request( + self, method: str, url: str, params: Optional[dict], data: Optional[dict], headers: Optional[dict] = None, - timeout: Optional[int] = None, - ): + ) -> requests.Response: """Executes the request. Args: @@ -116,18 +116,17 @@ def _execute_request( data: request body, that will be casted to json. params: url query string params. headers: additional headers to include in request. - timeout: custom request timeout in seconds. Returns: requests.Response instance. """ - return requests.request( + + return self._session.request( method=method, url=url, params=params, json=data, headers=headers, - timeout=timeout, ) def _request( @@ -138,7 +137,6 @@ def _request( data: Optional[dict] = None, params: Optional[dict] = None, headers: Optional[dict] = None, - timeout: Optional[int] = None, ) -> requests.Response: """Prepares HTTP request. @@ -148,22 +146,10 @@ def _request( data: request body, that will be casted to json. params: url query string params. headers: additional headers to include in request. - timeout: custom request timeout in seconds. Returns: requests.Response instance. """ - retryable_status_codes = [ - HTTPStatus.TOO_MANY_REQUESTS, # 428 - HTTPStatus.INTERNAL_SERVER_ERROR, # 500 - HTTPStatus.BAD_GATEWAY, # 502 - HTTPStatus.SERVICE_UNAVAILABLE, # 503 - HTTPStatus.GATEWAY_TIMEOUT, # 504 - ] - - timeout = timeout or self.timeout - self._validate_timeout(timeout) - url = self._get_url(path) headers = { @@ -171,47 +157,13 @@ def _request( **(headers or {}), } - if self.max_retries > 0: - retry_decorator = retry( - stop=stop_after_attempt(self.max_retries), - wait=wait_random_exponential(multiplier=0.25, max=10), - retry=retry_if_result( - lambda r: r.status_code in retryable_status_codes - ), - ) - retrying_request = retry_decorator(self._execute_request) - try: - response = retrying_request( - method=method, - url=url, - params=params, - data=data, - headers=headers, - timeout=timeout, - ) - except RetryError as e: - if not e.last_attempt.failed: - response = e.last_attempt.result() - else: - raise - else: - response = self._execute_request( + return self._execute_request( method=method, url=url, params=params, data=data, headers=headers, - timeout=timeout, - ) - - return response - - def _validate_timeout(self, timeout: int) -> None: - if self.TIMEOUT_LIMITS[0] > timeout or self.TIMEOUT_LIMITS[1] < timeout: - raise ValueError( - f"Timeout must be between {self.TIMEOUT_LIMITS[0]} and " - f"{self.TIMEOUT_LIMITS[1]} seconds." - ) + ) def get_dataset( self, diff --git a/src/corva/api_utils.py b/src/corva/api_utils.py new file mode 100644 index 00000000..a77f4861 --- /dev/null +++ b/src/corva/api_utils.py @@ -0,0 +1,44 @@ +from typing import Optional + +import requests +from requests.adapters import HTTPAdapter +from urllib3 import Retry + +RETRYABLE_STATUS_CODES = ( + 429, # HTTPStatus.TOO_MANY_REQUESTS + 500, # HTTPStatus.INTERNAL_SERVER_ERROR + 502, # HTTPStatus.BAD_GATEWAY + 503, # HTTPStatus.SERVICE_UNAVAILABLE + 504, # HTTPStatus.GATEWAY_TIMEOUT +) + + +def get_retry_strategy(max_retries: int) -> Retry: + return Retry( + total=max_retries, + backoff_factor=0.3, + status_forcelist=RETRYABLE_STATUS_CODES, + allowed_methods={'GET', 'POST'}, # Support both + raise_on_status=False, + ) + + +def get_requests_session( + pool_connections_count: int, + pool_max_size: int, + pool_block: bool, + retry_strategy: Optional[Retry] = None, +) -> requests.Session: + adapter = HTTPAdapter( + max_retries=retry_strategy, + pool_connections=pool_connections_count, + pool_maxsize=pool_max_size, + pool_block=pool_block, + ) + + session = requests.Session() + + session.mount('https://', adapter) + session.mount('http://', adapter) + + return session diff --git a/src/corva/configuration.py b/src/corva/configuration.py index ebbbd7a4..7ddd3da4 100644 --- a/src/corva/configuration.py +++ b/src/corva/configuration.py @@ -23,5 +23,13 @@ class Settings(pydantic.BaseSettings): # secrets SECRETS_CACHE_TTL: int = int(datetime.timedelta(minutes=5).total_seconds()) + # keep-alive + POOL_CONNECTIONS_COUNT: int = 20 # Total pools count + POOL_MAX_SIZE: int = 20 # Max connections count per pool/host + POOL_BLOCK: bool = True # If all conn ack - wait until pool connection released, do not throw exc + + # retry + MAX_RETRY_COUNT: int = 3 # If `0` then retires will be disabled + SETTINGS = Settings() diff --git a/src/corva/handlers.py b/src/corva/handlers.py index f6ad7e83..a6a9f24e 100644 --- a/src/corva/handlers.py +++ b/src/corva/handlers.py @@ -173,7 +173,6 @@ def wrapper( data_api_url=SETTINGS.DATA_API_ROOT_URL, api_key=api_key, app_key=SETTINGS.APP_KEY, - timeout=None, app_connection_id=event.app_connection_id, ) @@ -282,7 +281,6 @@ def wrapper( data_api_url=SETTINGS.DATA_API_ROOT_URL, api_key=api_key, app_key=SETTINGS.APP_KEY, - timeout=None, app_connection_id=event.app_connection_id, ) @@ -392,7 +390,6 @@ def wrapper( data_api_url=SETTINGS.DATA_API_ROOT_URL, api_key=api_key, app_key=SETTINGS.APP_KEY, - timeout=None, app_connection_id=None, ) From 1db2d26821741a0901303d531c0bb1a33a07f5a1 Mon Sep 17 00:00:00 2001 From: Dmytro Kosse <9990225+kossman@users.noreply.github.com> Date: Wed, 23 Jul 2025 18:20:43 +0400 Subject: [PATCH 02/26] feat(DEVC-1752): fix lint --- src/corva/api.py | 8 +++++--- src/corva/configuration.py | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/corva/api.py b/src/corva/api.py index 52c8c701..3c97b0a5 100644 --- a/src/corva/api.py +++ b/src/corva/api.py @@ -1,10 +1,10 @@ import json import posixpath import re - from typing import List, Optional, Sequence, Union import requests +from urllib3 import Retry from corva.api_utils import get_retry_strategy, get_requests_session from corva.configuration import SETTINGS @@ -35,8 +35,10 @@ def __init__( self.api_key = api_key self.app_key = app_key self.app_connection_id = app_connection_id - self._retry_strategy = get_retry_strategy(max_retries) if max_retries not in (None, 0) else None - self._session = get_requests_session( + self._retry_strategy: Optional[Retry] = ( + get_retry_strategy(max_retries) if max_retries not in (None, 0) else None + ) + self._session: requests.Session = get_requests_session( retry_strategy=self._retry_strategy, pool_connections_count=pool_connections_count, pool_max_size=pool_max_size, diff --git a/src/corva/configuration.py b/src/corva/configuration.py index 7ddd3da4..aafc994c 100644 --- a/src/corva/configuration.py +++ b/src/corva/configuration.py @@ -26,7 +26,7 @@ class Settings(pydantic.BaseSettings): # keep-alive POOL_CONNECTIONS_COUNT: int = 20 # Total pools count POOL_MAX_SIZE: int = 20 # Max connections count per pool/host - POOL_BLOCK: bool = True # If all conn ack - wait until pool connection released, do not throw exc + POOL_BLOCK: bool = True # If all conn ack - wait until pool connection released # retry MAX_RETRY_COUNT: int = 3 # If `0` then retires will be disabled From be9cb65b3fd4ea22ecc15bf41d33f80b83700adb Mon Sep 17 00:00:00 2001 From: Dmytro Kosse <9990225+kossman@users.noreply.github.com> Date: Wed, 23 Jul 2025 18:25:11 +0400 Subject: [PATCH 03/26] feat(DEVC-1752): fix lint --- src/corva/api.py | 12 ++++++------ src/corva/configuration.py | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/corva/api.py b/src/corva/api.py index 3c97b0a5..1bf5c69e 100644 --- a/src/corva/api.py +++ b/src/corva/api.py @@ -6,7 +6,7 @@ import requests from urllib3 import Retry -from corva.api_utils import get_retry_strategy, get_requests_session +from corva.api_utils import get_requests_session, get_retry_strategy from corva.configuration import SETTINGS @@ -160,11 +160,11 @@ def _request( } return self._execute_request( - method=method, - url=url, - params=params, - data=data, - headers=headers, + method=method, + url=url, + params=params, + data=data, + headers=headers, ) def get_dataset( diff --git a/src/corva/configuration.py b/src/corva/configuration.py index aafc994c..edaf6e85 100644 --- a/src/corva/configuration.py +++ b/src/corva/configuration.py @@ -26,7 +26,7 @@ class Settings(pydantic.BaseSettings): # keep-alive POOL_CONNECTIONS_COUNT: int = 20 # Total pools count POOL_MAX_SIZE: int = 20 # Max connections count per pool/host - POOL_BLOCK: bool = True # If all conn ack - wait until pool connection released + POOL_BLOCK: bool = True # Wait until connection released # retry MAX_RETRY_COUNT: int = 3 # If `0` then retires will be disabled From 474612d399d52c87a2bdc71e3ba0b9659a4a0c6a Mon Sep 17 00:00:00 2001 From: Dmytro Kosse <9990225+kossman@users.noreply.github.com> Date: Wed, 23 Jul 2025 18:32:23 +0400 Subject: [PATCH 04/26] feat(DEVC-1752): fix format --- src/corva/api_utils.py | 8 ++++---- src/corva/configuration.py | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/corva/api_utils.py b/src/corva/api_utils.py index a77f4861..54a8b710 100644 --- a/src/corva/api_utils.py +++ b/src/corva/api_utils.py @@ -24,10 +24,10 @@ def get_retry_strategy(max_retries: int) -> Retry: def get_requests_session( - pool_connections_count: int, - pool_max_size: int, - pool_block: bool, - retry_strategy: Optional[Retry] = None, + pool_connections_count: int, + pool_max_size: int, + pool_block: bool, + retry_strategy: Optional[Retry] = None, ) -> requests.Session: adapter = HTTPAdapter( max_retries=retry_strategy, diff --git a/src/corva/configuration.py b/src/corva/configuration.py index edaf6e85..75f641f6 100644 --- a/src/corva/configuration.py +++ b/src/corva/configuration.py @@ -24,12 +24,12 @@ class Settings(pydantic.BaseSettings): SECRETS_CACHE_TTL: int = int(datetime.timedelta(minutes=5).total_seconds()) # keep-alive - POOL_CONNECTIONS_COUNT: int = 20 # Total pools count - POOL_MAX_SIZE: int = 20 # Max connections count per pool/host - POOL_BLOCK: bool = True # Wait until connection released + POOL_CONNECTIONS_COUNT: int = 20 # Total pools count + POOL_MAX_SIZE: int = 20 # Max connections count per pool/host + POOL_BLOCK: bool = True # Wait until connection released # retry - MAX_RETRY_COUNT: int = 3 # If `0` then retires will be disabled + MAX_RETRY_COUNT: int = 3 # If `0` then retires will be disabled SETTINGS = Settings() From bc00f69df48806e53d06aadfc03cc435432e6869 Mon Sep 17 00:00:00 2001 From: Dmytro Kosse <9990225+kossman@users.noreply.github.com> Date: Wed, 23 Jul 2025 18:39:56 +0400 Subject: [PATCH 05/26] feat(DEVC-1752): fix format --- src/corva/api.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/corva/api.py b/src/corva/api.py index 1bf5c69e..e507bd9d 100644 --- a/src/corva/api.py +++ b/src/corva/api.py @@ -25,24 +25,24 @@ def __init__( api_key: str, app_key: str, app_connection_id: Optional[int] = None, - max_retries: Optional[int] = SETTINGS.MAX_RETRY_COUNT, - pool_connections_count: Optional[int] = SETTINGS.POOL_CONNECTIONS_COUNT, - pool_max_size: Optional[int] = SETTINGS.POOL_MAX_SIZE, - pool_block: Optional[bool] = SETTINGS.POOL_BLOCK, + max_retries: Optional[int] = None, + pool_conn_count: Optional[int] = None, + pool_max_size: Optional[int] = None, + pool_block: Optional[bool] = None, ): self.api_url = api_url self.data_api_url = data_api_url self.api_key = api_key self.app_key = app_key self.app_connection_id = app_connection_id - self._retry_strategy: Optional[Retry] = ( - get_retry_strategy(max_retries) if max_retries not in (None, 0) else None + self._retry_strategy: Retry = get_retry_strategy( + max_retries or SETTINGS.MAX_RETRY_COUNT ) - self._session: requests.Session = get_requests_session( + self._session = get_requests_session( retry_strategy=self._retry_strategy, - pool_connections_count=pool_connections_count, - pool_max_size=pool_max_size, - pool_block=pool_block, + pool_connections_count=(pool_conn_count or SETTINGS.POOL_CONNECTIONS_COUNT), + pool_max_size=pool_max_size or SETTINGS.POOL_MAX_SIZE, + pool_block=pool_block or SETTINGS.POOL_BLOCK, ) @property From 41064cd73c663b62d87bb09cdd1cfc92c7e80e87 Mon Sep 17 00:00:00 2001 From: Dmytro Kosse <9990225+kossman@users.noreply.github.com> Date: Wed, 23 Jul 2025 18:47:19 +0400 Subject: [PATCH 06/26] feat(DEVC-1752): bump version for fakeredis since py3.8 no longer supported for versions since 2.30.0 and upper --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index e21327c2..7bcb10f0 100644 --- a/setup.py +++ b/setup.py @@ -39,7 +39,7 @@ packages=setuptools.find_packages("src"), package_dir={"": "src"}, install_requires=[ - "fakeredis[lua] >=2.26.2, <3.0.0", + "fakeredis[lua] >=2.26.2, <2.30.0", "pydantic >=1.8.2, <2.0.0", "redis >=5.2.1, <6.0.0", "requests >=2.32.3, <3.0.0", From 2ce6d39aeebc8722cdef8bdd72a6ca8bf7cdcb79 Mon Sep 17 00:00:00 2001 From: Dmytro Kosse <9990225+kossman@users.noreply.github.com> Date: Fri, 25 Jul 2025 15:15:47 +0400 Subject: [PATCH 07/26] feat(DEVC-1752): remove redundant tests according to new schema for retry logic --- docs/modules/ROOT/examples/api/tutorial008.py | 7 -- src/corva/api.py | 16 ++-- tests/unit/test_api.py | 78 ------------------- tests/unit/test_docs/test_api.py | 7 -- 4 files changed, 5 insertions(+), 103 deletions(-) delete mode 100644 docs/modules/ROOT/examples/api/tutorial008.py diff --git a/docs/modules/ROOT/examples/api/tutorial008.py b/docs/modules/ROOT/examples/api/tutorial008.py deleted file mode 100644 index f42e9126..00000000 --- a/docs/modules/ROOT/examples/api/tutorial008.py +++ /dev/null @@ -1,7 +0,0 @@ -from corva import Api, Cache, ScheduledDataTimeEvent, scheduled - - -@scheduled -def scheduled_app(event: ScheduledDataTimeEvent, api: Api, cache: Cache): - api.max_retries = 5 # Enabling up to 5 retries when HTTP error happens. - ... diff --git a/src/corva/api.py b/src/corva/api.py index e507bd9d..0fed2718 100644 --- a/src/corva/api.py +++ b/src/corva/api.py @@ -25,7 +25,7 @@ def __init__( api_key: str, app_key: str, app_connection_id: Optional[int] = None, - max_retries: Optional[int] = None, + max_retries: Optional[int] = 0, pool_conn_count: Optional[int] = None, pool_max_size: Optional[int] = None, pool_block: Optional[bool] = None, @@ -52,16 +52,6 @@ def default_headers(self): "X-Corva-App": self.app_key, } - @property - def max_retries(self) -> int: - return self._max_retries - - @max_retries.setter - def max_retries(self, value: int): - if not (0 <= value <= 10): - raise ValueError("Values between 0 and 10 are allowed") - self._max_retries = value - def get(self, path: str, **kwargs): return self._request("GET", path, **kwargs) @@ -109,6 +99,7 @@ def _execute_request( params: Optional[dict], data: Optional[dict], headers: Optional[dict] = None, + timeout: Optional[int] = None, ) -> requests.Response: """Executes the request. @@ -129,6 +120,7 @@ def _execute_request( params=params, json=data, headers=headers, + timeout=timeout, ) def _request( @@ -139,6 +131,7 @@ def _request( data: Optional[dict] = None, params: Optional[dict] = None, headers: Optional[dict] = None, + timeout: Optional[int] = None, ) -> requests.Response: """Prepares HTTP request. @@ -165,6 +158,7 @@ def _request( params=params, data=data, headers=headers, + timeout=timeout, ) def get_dataset( diff --git a/tests/unit/test_api.py b/tests/unit/test_api.py index 0c424a99..ef09212f 100644 --- a/tests/unit/test_api.py +++ b/tests/unit/test_api.py @@ -1,7 +1,6 @@ import contextlib import json import re -import time import urllib.parse from http import HTTPStatus @@ -74,8 +73,6 @@ def test_request_additional_headers(api, requests_mock: RequestsMocker): [ (3, contextlib.nullcontext()), (30, contextlib.nullcontext()), - (2, pytest.raises(ValueError)), - (31, pytest.raises(ValueError)), ], ) def test_request_timeout_limits(timeout, exc_ctx, api, requests_mock: RequestsMocker): @@ -200,81 +197,6 @@ def test_disabled_by_default_retrying_logic_works_as_expected( ), "For disabled by default retrying functionality only 1 request should happen." -def test_enabled_retrying_logic_with_all_failed_retries_returns_http_response_object( - api, requests_mock: RequestsMocker -): - api.max_retries = 1 - path = "/" - url = f"{SETTINGS.API_ROOT_URL}{path}" - - bad_requests_statuses_codes = [ - HTTPStatus.BAD_GATEWAY, - HTTPStatus.SERVICE_UNAVAILABLE, - ] - - requests_mock.register_uri( - "GET", - url, - [ - {"status_code": int(status_code)} - for status_code in bad_requests_statuses_codes - ], - ) - - # Making sure all retrying attempts were failed. - assert api.max_retries < len(bad_requests_statuses_codes) - - response = api.get(path) - - assert response.status_code == bad_requests_statuses_codes[0] - - -def test_enabled_retrying_logic_works_as_expected(api, requests_mock: RequestsMocker): - api.max_retries = 6 # Enabling retrying functionality to make up to 6 attempts. - path = "/" - url = f"{SETTINGS.API_ROOT_URL}{path}" - - bad_requests_statuses_codes = [ - HTTPStatus.TOO_MANY_REQUESTS, - HTTPStatus.INTERNAL_SERVER_ERROR, - HTTPStatus.BAD_GATEWAY, - HTTPStatus.SERVICE_UNAVAILABLE, - HTTPStatus.GATEWAY_TIMEOUT, - ] - good_requests_statuses_codes = [HTTPStatus.OK] - - requests_sequence_return_status_codes = ( - bad_requests_statuses_codes + good_requests_statuses_codes - ) - - requests_mock.register_uri( - "GET", - url, - [ - {"status_code": int(status_code)} - for status_code in requests_sequence_return_status_codes - ], - ) - - start_time = time.time() - response = api.get(path) - end_time = time.time() - - assert response.status_code in good_requests_statuses_codes - assert len(requests_mock.request_history) == len( - requests_sequence_return_status_codes - ) - assert end_time - start_time > 1, ( - f"At least 1 second retry delay should be applied for " - f"{len(bad_requests_statuses_codes)} retries." - ) - - -def test__trying_to_set_wrong_max_retries__value_error_raised(api): - with pytest.raises(ValueError): - api.max_retries = 15 - - def test__app_insert_data__improves_coverage(api, requests_mock: RequestsMocker): post_mock = requests_mock.post( diff --git a/tests/unit/test_docs/test_api.py b/tests/unit/test_docs/test_api.py index e148de13..e2e6a920 100644 --- a/tests/unit/test_docs/test_api.py +++ b/tests/unit/test_docs/test_api.py @@ -15,7 +15,6 @@ tutorial005, tutorial006, tutorial007, - tutorial008, ) @@ -122,9 +121,3 @@ def test_tutorial007(app_runner, mocker: MockerFixture): app_runner(tutorial007.scheduled_app, time_event) time_mock.assert_called_once() - -def test_tutorial008(app_runner, mocker: MockerFixture): - time_event = ScheduledDataTimeEvent( - asset_id=0, company_id=0, start_time=0, end_time=0 - ) - app_runner(tutorial008.scheduled_app, time_event) From 124716079c1b8f67450b5f32e0928ca7aaf005cf Mon Sep 17 00:00:00 2001 From: Dmytro Kosse <9990225+kossman@users.noreply.github.com> Date: Fri, 25 Jul 2025 15:18:17 +0400 Subject: [PATCH 08/26] feat(DEVC-1752): fix format --- tests/unit/test_docs/test_api.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/unit/test_docs/test_api.py b/tests/unit/test_docs/test_api.py index e2e6a920..ff58d915 100644 --- a/tests/unit/test_docs/test_api.py +++ b/tests/unit/test_docs/test_api.py @@ -120,4 +120,3 @@ def test_tutorial007(app_runner, mocker: MockerFixture): time_mock = mocker.patch.object(Api, 'insert_data') app_runner(tutorial007.scheduled_app, time_event) time_mock.assert_called_once() - From b6d72f9e38046c372720e0fdf551eb217d557d1b Mon Sep 17 00:00:00 2001 From: Dmytro Kosse <9990225+kossman@users.noreply.github.com> Date: Fri, 25 Jul 2025 15:22:48 +0400 Subject: [PATCH 09/26] feat(DEVC-1752): skip some tests on py3.13 regarding logging/capsys --- tests/unit/test_logging.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/unit/test_logging.py b/tests/unit/test_logging.py index 248d363b..ae69c79c 100644 --- a/tests/unit/test_logging.py +++ b/tests/unit/test_logging.py @@ -1,5 +1,6 @@ import datetime import logging +import sys import freezegun import pytest @@ -154,6 +155,7 @@ def app(event, api, cache): assert capsys.readouterr().out == expected +@pytest.mark.skipif(sys.version_info >= (3, 13), reason="Fails on 3.13 due to logging/capsys issue") def test_each_app_invoke_has_separate_logger(context, capsys, mocker: MockerFixture): @stream def app(event, api, cache): @@ -234,6 +236,7 @@ def app(event, api, cache): assert capsys.readouterr().out == expected +@pytest.mark.skipif(sys.version_info >= (3, 13), reason="Fails on 3.13 due to logging/capsys issue") @pytest.mark.parametrize( 'max_message_count, expected', [ From 1d1623f39cb65a4fcec10c6bfb349f1440295d72 Mon Sep 17 00:00:00 2001 From: Dmytro Kosse <9990225+kossman@users.noreply.github.com> Date: Fri, 25 Jul 2025 15:26:04 +0400 Subject: [PATCH 10/26] feat(DEVC-1752): fix format --- .../ROOT/examples/testing/tutorial008.py | 38 ------------------- tests/unit/test_logging.py | 10 ++++- 2 files changed, 8 insertions(+), 40 deletions(-) delete mode 100644 docs/modules/ROOT/examples/testing/tutorial008.py diff --git a/docs/modules/ROOT/examples/testing/tutorial008.py b/docs/modules/ROOT/examples/testing/tutorial008.py deleted file mode 100644 index f7d23f2e..00000000 --- a/docs/modules/ROOT/examples/testing/tutorial008.py +++ /dev/null @@ -1,38 +0,0 @@ -from corva import ScheduledDataTimeEvent, scheduled -from corva.service.cache_sdk import UserRedisSdk - - -@scheduled -def scheduled_fibonacci(event, api, cache): # <.> - number1 = int(cache.get('number1') or 1) - number2 = int(cache.get('number2') or 1) - number3 = number1 + number2 - cache.set('number1', number2) - cache.set('number2', number3) - - return number3 - - -def test_reset_cache(app_runner): # <.> - event = ScheduledDataTimeEvent( - asset_id=0, company_id=0, start_time=0, end_time=0 - ) # <.> - - for _ in range(5): - result = app_runner(scheduled_fibonacci, event) # <.> - assert result == 2 # <.> - - -def test_reuse_cache(app_runner): # <.> - event = ScheduledDataTimeEvent( - asset_id=0, company_id=0, start_time=0, end_time=0 - ) # <.> - - cache = UserRedisSdk( - hash_name='hash_name', redis_dsn='redis://localhost', use_fakes=True - ) # <.> - - expected_results = [2, 3, 5, 8, 13, 21, 34, 55] - for expected_result in expected_results: - result = app_runner(scheduled_fibonacci, event, cache=cache) # <.> - assert result == expected_result # <.> diff --git a/tests/unit/test_logging.py b/tests/unit/test_logging.py index ae69c79c..48085286 100644 --- a/tests/unit/test_logging.py +++ b/tests/unit/test_logging.py @@ -155,7 +155,10 @@ def app(event, api, cache): assert capsys.readouterr().out == expected -@pytest.mark.skipif(sys.version_info >= (3, 13), reason="Fails on 3.13 due to logging/capsys issue") +@pytest.mark.skipif( + condition=sys.version_info >= (3, 13), + reason="Fails on 3.13 due to logging/capsys issue" +) def test_each_app_invoke_has_separate_logger(context, capsys, mocker: MockerFixture): @stream def app(event, api, cache): @@ -236,7 +239,10 @@ def app(event, api, cache): assert capsys.readouterr().out == expected -@pytest.mark.skipif(sys.version_info >= (3, 13), reason="Fails on 3.13 due to logging/capsys issue") +@pytest.mark.skipif( + condition=sys.version_info >= (3, 13), + reason="Fails on 3.13 due to logging/capsys issue" +) @pytest.mark.parametrize( 'max_message_count, expected', [ From a7bc9db55f96ab761c2c9c29d828d7e4a1e39d14 Mon Sep 17 00:00:00 2001 From: Dmytro Kosse <9990225+kossman@users.noreply.github.com> Date: Fri, 25 Jul 2025 15:28:10 +0400 Subject: [PATCH 11/26] Revert "feat(DEVC-1752): fix format" This reverts commit 1d1623f39cb65a4fcec10c6bfb349f1440295d72. --- .../ROOT/examples/testing/tutorial008.py | 38 +++++++++++++++++++ tests/unit/test_logging.py | 10 +---- 2 files changed, 40 insertions(+), 8 deletions(-) create mode 100644 docs/modules/ROOT/examples/testing/tutorial008.py diff --git a/docs/modules/ROOT/examples/testing/tutorial008.py b/docs/modules/ROOT/examples/testing/tutorial008.py new file mode 100644 index 00000000..f7d23f2e --- /dev/null +++ b/docs/modules/ROOT/examples/testing/tutorial008.py @@ -0,0 +1,38 @@ +from corva import ScheduledDataTimeEvent, scheduled +from corva.service.cache_sdk import UserRedisSdk + + +@scheduled +def scheduled_fibonacci(event, api, cache): # <.> + number1 = int(cache.get('number1') or 1) + number2 = int(cache.get('number2') or 1) + number3 = number1 + number2 + cache.set('number1', number2) + cache.set('number2', number3) + + return number3 + + +def test_reset_cache(app_runner): # <.> + event = ScheduledDataTimeEvent( + asset_id=0, company_id=0, start_time=0, end_time=0 + ) # <.> + + for _ in range(5): + result = app_runner(scheduled_fibonacci, event) # <.> + assert result == 2 # <.> + + +def test_reuse_cache(app_runner): # <.> + event = ScheduledDataTimeEvent( + asset_id=0, company_id=0, start_time=0, end_time=0 + ) # <.> + + cache = UserRedisSdk( + hash_name='hash_name', redis_dsn='redis://localhost', use_fakes=True + ) # <.> + + expected_results = [2, 3, 5, 8, 13, 21, 34, 55] + for expected_result in expected_results: + result = app_runner(scheduled_fibonacci, event, cache=cache) # <.> + assert result == expected_result # <.> diff --git a/tests/unit/test_logging.py b/tests/unit/test_logging.py index 48085286..ae69c79c 100644 --- a/tests/unit/test_logging.py +++ b/tests/unit/test_logging.py @@ -155,10 +155,7 @@ def app(event, api, cache): assert capsys.readouterr().out == expected -@pytest.mark.skipif( - condition=sys.version_info >= (3, 13), - reason="Fails on 3.13 due to logging/capsys issue" -) +@pytest.mark.skipif(sys.version_info >= (3, 13), reason="Fails on 3.13 due to logging/capsys issue") def test_each_app_invoke_has_separate_logger(context, capsys, mocker: MockerFixture): @stream def app(event, api, cache): @@ -239,10 +236,7 @@ def app(event, api, cache): assert capsys.readouterr().out == expected -@pytest.mark.skipif( - condition=sys.version_info >= (3, 13), - reason="Fails on 3.13 due to logging/capsys issue" -) +@pytest.mark.skipif(sys.version_info >= (3, 13), reason="Fails on 3.13 due to logging/capsys issue") @pytest.mark.parametrize( 'max_message_count, expected', [ From 98e9bc8e35e37db8877007c594177a65923bad04 Mon Sep 17 00:00:00 2001 From: Dmytro Kosse <9990225+kossman@users.noreply.github.com> Date: Fri, 25 Jul 2025 15:33:57 +0400 Subject: [PATCH 12/26] feat(DEVC-1752): try again --- tests/unit/test_docs/test_testing.py | 1 - tests/unit/test_logging.py | 2 -- 2 files changed, 3 deletions(-) diff --git a/tests/unit/test_docs/test_testing.py b/tests/unit/test_docs/test_testing.py index fca01cc2..5bde6264 100644 --- a/tests/unit/test_docs/test_testing.py +++ b/tests/unit/test_docs/test_testing.py @@ -8,7 +8,6 @@ tutorial005, tutorial006, tutorial007, - tutorial008, ) diff --git a/tests/unit/test_logging.py b/tests/unit/test_logging.py index ae69c79c..ea20a82e 100644 --- a/tests/unit/test_logging.py +++ b/tests/unit/test_logging.py @@ -155,7 +155,6 @@ def app(event, api, cache): assert capsys.readouterr().out == expected -@pytest.mark.skipif(sys.version_info >= (3, 13), reason="Fails on 3.13 due to logging/capsys issue") def test_each_app_invoke_has_separate_logger(context, capsys, mocker: MockerFixture): @stream def app(event, api, cache): @@ -236,7 +235,6 @@ def app(event, api, cache): assert capsys.readouterr().out == expected -@pytest.mark.skipif(sys.version_info >= (3, 13), reason="Fails on 3.13 due to logging/capsys issue") @pytest.mark.parametrize( 'max_message_count, expected', [ From 8d9476b10c2a5a123b9f28a4a1ecc617b903641b Mon Sep 17 00:00:00 2001 From: Dmytro Kosse <9990225+kossman@users.noreply.github.com> Date: Fri, 25 Jul 2025 15:53:01 +0400 Subject: [PATCH 13/26] feat(DEVC-1752): try to bump pytest version to more fresh --- requirements-test.txt | 2 +- tests/unit/test_docs/test_testing.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements-test.txt b/requirements-test.txt index 72512041..3f183293 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -1,5 +1,5 @@ coverage==7.6.1 freezegun==1.5.1 -pytest==6.2.5 +pytest==8.4.1 pytest-mock==3.3.1 requests-mock==1.8.0 diff --git a/tests/unit/test_docs/test_testing.py b/tests/unit/test_docs/test_testing.py index 5bde6264..fca01cc2 100644 --- a/tests/unit/test_docs/test_testing.py +++ b/tests/unit/test_docs/test_testing.py @@ -8,6 +8,7 @@ tutorial005, tutorial006, tutorial007, + tutorial008, ) From cb15d1e527063b7a8426665a637c3ab0dd822e92 Mon Sep 17 00:00:00 2001 From: Dmytro Kosse <9990225+kossman@users.noreply.github.com> Date: Fri, 25 Jul 2025 15:56:36 +0400 Subject: [PATCH 14/26] feat(DEVC-1752): try to bump 3.13 version --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 95f35930..c1be1d01 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -3,7 +3,7 @@ name: CI on: push env: - PYTHON_VERSIONS: '[ "3.8", "3.9", "3.10", "3.11", "3.12", "3.13" ]' + PYTHON_VERSIONS: '[ "3.8", "3.9", "3.10", "3.11", "3.12", "3.13.1" ]' jobs: From 40dd5cc9f796328dfa8d1de7c1d3f3ddce60d0ca Mon Sep 17 00:00:00 2001 From: Dmytro Kosse <9990225+kossman@users.noreply.github.com> Date: Fri, 25 Jul 2025 15:57:06 +0400 Subject: [PATCH 15/26] feat(DEVC-1752): try to bump 3.13 version --- requirements-test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-test.txt b/requirements-test.txt index 3f183293..72512041 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -1,5 +1,5 @@ coverage==7.6.1 freezegun==1.5.1 -pytest==8.4.1 +pytest==6.2.5 pytest-mock==3.3.1 requests-mock==1.8.0 From 5902deb01d5f641fa5f9e97f0c4418ad4722b328 Mon Sep 17 00:00:00 2001 From: Dmytro Kosse <9990225+kossman@users.noreply.github.com> Date: Fri, 25 Jul 2025 15:58:19 +0400 Subject: [PATCH 16/26] eat(DEVC-1752): try to bump 3.13 version --- tests/unit/test_logging.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/unit/test_logging.py b/tests/unit/test_logging.py index ea20a82e..248d363b 100644 --- a/tests/unit/test_logging.py +++ b/tests/unit/test_logging.py @@ -1,6 +1,5 @@ import datetime import logging -import sys import freezegun import pytest From 9fae7587f38873c2fecf4fdbc9c5dd6c57def02d Mon Sep 17 00:00:00 2001 From: Dmytro Kosse <9990225+kossman@users.noreply.github.com> Date: Fri, 25 Jul 2025 15:59:50 +0400 Subject: [PATCH 17/26] feat(DEVC-1752): try to bump 3.13 version to 3.13.4 --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c1be1d01..11f40bbf 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -3,7 +3,7 @@ name: CI on: push env: - PYTHON_VERSIONS: '[ "3.8", "3.9", "3.10", "3.11", "3.12", "3.13.1" ]' + PYTHON_VERSIONS: '[ "3.8", "3.9", "3.10", "3.11", "3.12", "3.13.4" ]' jobs: From de5ad56b0adbd0b4dc674b98449f089c5a360667 Mon Sep 17 00:00:00 2001 From: Dmytro Kosse <9990225+kossman@users.noreply.github.com> Date: Fri, 25 Jul 2025 16:00:59 +0400 Subject: [PATCH 18/26] feat(DEVC-1752): try to bump 3.13 version to 3.13.3 --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 11f40bbf..2c104911 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -3,7 +3,7 @@ name: CI on: push env: - PYTHON_VERSIONS: '[ "3.8", "3.9", "3.10", "3.11", "3.12", "3.13.4" ]' + PYTHON_VERSIONS: '[ "3.8", "3.9", "3.10", "3.11", "3.12", "3.13.3" ]' jobs: From 15530edd688ab652d9299ac62f8a63da284eaf27 Mon Sep 17 00:00:00 2001 From: Dmytro Kosse <9990225+kossman@users.noreply.github.com> Date: Fri, 25 Jul 2025 16:05:19 +0400 Subject: [PATCH 19/26] feat(DEVC-1752): remove unused dependency tenacity for python-sdk --- setup.py | 1 - 1 file changed, 1 deletion(-) diff --git a/setup.py b/setup.py index 7bcb10f0..e929419b 100644 --- a/setup.py +++ b/setup.py @@ -44,7 +44,6 @@ "redis >=5.2.1, <6.0.0", "requests >=2.32.3, <3.0.0", "urllib3 <2", # lambda doesnt support version 2 yet - "tenacity >=8.2.3, <9.0.0", ], python_requires='>=3.8, <4.0', license='The Unlicense', From 5483f76287306299ff28ae3db24677ef9c4b150f Mon Sep 17 00:00:00 2001 From: Dmytro Kosse <9990225+kossman@users.noreply.github.com> Date: Fri, 25 Jul 2025 16:18:16 +0400 Subject: [PATCH 20/26] feat(DEVC-1752): update changelog --- CHANGELOG.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 936bf4b6..755f5aef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [1.14.1] - 2025-07-25 +### Added +- Session mechanism for significantly decrease number of an http load on data-api for apps with intensive calling +- Added possibility to adjust some params related to connection pool + - `POOL_CONNECTIONS_COUNT`: Total pools count + - `POOL_MAX_SIZE`: Max connections count per pool/host + - `POOL_BLOCK`: Wait until connection released or not (instantly raise an exception) + - `MAX_RETRY_COUNT`: If 0 then retires will be disabled, otherwise retrying logic will be used +- Move retrying logic from `tenacity` to internal `urllib3.util.Retry(...)` +- Removed redundant dependency `tenacity` from `python-sdk` +- Bump version for `py3.13` to `py3.13.3` at CI version matrix in order to fix broken tests for logging +- Bump version for `fakeredis` to fix some tests + + ## [1.14.0] - 2025-04-17 ### Fixed - merge_events parameter for scheduled data time apps should result in correct start/end times in a final app event. From 0be9d54f97208363f92dacf06cc4abf277d36c3c Mon Sep 17 00:00:00 2001 From: Dmytro Kosse <9990225+kossman@users.noreply.github.com> Date: Fri, 25 Jul 2025 16:28:49 +0400 Subject: [PATCH 21/26] feat(DEVC-1752): add adjusting debug level for `urllib3.connectionpool` logger --- src/corva/logger.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/corva/logger.py b/src/corva/logger.py index 32edd6d2..fff95d12 100644 --- a/src/corva/logger.py +++ b/src/corva/logger.py @@ -12,6 +12,8 @@ CORVA_LOGGER = logging.getLogger('corva') CORVA_LOGGER.setLevel(SETTINGS.LOG_LEVEL) +logging.getLogger("urllib3.connectionpool").setLevel(SETTINGS.LOG_LEVEL) + # unset to pass messages to ancestor loggers, including OTel Log Sending handler # see https://github.com/corva-ai/otel/pull/37 # see https://corvaqa.atlassian.net/browse/EE-31 From 3b3f761cd94fc2ea068dd88a53aa5319b7bfb95a Mon Sep 17 00:00:00 2001 From: Dmytro Kosse <9990225+kossman@users.noreply.github.com> Date: Fri, 25 Jul 2025 16:34:49 +0400 Subject: [PATCH 22/26] feat(DEVC-1752): pre release --- docs/antora-playbook.yml | 2 +- docs/antora.yml | 2 +- src/version.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/antora-playbook.yml b/docs/antora-playbook.yml index e8b6546e..26c24511 100644 --- a/docs/antora-playbook.yml +++ b/docs/antora-playbook.yml @@ -7,7 +7,7 @@ content: start_path: docs branches: [] # branches: HEAD # Use this for local development - tags: [v1.14.0] + tags: [v1.14.1] asciidoc: attributes: page-toclevels: 5 diff --git a/docs/antora.yml b/docs/antora.yml index da9cec15..d88fd5fb 100644 --- a/docs/antora.yml +++ b/docs/antora.yml @@ -1,3 +1,3 @@ name: corva-sdk -version: ~ +version: 1.14.1 nav: [modules/ROOT/nav.adoc] diff --git a/src/version.py b/src/version.py index c1230ddf..ffeef941 100644 --- a/src/version.py +++ b/src/version.py @@ -1 +1 @@ -VERSION = "1.14.0" +VERSION = "1.14.1" From 91051e48490755b441ee414348bd7b32f43baa92 Mon Sep 17 00:00:00 2001 From: Dmytro Kosse <9990225+kossman@users.noreply.github.com> Date: Fri, 25 Jul 2025 16:39:24 +0400 Subject: [PATCH 23/26] feat(DEVC-1752): pre release --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 755f5aef..8bb200f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -419,7 +419,8 @@ env variables, that should be used to configure logging. - Event classes: `StreamEvent`, `ScheduledEvent` and `TaskEvent`. -[Unreleased] https://github.com/corva-ai/python-sdk/compare/v1.14.0...master +[Unreleased] https://github.com/corva-ai/python-sdk/compare/v1.14.1...master +[1.14.1] https://github.com/corva-ai/python-sdk/compare/v1.14.0...v1.14.1 [1.14.0] https://github.com/corva-ai/python-sdk/compare/v1.13.1...v1.14.0 [1.13.1] https://github.com/corva-ai/python-sdk/compare/v1.13.0...v1.13.1 [1.13.0] https://github.com/corva-ai/python-sdk/compare/v1.12.1...v1.13.0 From 6e54eb5a9fb03eaccfc834946fed3a4dae588d73 Mon Sep 17 00:00:00 2001 From: Dmytro Kosse <9990225+kossman@users.noreply.github.com> Date: Fri, 25 Jul 2025 18:12:40 +0400 Subject: [PATCH 24/26] feat(DEVC-1752): unify retry logic with existing NodeJS way --- src/corva/api.py | 12 ++++++------ src/corva/api_utils.py | 9 ++++++--- src/corva/configuration.py | 3 ++- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/corva/api.py b/src/corva/api.py index 0fed2718..333c19ba 100644 --- a/src/corva/api.py +++ b/src/corva/api.py @@ -4,7 +4,6 @@ from typing import List, Optional, Sequence, Union import requests -from urllib3 import Retry from corva.api_utils import get_requests_session, get_retry_strategy from corva.configuration import SETTINGS @@ -25,7 +24,8 @@ def __init__( api_key: str, app_key: str, app_connection_id: Optional[int] = None, - max_retries: Optional[int] = 0, + max_retries: Optional[int] = 3, + backoff_factor_retries: Optional[float] = 1, pool_conn_count: Optional[int] = None, pool_max_size: Optional[int] = None, pool_block: Optional[bool] = None, @@ -35,11 +35,11 @@ def __init__( self.api_key = api_key self.app_key = app_key self.app_connection_id = app_connection_id - self._retry_strategy: Retry = get_retry_strategy( - max_retries or SETTINGS.MAX_RETRY_COUNT - ) self._session = get_requests_session( - retry_strategy=self._retry_strategy, + retry_strategy=get_retry_strategy( + max_retries=max_retries or SETTINGS.MAX_RETRY_COUNT, + backoff_factor=backoff_factor_retries or SETTINGS.BACKOFF_FACTOR, + ), pool_connections_count=(pool_conn_count or SETTINGS.POOL_CONNECTIONS_COUNT), pool_max_size=pool_max_size or SETTINGS.POOL_MAX_SIZE, pool_block=pool_block or SETTINGS.POOL_BLOCK, diff --git a/src/corva/api_utils.py b/src/corva/api_utils.py index 54a8b710..0a839008 100644 --- a/src/corva/api_utils.py +++ b/src/corva/api_utils.py @@ -12,14 +12,17 @@ 504, # HTTPStatus.GATEWAY_TIMEOUT ) +# All HTTP methods allowed, see this discussion: +# https://corva.slack.com/archives/C0411LUPVL6/p1753451234091869 +ALLOWED_RETRY_METHODS = ("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD", "TRACE") -def get_retry_strategy(max_retries: int) -> Retry: +def get_retry_strategy(max_retries: int, backoff_factor: float = 1) -> Retry: return Retry( total=max_retries, - backoff_factor=0.3, + backoff_factor=backoff_factor, status_forcelist=RETRYABLE_STATUS_CODES, - allowed_methods={'GET', 'POST'}, # Support both raise_on_status=False, + allowed_methods=ALLOWED_RETRY_METHODS ) diff --git a/src/corva/configuration.py b/src/corva/configuration.py index 75f641f6..77edc175 100644 --- a/src/corva/configuration.py +++ b/src/corva/configuration.py @@ -29,7 +29,8 @@ class Settings(pydantic.BaseSettings): POOL_BLOCK: bool = True # Wait until connection released # retry - MAX_RETRY_COUNT: int = 3 # If `0` then retires will be disabled + MAX_RETRY_COUNT: int = 3 # If `0` then retries will be disabled + BACKOFF_FACTOR: float = 1.0 SETTINGS = Settings() From 348771acfaa9425ebf9dc4a97c80efde85a79401 Mon Sep 17 00:00:00 2001 From: Dmytro Kosse <9990225+kossman@users.noreply.github.com> Date: Fri, 25 Jul 2025 18:14:19 +0400 Subject: [PATCH 25/26] feat(DEVC-1752): fix linter --- src/corva/api_utils.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/corva/api_utils.py b/src/corva/api_utils.py index 0a839008..f3ac984d 100644 --- a/src/corva/api_utils.py +++ b/src/corva/api_utils.py @@ -14,7 +14,17 @@ # All HTTP methods allowed, see this discussion: # https://corva.slack.com/archives/C0411LUPVL6/p1753451234091869 -ALLOWED_RETRY_METHODS = ("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD", "TRACE") +ALLOWED_RETRY_METHODS = ( + "GET", + "POST", + "PUT", + "PATCH", + "DELETE", + "OPTIONS", + "HEAD", + "TRACE", +) + def get_retry_strategy(max_retries: int, backoff_factor: float = 1) -> Retry: return Retry( From 5290ae522fc56bd052e17fe6d6959b45e6805ec5 Mon Sep 17 00:00:00 2001 From: Dmytro Kosse <9990225+kossman@users.noreply.github.com> Date: Fri, 25 Jul 2025 18:25:08 +0400 Subject: [PATCH 26/26] feat(DEVC-1752): fix format --- src/corva/api_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/corva/api_utils.py b/src/corva/api_utils.py index f3ac984d..cbfe86f7 100644 --- a/src/corva/api_utils.py +++ b/src/corva/api_utils.py @@ -32,7 +32,7 @@ def get_retry_strategy(max_retries: int, backoff_factor: float = 1) -> Retry: backoff_factor=backoff_factor, status_forcelist=RETRYABLE_STATUS_CODES, raise_on_status=False, - allowed_methods=ALLOWED_RETRY_METHODS + allowed_methods=ALLOWED_RETRY_METHODS, )