diff --git a/API.md b/API.md index b6f42e1..ba4ac3f 100644 --- a/API.md +++ b/API.md @@ -4,14 +4,24 @@ Внутри объекта ведётся учёт скорости отправки запросов к серверу, поэтому важно, чтобы все запросы приложения в отношении одного аккаунта с одного IP-адреса отправлялись из одного экземпляра `Bitrix`. -### Метод ` __init__(self, webhook: str, verbose: bool = True, respect_velocity_policy: bool = True, client: aiohttp.ClientSession = None):` -Создаёт экземпляр объекта `Bitrix`. +### Метод ` __init__(self, webhook: str, verbose: bool = True, respect_velocity_policy: bool = True, request_pool_size: int = 50, requests_per_second: float = 2.0, client: aiohttp.ClientSession = None):` +Создаёт клиента для доступа к Битрикс24. #### Параметры -* `webhook: str` - URL вебхука, полученного от сервера Битрикс. -* `verbose: bool = True` - показывать прогрессбар при выполнении запроса. -* `respect_velocity_policy: bool = True` - соблюдать политику Битрикса о скорости запросов. -* `client: aiohttp.ClientSession = None` - использовать для HTTP-вызовов клиента, инициализированного и настроенного пользователем. +- `webhook: str` - URL вебхука, полученного от сервера Битрикс. +- `verbose: bool = True` - показывать прогрессбар при выполнении запроса. +- `respect_velocity_policy: bool = True` - соблюдать политику Битрикса о скорости запросов. +- `request_pool_size: int = 50` - размер пула запросов, который +можно отправить на сервер без ожидания +- `requests_per_second: float = 2.0` - максимальная скорость запросов, +которая будет использоваться после переполнения пула +- `client: aiohttp.ClientSession = None` - использовать для HTTP-вызовов клиента, инициализированного и настроенного пользователем. + +Параметры `request_pool_size` и `requests_per_second` установлены согласно ограничениям Битрикс24. + +Вы можете повысить их, если у вас Энтерпрайз (https://dev.1c-bitrix.ru/learning/course/index.php?COURSE_ID=93&LESSON_ID=7885). + +Либо, если хотите снизить скорость запросов к серверу, вы можете понизить значение этих параметров. ### Метод `get_all(self, method: str, params: dict = None) -> list | dict` Получить полный список сущностей по запросу `method`. diff --git a/README.md b/README.md index e068dd9..8896f5c 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ API wrapper для Питона для быстрого получения да - Продвинутые стратегии работы с постраничным доступом ускоряют выгрузку на порядки (см. [результаты тестов](https://github.com/leshchenko1979/fast_bitrix24/discussions/113)). ### Избежание отказов сервера +- Соблюдаются все [политики Битрикса по ограничению скорости запросов](#официальная-политика-битрикс24-по-скорости-запросов) - Автоматический autothrottling - если сервер возвращает ошибки, скорость автоматически понижается. - Если сервер для сложных запросов начинает возвращать ошибки, можно в одну строку понизить скорость запроосов. @@ -188,7 +189,11 @@ leads = await b.get_all('crm.lead.list') Библиотека соблюдает официальные ограничения Битрикс24 по скорости запросов (см. ниже "Официальная политика Битрикс24 по скорости запросов"). Одновременно, она начинает снижать скорость запросов, если сервер начинает возвращать ошибки (autothrottling). Подобный подход позволяет на порядки увеличить скорость получения данных (см. [тесты скорости](https://github.com/leshchenko1979/fast_bitrix24/blob/master/speed_tests/strategies.ipynb)). ### Официальная политика Битрикс24 по скорости запросов -https://helpdesk.bitrix24.ru/open/15959788 +Одновременно работает два ограничения: +1. Ограничение по методу Leaky Bucket: https://dev.1c-bitrix.ru/learning/course/index.php?COURSE_ID=93&LESSON_ID=7885 +2. Ограничение по методу Sliding Window в разрезе каждого метода: https://helpdesk.bitrix24.ru/open/15959788 + +Оба эти ограничения соблюдаются библиотекой. ## Советы и подсказки ### А умеет ли ваша библиотека ...? @@ -289,6 +294,15 @@ b = BitrixAsync(webhook) leads = await b.get_all('crm.lead.list') ``` +## У меня Энтерпрайз. Как мне настроить более высокую скорость запросов? + +В конструкторе указывайте параметры `request_pool_size=250` и `requests_per_second=5`: + +```python +from fast_bitrix24 import Bitrix +b = Bitrix(webhook, request_pool_size=250, requests_per_second=5) +``` + ## Как связаться с автором - telegram: https://t.me/+U7hfrV7h53bRvKAS - создать новый github issue: https://github.com/leshchenko1979/fast_bitrix24/issues/new diff --git a/fast_bitrix24/bitrix.py b/fast_bitrix24/bitrix.py index d612ee6..207d0b1 100644 --- a/fast_bitrix24/bitrix.py +++ b/fast_bitrix24/bitrix.py @@ -30,10 +30,12 @@ def __init__( webhook: str, verbose: bool = True, respect_velocity_policy: bool = True, + request_pool_size: int = 50, + requests_per_second: float = 2.0, client: aiohttp.ClientSession = None, ): """ - Создает объект класса BitrixAsync. + Создает объект для запросов к Битрикс24. Параметры: - `webhook: str` - URL вебхука, полученного от сервера Битрикс @@ -41,12 +43,22 @@ def __init__( запроса - `respect_velocity_policy: bool = True` - соблюдать ли политику Битрикса о скорости запросов + - `request_pool_size: int = 50` - размер пула запросов, который + можно отправить на сервер без ожидания + - `requests_per_second: float = 2.0` - максимальная скорость запросов, + которая будет использоваться после переполнения пула - `client: aiohttp.ClientSession = None` - использовать для HTTP-вызовов объект aiohttp.ClientSession, инициализированнный и настроенный пользователем. """ - self.srh = ServerRequestHandler(webhook, respect_velocity_policy, client) + self.srh = ServerRequestHandler( + webhook=webhook, + respect_velocity_policy=respect_velocity_policy, + request_pool_size=request_pool_size, + requests_per_second=requests_per_second, + client=client, + ) self.verbose = verbose @log diff --git a/fast_bitrix24/srh.py b/fast_bitrix24/srh.py index 05ff4fe..b463af3 100644 --- a/fast_bitrix24/srh.py +++ b/fast_bitrix24/srh.py @@ -12,9 +12,6 @@ from .logger import logger from .utils import _url_valid -BITRIX_POOL_SIZE = 50 -BITRIX_RPS = 2.0 - BITRIX_MAX_BATCH_SIZE = 50 BITRIX_MAX_CONCURRENT_REQUESTS = 50 @@ -54,7 +51,14 @@ class ServerRequestHandler: последовательных запросов к серверу. """ - def __init__(self, webhook, respect_velocity_policy, client): + def __init__( + self, + webhook: str, + respect_velocity_policy: bool, + request_pool_size: int, + requests_per_second: float, + client, + ): self.webhook = self.standardize_webhook(webhook) self.respect_velocity_policy = respect_velocity_policy @@ -83,7 +87,9 @@ def __init__(self, webhook, respect_velocity_policy, client): # rate throttlers by method self.method_throttlers = {} # dict[str, LeakyBucketLimiter] - self.leaky_bucket_throttler = LeakyBucketThrottler(BITRIX_POOL_SIZE, BITRIX_RPS) + self.leaky_bucket_throttler = LeakyBucketThrottler( + request_pool_size, requests_per_second + ) @staticmethod def standardize_webhook(webhook): diff --git a/tests/test_server_responses.py b/tests/test_server_responses.py index 285c983..331613a 100644 --- a/tests/test_server_responses.py +++ b/tests/test_server_responses.py @@ -17,7 +17,7 @@ def __init__(self, response: Union[Dict, List[Dict]]): self.response = response if isinstance(response, list) else [response] self.element_no = -1 - super().__init__("https://google.com/path", False, None) + super().__init__("https://google.com/path", False, 50, 2, None) async def single_request(self, *args, **kwargs): self.element_no += 1 diff --git a/tests/test_srh.py b/tests/test_srh.py index 1c9da99..5a7b03a 100644 --- a/tests/test_srh.py +++ b/tests/test_srh.py @@ -18,7 +18,7 @@ async def mock_post(url, json): mock_session = AsyncMock() mock_session.post = mock_post - handler = ServerRequestHandler('https://google.com/webhook', True, mock_session) + handler = ServerRequestHandler('https://google.com/webhook', True, 50, 2, mock_session) # Call the method result = await handler.request_attempt('method', {'param': 'value'}) diff --git a/tests/test_throttle.py b/tests/test_throttle.py index 8d160fc..2802356 100644 --- a/tests/test_throttle.py +++ b/tests/test_throttle.py @@ -10,15 +10,14 @@ # Test the acquire method of the LeakyBucketThrottler class @pytest.mark.asyncio @pytest.mark.parametrize( - "pool_size, requests_per_second, sleep_time, test_id", + "pool_size, requests_per_second, requests_made, sleep_time, test_id", [ - (5, 1.0, 0, "acquire_happy_path_no_wait"), - (5, 1.0, 1, "acquire_happy_path_with_wait"), - (1, 0.1, 10, "acquire_edge_long_wait"), + (5, 1.0, 3, 0, "acquire_happy_no_wait"), + (5, 1.0, 8, 1, "acquire_happy_path_wait"), ], ) async def test_leaky_bucket( - pool_size, requests_per_second, sleep_time, test_id, monkeypatch + pool_size, requests_per_second, requests_made, sleep_time, test_id, monkeypatch ): # Set up mocks start_time = time.monotonic() @@ -37,16 +36,15 @@ async def fake_sleep(duration): # Arrange throttler = LeakyBucketThrottler(pool_size, requests_per_second) - for _ in range(pool_size): + for _ in range(requests_made): throttler.add_request_record() - await asyncio.sleep(sleep_time) # Act async with throttler.acquire() as _: pass # Assert - assert len(throttler._request_history) <= pool_size + assert sum(sleep_log) == sleep_time @pytest.mark.parametrize(