Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Настройка размера батчей и внутреннего предела operating_time #246

Merged
merged 2 commits into from
Jul 23, 2024
Merged
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
7 changes: 6 additions & 1 deletion API.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

Внутри объекта ведётся учёт скорости отправки запросов к серверу, поэтому важно, чтобы все запросы приложения в отношении одного аккаунта с одного IP-адреса отправлялись из одного экземпляра `Bitrix`.

### Метод ` __init__(self, webhook: str, token_func: Awaitable = None, verbose: bool = True, respect_velocity_policy: bool = True, request_pool_size: int = 50, requests_per_second: float = 2.0, ssl: bool = True, client: aiohttp.ClientSession = None):`
### Метод ` __init__(self, webhook: str, token_func: Awaitable = None, verbose: bool = True, respect_velocity_policy: bool = True, request_pool_size: int = 50, requests_per_second: float = 2.0, batch_size: int = 50, operating_time_limit: int = 480, ssl: bool = True, client: aiohttp.ClientSession = None):`
Создаёт клиента для доступа к Битрикс24.

#### Параметры
Expand All @@ -16,6 +16,11 @@
можно отправить на сервер без ожидания
- `requests_per_second: float = 2.0` - максимальная скорость запросов,
которая будет использоваться после переполнения пула
- `batch_size: int = 50` - максимальное количество запросов, которые
будут отправляться на сервер в одном батче
- `operating_time_limit: int = 480` - максимальное допустимое время отработки
запросов к одному методу REST API в секундах, допустимое за 10 минут,
leshchenko1979 marked this conversation as resolved.
Show resolved Hide resolved
после которого запросы будут замедляться
- `ssl: bool = True` - использовать ли проверку SSL-сертификата при HTTP-соединениях с сервером Битрикс.
- `client: aiohttp.ClientSession = None` - использовать для HTTP-вызовов клиента, инициализированного и настроенного пользователем.

Expand Down
18 changes: 16 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -239,8 +239,9 @@ import logging
logging.getLogger('fast_bitrix24').addHandler(logging.StreamHandler())
```

### Я хочу добавить несколько лидов списком, но получаю ошибку сервера.
Оберните вызов `call()` в `slow`:
### Я получаю ошибку сервера, когда сильно его нагружаю. Что делать?
leshchenko1979 marked this conversation as resolved.
Show resolved Hide resolved
#### Способ 1
Оберните обращения к Битриксу в `slow`:

```python
with bx.slow():
Expand All @@ -249,6 +250,19 @@ with bx.slow():

[См. подробнее](API.md#контекстный-менеджер-slowmaxconcurrentrequests-int--1) о `slow`.

#### Способ 2
При инициализации клиента поэкспериментируйте со значениями параметров, регулирующих нагрузку на сервер:

```python
bx = Bitrix(
webhook,
request_pool_size = 20, # по умолчанию - 50
requests_per_second = 1.0, # по умолчанию - 1.0
batch_size = 20, # по умолчанию - 50
operating_time_limit = 100, # по умолчанию - 480
)
```

### Я хочу вызвать `call()` только один раз, а не по списку.
Передавайте параметры запроса методу `call()`, он может делать как запросы по списку, так и единичный запрос:

Expand Down
9 changes: 9 additions & 0 deletions fast_bitrix24/bitrix.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ def __init__(
respect_velocity_policy: bool = True,
request_pool_size: int = 50,
requests_per_second: float = 2.0,
batch_size: int = 50,
leshchenko1979 marked this conversation as resolved.
Show resolved Hide resolved
operating_time_limit: int = 480,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (bug_risk): Consider validating operating_time_limit.

Validating operating_time_limit to ensure it is within a reasonable range can prevent potential issues with request throttling.

Suggested change
operating_time_limit: int = 480,
operating_time_limit: int = 480,
if not (0 < operating_time_limit <= 1440):
raise ValueError("operating_time_limit must be between 1 and 1440 minutes")

client: aiohttp.ClientSession = None,
ssl: bool = True,
):
Expand All @@ -53,6 +55,11 @@ def __init__(
можно отправить на сервер без ожидания
- `requests_per_second: float = 2.0` - максимальная скорость запросов,
которая будет использоваться после переполнения пула
- `batch_size: int = 50` - максимальное количество запросов, которые
будут отправляться на сервер в одном батче
- `operating_time_limit: int = 480` - максимальное допустимое время отработки
запросов к одному методу REST API в секундах, допустимое за 10 минут,
после которого запросы будут замедляться
- `ssl: bool = True` - использовать ли проверку SSL-сертификата
при HTTP-соединениях с сервером Битрикс.
- `client: aiohttp.ClientSession = None` - использовать для HTTP-вызовов
Expand All @@ -69,10 +76,12 @@ def __init__(
respect_velocity_policy=respect_velocity_policy,
request_pool_size=request_pool_size,
requests_per_second=requests_per_second,
operating_time_limit=operating_time_limit,
ssl=ssl,
client=client,
)
self.verbose = verbose
self.batch_size = batch_size

@log
async def get_all(self, method: str, params: dict = None) -> Union[list, dict]:
Expand Down
4 changes: 2 additions & 2 deletions fast_bitrix24/mult_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from tqdm.auto import tqdm

from .server_response import ServerResponseParser
from .srh import BITRIX_MAX_BATCH_SIZE, ServerRequestHandler
from .srh import ServerRequestHandler
from .utils import http_build_query


Expand Down Expand Up @@ -38,7 +38,7 @@ def generate_tasks(self):

batches = (
self.package_batch(chunk)
for chunk in chunked(self.item_list, BITRIX_MAX_BATCH_SIZE)
for chunk in chunked(self.item_list, self.bitrix.batch_size)
)

for batch in batches:
Expand Down
10 changes: 4 additions & 6 deletions fast_bitrix24/srh.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,8 @@
from .logger import logger
from .utils import _url_valid

BITRIX_MAX_BATCH_SIZE = 50
BITRIX_MAX_CONCURRENT_REQUESTS = 50

BITRIX_MAX_REQUEST_RUNNING_TIME = 480
BITRIX_MEASUREMENT_PERIOD = 10 * 60

MAX_RETRIES = 10
Expand Down Expand Up @@ -50,9 +48,6 @@ class ServerRequestHandler:

Основная цель - вести учет количества запросов, которые можно передать
серверу Битрикс без получения ошибки `5XX`.

Используется как контекстный менеджер, оборачивающий несколько
последовательных запросов к серверу.
"""

def __init__(
Expand All @@ -62,6 +57,7 @@ def __init__(
respect_velocity_policy: bool,
request_pool_size: int,
requests_per_second: float,
operating_time_limit: int,
leshchenko1979 marked this conversation as resolved.
Show resolved Hide resolved
client,
ssl: bool = True,
):
Expand All @@ -78,6 +74,8 @@ def __init__(

self.active_runs = 0

self.operating_time_limit = operating_time_limit

# если пользователь при инициализации передал клиента со своими настройками,
# то будем использовать его клиента
self.client_provided_by_user = bool(client)
Expand Down Expand Up @@ -249,7 +247,7 @@ async def acquire(self, method: str):
if self.respect_velocity_policy:
if method not in self.method_throttlers:
self.method_throttlers[method] = SlidingWindowThrottler(
BITRIX_MAX_REQUEST_RUNNING_TIME, BITRIX_MEASUREMENT_PERIOD
self.operating_time_limit, BITRIX_MEASUREMENT_PERIOD
)

async with self.method_throttlers[method].acquire():
Expand Down
7 changes: 6 additions & 1 deletion tests/test_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@

from typing import Dict, List, Union


class MockSRH(ServerRequestHandler):
def __init__(self, token_func, response: Union[Dict, List[Dict]]):
self.response = response if isinstance(response, list) else [response]
self.element_no = 0

super().__init__("https://google.com/path", token_func, False, 50, 2, None)
super().__init__("https://google.com/path", token_func, False, 50, 2, 480, None)

async def request_attempt(self, *args, **kwargs):
result = self.response[self.element_no]
Expand All @@ -28,25 +29,29 @@ def test_first_request():

raise AssertionError


@pytest.mark.skip(reason="TODO")
def test_auth_success():
# нужно проверить, что серверу передается токен, полученный от token_func

raise AssertionError


@pytest.mark.skip(reason="TODO")
def test_auth_failure():
# нужно проверить, что вызывается функция запроса токена, если сервер вернул ошибку токена

raise AssertionError


@pytest.mark.skip(reason="TODO")
def test_abort_on_multiple_failures():
# нужно проверить, что если token_func регулярно возвращает токен, который отвергается сервером,
# то запрос оборвется после MAX_RETRIES неудачных попыток

raise AssertionError


def test_expired_token(bx_dummy):
# нужно проверить, что вызывается функция запроса токена, если токен истек

Expand Down
2 changes: 1 addition & 1 deletion tests/test_server_responses.py
Original file line number Diff line number Diff line change
Expand Up @@ -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", None, False, 50, 2, None)
super().__init__("https://google.com/path", None, False, 50, 2, 480, None)

async def single_request(self, *args, **kwargs):
self.element_no += 1
Expand Down
11 changes: 7 additions & 4 deletions tests/test_srh.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@
from fast_bitrix24.srh import ServerRequestHandler
import aiohttp


@pytest.mark.asyncio
async def test_request_attempt():
# Create a mock response
mock_response = Mock(spec=aiohttp.ClientResponse)
mock_response.status = 200
mock_response.json.return_value = {'time': {'operating': 1000}}
mock_response.json.return_value = {"time": {"operating": 1000}}

@contextlib.asynccontextmanager
async def mock_post(url, json, ssl):
Expand All @@ -18,11 +19,13 @@ async def mock_post(url, json, ssl):
mock_session = AsyncMock()
mock_session.post = mock_post

handler = ServerRequestHandler('https://google.com/webhook', None, True, 50, 2, mock_session)
handler = ServerRequestHandler(
"https://google.com/webhook", None, True, 50, 2, 480, mock_session
)

# Call the method
result = await handler.request_attempt('method', {'param': 'value'})
result = await handler.request_attempt("method", {"param": "value"})

# Assert the expected behavior
assert result == {'time': {'operating': 1000}}
assert result == {"time": {"operating": 1000}}
assert "method" in handler.method_throttlers
Loading