apikeyrotator


Nameapikeyrotator JSON
Version 0.0.3 PyPI version JSON
download
home_pageNone
SummaryUltra simple API key rotation for bypassing rate limits
upload_time2025-09-19 11:24:39
maintainerNone
docs_urlNone
authorNone
requires_pythonNone
licenseMIT License Copyright (c) 2024 Prime Evolution Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
keywords api rotation rate limit requests
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # APIKeyRotator

**Ultra simple API key rotation for bypassing rate limits**

`APIKeyRotator` - это Python библиотека, разработанная для упрощения ротации API ключей, автоматической обработки лимитов запросов, ошибок и повторных попыток. Она предоставляет как синхронный, так и асинхронный интерфейс, сохраняя при этом максимальную простоту использования.

## Особенности

*   **Простота использования:** Интуитивно понятный API, похожий на `requests` и `aiohttp`.
*   **Автоматическая ротация ключей:** Переключается на следующий ключ при возникновении ошибок или превышении лимитов.
*   **Экспоненциальная задержка:** Автоматически применяет экспоненциальную задержку при повторных попытках.
*   **Гибкая конфигурация:** Настраиваемые максимальное количество повторных попыток, базовая задержка и таймауты.
*   **Поддержка синхронных и асинхронных запросов:** Используйте `APIKeyRotator` для синхронных операций и `AsyncAPIKeyRotator` для асинхронных.
*   **Автоматическое определение заголовков:** Пытается определить тип авторизации (Bearer, X-API-Key, Key) на основе формата ключа.
*   **Кастомизируемая логика:** Возможность предоставить собственные функции для определения необходимости повторной попытки и формирования заголовков.
*   **Умный парсинг ключей:** Ключи могут быть переданы списком, строкой через запятую или из переменной окружения.

## Установка

```bash
pip install apikeyrotator
```

## Использование

### Синхронный режим (APIKeyRotator)

Используйте `APIKeyRotator` для выполнения синхронных HTTP-запросов. Его API очень похож на библиотеку `requests`.

```python
import os
import requests
from apikeyrotator import APIKeyRotator, AllKeysExhaustedError

# Пример: ключи из переменной окружения (рекомендуется)
# export API_KEYS="your_key_1,your_key_2,your_key_3"

# Или передайте ключи напрямую
rotator = APIKeyRotator(
    api_keys=["key_sync_1", "key_sync_2", "key_sync_3"],
    max_retries=5, # Максимальное количество попыток для каждого ключа
    base_delay=0.5 # Базовая задержка между попытками
)

try:
    # Выполняем GET запрос
    response = rotator.get("https://api.example.com/data", params={"query": "test"})
    response.raise_for_status() # Вызовет исключение для 4xx/5xx ответов
    print(f"Успешный синхронный GET запрос: {response.status_code}")
    print(response.json())

    # Выполняем POST запрос
    response = rotator.post("https://api.example.com/submit", json={"data": "payload"})
    response.raise_for_status()
    print(f"Успешный синхронный POST запрос: {response.status_code}")
    print(response.json())

except AllKeysExhaustedError as e:
    print(f"Все ключи исчерпаны: {e}")
except Exception as e:
    print(f"Произошла ошибка: {e}")

# Пример с кастомной логикой определения повторных попыток
def custom_sync_retry_logic(response: requests.Response) -> bool:
    # Повторять, если статус 429 (Too Many Requests) или 403 (Forbidden)
    return response.status_code in [429, 403]

rotator_custom = APIKeyRotator(
    api_keys=["key_sync_custom_1"],
    should_retry_callback=custom_sync_retry_logic
)

try:
    response = rotator_custom.get("https://api.example.com/protected")
    print(f"Успешный синхронный запрос с кастомной логикой: {response.status_code}")
except AllKeysExhaustedError as e:
    print(f"Все ключи исчерпаны (кастомная логика): {e}")
```

### Асинхронный режим (AsyncAPIKeyRotator)

Используйте `AsyncAPIKeyRotator` для выполнения асинхронных HTTP-запросов. Его API очень похож на библиотеку `aiohttp`.

```python
import asyncio
import aiohttp
from apikeyrotator import AsyncAPIKeyRotator, AllKeysExhaustedError

async def main():
    # Пример: ключи из переменной окружения (рекомендуется)
    # export API_KEYS="your_async_key_1,your_async_key_2"

    # Или передайте ключи напрямую
    async with AsyncAPIKeyRotator(
        api_keys=["key_async_1", "key_async_2"],
        max_retries=5,
        base_delay=0.5
    ) as rotator:
        try:
            # Выполняем GET запрос
            async with rotator.get("https://api.example.com/async_data", params={"query": "async_test"}) as response:
                response.raise_for_status()
                data = await response.json()
                print(f"Успешный асинхронный GET запрос: {response.status}")
                print(data)

            # Выполняем POST запрос
            async with rotator.post("https://api.example.com/async_submit", json={"data": "async_payload"}) as response:
                response.raise_for_status()
                data = await response.json()
                print(f"Успешный асинхронный POST запрос: {response.status}")
                print(data)

        except AllKeysExhaustedError as e:
            print(f"Все ключи исчерпаны (асинхронно): {e}")
        except aiohttp.ClientError as e:
            print(f"Произошла асинхронная ошибка клиента: {e}")
        except Exception as e:
            print(f"Произошла непредвиденная ошибка: {e}")

# Пример с кастомной логикой определения повторных попыток и формирования заголовков
def custom_async_retry_logic(status_code: int) -> bool:
    # Повторять, если статус 429 (Too Many Requests) или 503 (Service Unavailable)
    return status_code in [429, 503]

def custom_header_callback(key: str, existing_headers: Optional[dict]) -> dict:
    headers = existing_headers.copy() if existing_headers else {}
    headers["X-Custom-Auth"] = f"Token {key}"
    headers["User-Agent"] = "MyAwesomeApp/1.0"
    return headers

async def main_custom_async():
    async with AsyncAPIKeyRotator(
        api_keys=["key_async_custom_1"],
        should_retry_callback=custom_async_retry_logic,
        header_callback=custom_header_callback
    ) as rotator:
        try:
            async with rotator.get("https://api.example.com/custom_auth") as response:
                response.raise_for_status()
                data = await response.json()
                print(f"Успешный асинхронный запрос с кастомной логикой: {response.status}")
                print(data)
        except AllKeysExhaustedError as e:
            print(f"Все ключи исчерпаны (кастомная асинхронная логика): {e}")
        except Exception as e:
            print(f"Произошла ошибка: {e}")

if __name__ == "__main__":
    asyncio.run(main())
    # asyncio.run(main_custom_async()) # Раскомментируйте для запуска примера с кастомной логикой
```

## Обработка ошибок

Библиотека выбрасывает следующие исключения:

*   `NoAPIKeysError`: Если ключи API не были предоставлены или не найдены.
*   `AllKeysExhaustedError`: Если все предоставленные ключи API были исчерпаны после всех попыток.

## Разработка

Для запуска тестов или разработки:

```bash
git clone https://github.com/PrimeevolutionZ/apikeyrotator.git
cd apikeyrotator
pip install -e .
# Запустите тесты, если они есть
```

## Лицензия

Эта библиотека распространяется под лицензией MIT. См. файл `LICENSE` для получения дополнительной информации.


            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "apikeyrotator",
    "maintainer": null,
    "docs_url": null,
    "requires_python": null,
    "maintainer_email": null,
    "keywords": "api, rotation, rate limit, requests",
    "author": null,
    "author_email": "Prime Evolution <develop@eclips-team.ru>",
    "download_url": "https://files.pythonhosted.org/packages/b1/37/f3c46aee7e126080982df14202a36e0b14f05af920f6c95a70cfbe342dfd/apikeyrotator-0.0.3.tar.gz",
    "platform": null,
    "description": "# APIKeyRotator\r\n\r\n**Ultra simple API key rotation for bypassing rate limits**\r\n\r\n`APIKeyRotator` - \u044d\u0442\u043e Python \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430, \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0430\u043d\u043d\u0430\u044f \u0434\u043b\u044f \u0443\u043f\u0440\u043e\u0449\u0435\u043d\u0438\u044f \u0440\u043e\u0442\u0430\u0446\u0438\u0438 API \u043a\u043b\u044e\u0447\u0435\u0439, \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0439 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u043b\u0438\u043c\u0438\u0442\u043e\u0432 \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432, \u043e\u0448\u0438\u0431\u043e\u043a \u0438 \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u044b\u0445 \u043f\u043e\u043f\u044b\u0442\u043e\u043a. \u041e\u043d\u0430 \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u043a\u0430\u043a \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u0439, \u0442\u0430\u043a \u0438 \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u0439 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441, \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u044f \u043f\u0440\u0438 \u044d\u0442\u043e\u043c \u043c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u0443\u044e \u043f\u0440\u043e\u0441\u0442\u043e\u0442\u0443 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f.\r\n\r\n## \u041e\u0441\u043e\u0431\u0435\u043d\u043d\u043e\u0441\u0442\u0438\r\n\r\n*   **\u041f\u0440\u043e\u0441\u0442\u043e\u0442\u0430 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f:** \u0418\u043d\u0442\u0443\u0438\u0442\u0438\u0432\u043d\u043e \u043f\u043e\u043d\u044f\u0442\u043d\u044b\u0439 API, \u043f\u043e\u0445\u043e\u0436\u0438\u0439 \u043d\u0430 `requests` \u0438 `aiohttp`.\r\n*   **\u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0430\u044f \u0440\u043e\u0442\u0430\u0446\u0438\u044f \u043a\u043b\u044e\u0447\u0435\u0439:** \u041f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f \u043d\u0430 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439 \u043a\u043b\u044e\u0447 \u043f\u0440\u0438 \u0432\u043e\u0437\u043d\u0438\u043a\u043d\u043e\u0432\u0435\u043d\u0438\u0438 \u043e\u0448\u0438\u0431\u043e\u043a \u0438\u043b\u0438 \u043f\u0440\u0435\u0432\u044b\u0448\u0435\u043d\u0438\u0438 \u043b\u0438\u043c\u0438\u0442\u043e\u0432.\r\n*   **\u042d\u043a\u0441\u043f\u043e\u043d\u0435\u043d\u0446\u0438\u0430\u043b\u044c\u043d\u0430\u044f \u0437\u0430\u0434\u0435\u0440\u0436\u043a\u0430:** \u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u043f\u0440\u0438\u043c\u0435\u043d\u044f\u0435\u0442 \u044d\u043a\u0441\u043f\u043e\u043d\u0435\u043d\u0446\u0438\u0430\u043b\u044c\u043d\u0443\u044e \u0437\u0430\u0434\u0435\u0440\u0436\u043a\u0443 \u043f\u0440\u0438 \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u044b\u0445 \u043f\u043e\u043f\u044b\u0442\u043a\u0430\u0445.\r\n*   **\u0413\u0438\u0431\u043a\u0430\u044f \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f:** \u041d\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0435\u043c\u044b\u0435 \u043c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u043e\u0435 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u044b\u0445 \u043f\u043e\u043f\u044b\u0442\u043e\u043a, \u0431\u0430\u0437\u043e\u0432\u0430\u044f \u0437\u0430\u0434\u0435\u0440\u0436\u043a\u0430 \u0438 \u0442\u0430\u0439\u043c\u0430\u0443\u0442\u044b.\r\n*   **\u041f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0430 \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u0445 \u0438 \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u0445 \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432:** \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 `APIKeyRotator` \u0434\u043b\u044f \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u0445 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0439 \u0438 `AsyncAPIKeyRotator` \u0434\u043b\u044f \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u0445.\r\n*   **\u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0435 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u043e\u0432:** \u041f\u044b\u0442\u0430\u0435\u0442\u0441\u044f \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c \u0442\u0438\u043f \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 (Bearer, X-API-Key, Key) \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435 \u0444\u043e\u0440\u043c\u0430\u0442\u0430 \u043a\u043b\u044e\u0447\u0430.\r\n*   **\u041a\u0430\u0441\u0442\u043e\u043c\u0438\u0437\u0438\u0440\u0443\u0435\u043c\u0430\u044f \u043b\u043e\u0433\u0438\u043a\u0430:** \u0412\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u0434\u043b\u044f \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u044f \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0441\u0442\u0438 \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u043e\u0439 \u043f\u043e\u043f\u044b\u0442\u043a\u0438 \u0438 \u0444\u043e\u0440\u043c\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u043e\u0432.\r\n*   **\u0423\u043c\u043d\u044b\u0439 \u043f\u0430\u0440\u0441\u0438\u043d\u0433 \u043a\u043b\u044e\u0447\u0435\u0439:** \u041a\u043b\u044e\u0447\u0438 \u043c\u043e\u0433\u0443\u0442 \u0431\u044b\u0442\u044c \u043f\u0435\u0440\u0435\u0434\u0430\u043d\u044b \u0441\u043f\u0438\u0441\u043a\u043e\u043c, \u0441\u0442\u0440\u043e\u043a\u043e\u0439 \u0447\u0435\u0440\u0435\u0437 \u0437\u0430\u043f\u044f\u0442\u0443\u044e \u0438\u043b\u0438 \u0438\u0437 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u043e\u0439 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f.\r\n\r\n## \u0423\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430\r\n\r\n```bash\r\npip install apikeyrotator\r\n```\r\n\r\n## \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\r\n\r\n### \u0421\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u0439 \u0440\u0435\u0436\u0438\u043c (APIKeyRotator)\r\n\r\n\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 `APIKeyRotator` \u0434\u043b\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u0445 HTTP-\u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432. \u0415\u0433\u043e API \u043e\u0447\u0435\u043d\u044c \u043f\u043e\u0445\u043e\u0436 \u043d\u0430 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0443 `requests`.\r\n\r\n```python\r\nimport os\r\nimport requests\r\nfrom apikeyrotator import APIKeyRotator, AllKeysExhaustedError\r\n\r\n# \u041f\u0440\u0438\u043c\u0435\u0440: \u043a\u043b\u044e\u0447\u0438 \u0438\u0437 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u043e\u0439 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f (\u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u0435\u0442\u0441\u044f)\r\n# export API_KEYS=\"your_key_1,your_key_2,your_key_3\"\r\n\r\n# \u0418\u043b\u0438 \u043f\u0435\u0440\u0435\u0434\u0430\u0439\u0442\u0435 \u043a\u043b\u044e\u0447\u0438 \u043d\u0430\u043f\u0440\u044f\u043c\u0443\u044e\r\nrotator = APIKeyRotator(\r\n    api_keys=[\"key_sync_1\", \"key_sync_2\", \"key_sync_3\"],\r\n    max_retries=5, # \u041c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u043e\u0435 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u043f\u043e\u043f\u044b\u0442\u043e\u043a \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043a\u043b\u044e\u0447\u0430\r\n    base_delay=0.5 # \u0411\u0430\u0437\u043e\u0432\u0430\u044f \u0437\u0430\u0434\u0435\u0440\u0436\u043a\u0430 \u043c\u0435\u0436\u0434\u0443 \u043f\u043e\u043f\u044b\u0442\u043a\u0430\u043c\u0438\r\n)\r\n\r\ntry:\r\n    # \u0412\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u043c GET \u0437\u0430\u043f\u0440\u043e\u0441\r\n    response = rotator.get(\"https://api.example.com/data\", params={\"query\": \"test\"})\r\n    response.raise_for_status() # \u0412\u044b\u0437\u043e\u0432\u0435\u0442 \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u0434\u043b\u044f 4xx/5xx \u043e\u0442\u0432\u0435\u0442\u043e\u0432\r\n    print(f\"\u0423\u0441\u043f\u0435\u0448\u043d\u044b\u0439 \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u0439 GET \u0437\u0430\u043f\u0440\u043e\u0441: {response.status_code}\")\r\n    print(response.json())\r\n\r\n    # \u0412\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u043c POST \u0437\u0430\u043f\u0440\u043e\u0441\r\n    response = rotator.post(\"https://api.example.com/submit\", json={\"data\": \"payload\"})\r\n    response.raise_for_status()\r\n    print(f\"\u0423\u0441\u043f\u0435\u0448\u043d\u044b\u0439 \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u0439 POST \u0437\u0430\u043f\u0440\u043e\u0441: {response.status_code}\")\r\n    print(response.json())\r\n\r\nexcept AllKeysExhaustedError as e:\r\n    print(f\"\u0412\u0441\u0435 \u043a\u043b\u044e\u0447\u0438 \u0438\u0441\u0447\u0435\u0440\u043f\u0430\u043d\u044b: {e}\")\r\nexcept Exception as e:\r\n    print(f\"\u041f\u0440\u043e\u0438\u0437\u043e\u0448\u043b\u0430 \u043e\u0448\u0438\u0431\u043a\u0430: {e}\")\r\n\r\n# \u041f\u0440\u0438\u043c\u0435\u0440 \u0441 \u043a\u0430\u0441\u0442\u043e\u043c\u043d\u043e\u0439 \u043b\u043e\u0433\u0438\u043a\u043e\u0439 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u044f \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u044b\u0445 \u043f\u043e\u043f\u044b\u0442\u043e\u043a\r\ndef custom_sync_retry_logic(response: requests.Response) -> bool:\r\n    # \u041f\u043e\u0432\u0442\u043e\u0440\u044f\u0442\u044c, \u0435\u0441\u043b\u0438 \u0441\u0442\u0430\u0442\u0443\u0441 429 (Too Many Requests) \u0438\u043b\u0438 403 (Forbidden)\r\n    return response.status_code in [429, 403]\r\n\r\nrotator_custom = APIKeyRotator(\r\n    api_keys=[\"key_sync_custom_1\"],\r\n    should_retry_callback=custom_sync_retry_logic\r\n)\r\n\r\ntry:\r\n    response = rotator_custom.get(\"https://api.example.com/protected\")\r\n    print(f\"\u0423\u0441\u043f\u0435\u0448\u043d\u044b\u0439 \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u0439 \u0437\u0430\u043f\u0440\u043e\u0441 \u0441 \u043a\u0430\u0441\u0442\u043e\u043c\u043d\u043e\u0439 \u043b\u043e\u0433\u0438\u043a\u043e\u0439: {response.status_code}\")\r\nexcept AllKeysExhaustedError as e:\r\n    print(f\"\u0412\u0441\u0435 \u043a\u043b\u044e\u0447\u0438 \u0438\u0441\u0447\u0435\u0440\u043f\u0430\u043d\u044b (\u043a\u0430\u0441\u0442\u043e\u043c\u043d\u0430\u044f \u043b\u043e\u0433\u0438\u043a\u0430): {e}\")\r\n```\r\n\r\n### \u0410\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u0439 \u0440\u0435\u0436\u0438\u043c (AsyncAPIKeyRotator)\r\n\r\n\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 `AsyncAPIKeyRotator` \u0434\u043b\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u0445 HTTP-\u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432. \u0415\u0433\u043e API \u043e\u0447\u0435\u043d\u044c \u043f\u043e\u0445\u043e\u0436 \u043d\u0430 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0443 `aiohttp`.\r\n\r\n```python\r\nimport asyncio\r\nimport aiohttp\r\nfrom apikeyrotator import AsyncAPIKeyRotator, AllKeysExhaustedError\r\n\r\nasync def main():\r\n    # \u041f\u0440\u0438\u043c\u0435\u0440: \u043a\u043b\u044e\u0447\u0438 \u0438\u0437 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u043e\u0439 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f (\u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u0435\u0442\u0441\u044f)\r\n    # export API_KEYS=\"your_async_key_1,your_async_key_2\"\r\n\r\n    # \u0418\u043b\u0438 \u043f\u0435\u0440\u0435\u0434\u0430\u0439\u0442\u0435 \u043a\u043b\u044e\u0447\u0438 \u043d\u0430\u043f\u0440\u044f\u043c\u0443\u044e\r\n    async with AsyncAPIKeyRotator(\r\n        api_keys=[\"key_async_1\", \"key_async_2\"],\r\n        max_retries=5,\r\n        base_delay=0.5\r\n    ) as rotator:\r\n        try:\r\n            # \u0412\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u043c GET \u0437\u0430\u043f\u0440\u043e\u0441\r\n            async with rotator.get(\"https://api.example.com/async_data\", params={\"query\": \"async_test\"}) as response:\r\n                response.raise_for_status()\r\n                data = await response.json()\r\n                print(f\"\u0423\u0441\u043f\u0435\u0448\u043d\u044b\u0439 \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u0439 GET \u0437\u0430\u043f\u0440\u043e\u0441: {response.status}\")\r\n                print(data)\r\n\r\n            # \u0412\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u043c POST \u0437\u0430\u043f\u0440\u043e\u0441\r\n            async with rotator.post(\"https://api.example.com/async_submit\", json={\"data\": \"async_payload\"}) as response:\r\n                response.raise_for_status()\r\n                data = await response.json()\r\n                print(f\"\u0423\u0441\u043f\u0435\u0448\u043d\u044b\u0439 \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u0439 POST \u0437\u0430\u043f\u0440\u043e\u0441: {response.status}\")\r\n                print(data)\r\n\r\n        except AllKeysExhaustedError as e:\r\n            print(f\"\u0412\u0441\u0435 \u043a\u043b\u044e\u0447\u0438 \u0438\u0441\u0447\u0435\u0440\u043f\u0430\u043d\u044b (\u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u043e): {e}\")\r\n        except aiohttp.ClientError as e:\r\n            print(f\"\u041f\u0440\u043e\u0438\u0437\u043e\u0448\u043b\u0430 \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430 \u043a\u043b\u0438\u0435\u043d\u0442\u0430: {e}\")\r\n        except Exception as e:\r\n            print(f\"\u041f\u0440\u043e\u0438\u0437\u043e\u0448\u043b\u0430 \u043d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430: {e}\")\r\n\r\n# \u041f\u0440\u0438\u043c\u0435\u0440 \u0441 \u043a\u0430\u0441\u0442\u043e\u043c\u043d\u043e\u0439 \u043b\u043e\u0433\u0438\u043a\u043e\u0439 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u044f \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u044b\u0445 \u043f\u043e\u043f\u044b\u0442\u043e\u043a \u0438 \u0444\u043e\u0440\u043c\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u043e\u0432\r\ndef custom_async_retry_logic(status_code: int) -> bool:\r\n    # \u041f\u043e\u0432\u0442\u043e\u0440\u044f\u0442\u044c, \u0435\u0441\u043b\u0438 \u0441\u0442\u0430\u0442\u0443\u0441 429 (Too Many Requests) \u0438\u043b\u0438 503 (Service Unavailable)\r\n    return status_code in [429, 503]\r\n\r\ndef custom_header_callback(key: str, existing_headers: Optional[dict]) -> dict:\r\n    headers = existing_headers.copy() if existing_headers else {}\r\n    headers[\"X-Custom-Auth\"] = f\"Token {key}\"\r\n    headers[\"User-Agent\"] = \"MyAwesomeApp/1.0\"\r\n    return headers\r\n\r\nasync def main_custom_async():\r\n    async with AsyncAPIKeyRotator(\r\n        api_keys=[\"key_async_custom_1\"],\r\n        should_retry_callback=custom_async_retry_logic,\r\n        header_callback=custom_header_callback\r\n    ) as rotator:\r\n        try:\r\n            async with rotator.get(\"https://api.example.com/custom_auth\") as response:\r\n                response.raise_for_status()\r\n                data = await response.json()\r\n                print(f\"\u0423\u0441\u043f\u0435\u0448\u043d\u044b\u0439 \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u0439 \u0437\u0430\u043f\u0440\u043e\u0441 \u0441 \u043a\u0430\u0441\u0442\u043e\u043c\u043d\u043e\u0439 \u043b\u043e\u0433\u0438\u043a\u043e\u0439: {response.status}\")\r\n                print(data)\r\n        except AllKeysExhaustedError as e:\r\n            print(f\"\u0412\u0441\u0435 \u043a\u043b\u044e\u0447\u0438 \u0438\u0441\u0447\u0435\u0440\u043f\u0430\u043d\u044b (\u043a\u0430\u0441\u0442\u043e\u043c\u043d\u0430\u044f \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u0430\u044f \u043b\u043e\u0433\u0438\u043a\u0430): {e}\")\r\n        except Exception as e:\r\n            print(f\"\u041f\u0440\u043e\u0438\u0437\u043e\u0448\u043b\u0430 \u043e\u0448\u0438\u0431\u043a\u0430: {e}\")\r\n\r\nif __name__ == \"__main__\":\r\n    asyncio.run(main())\r\n    # asyncio.run(main_custom_async()) # \u0420\u0430\u0441\u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0438\u0440\u0443\u0439\u0442\u0435 \u0434\u043b\u044f \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u043f\u0440\u0438\u043c\u0435\u0440\u0430 \u0441 \u043a\u0430\u0441\u0442\u043e\u043c\u043d\u043e\u0439 \u043b\u043e\u0433\u0438\u043a\u043e\u0439\r\n```\r\n\r\n## \u041e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u043e\u0448\u0438\u0431\u043e\u043a\r\n\r\n\u0411\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430 \u0432\u044b\u0431\u0440\u0430\u0441\u044b\u0432\u0430\u0435\u0442 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0435 \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f:\r\n\r\n*   `NoAPIKeysError`: \u0415\u0441\u043b\u0438 \u043a\u043b\u044e\u0447\u0438 API \u043d\u0435 \u0431\u044b\u043b\u0438 \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u044b \u0438\u043b\u0438 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u044b.\r\n*   `AllKeysExhaustedError`: \u0415\u0441\u043b\u0438 \u0432\u0441\u0435 \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u043d\u044b\u0435 \u043a\u043b\u044e\u0447\u0438 API \u0431\u044b\u043b\u0438 \u0438\u0441\u0447\u0435\u0440\u043f\u0430\u043d\u044b \u043f\u043e\u0441\u043b\u0435 \u0432\u0441\u0435\u0445 \u043f\u043e\u043f\u044b\u0442\u043e\u043a.\r\n\r\n## \u0420\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0430\r\n\r\n\u0414\u043b\u044f \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u0442\u0435\u0441\u0442\u043e\u0432 \u0438\u043b\u0438 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438:\r\n\r\n```bash\r\ngit clone https://github.com/PrimeevolutionZ/apikeyrotator.git\r\ncd apikeyrotator\r\npip install -e .\r\n# \u0417\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u0435 \u0442\u0435\u0441\u0442\u044b, \u0435\u0441\u043b\u0438 \u043e\u043d\u0438 \u0435\u0441\u0442\u044c\r\n```\r\n\r\n## \u041b\u0438\u0446\u0435\u043d\u0437\u0438\u044f\r\n\r\n\u042d\u0442\u0430 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430 \u0440\u0430\u0441\u043f\u0440\u043e\u0441\u0442\u0440\u0430\u043d\u044f\u0435\u0442\u0441\u044f \u043f\u043e\u0434 \u043b\u0438\u0446\u0435\u043d\u0437\u0438\u0435\u0439 MIT. \u0421\u043c. \u0444\u0430\u0439\u043b `LICENSE` \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0439 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438.\r\n\r\n",
    "bugtrack_url": null,
    "license": "MIT License\r\n        \r\n        Copyright (c) 2024 Prime Evolution\r\n        \r\n        Permission is hereby granted, free of charge, to any person obtaining a copy\r\n        of this software and associated documentation files (the \"Software\"), to deal\r\n        in the Software without restriction, including without limitation the rights\r\n        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n        copies of the Software, and to permit persons to whom the Software is\r\n        furnished to do so, subject to the following conditions:\r\n        \r\n        The above copyright notice and this permission notice shall be included in all\r\n        copies or substantial portions of the Software.\r\n        \r\n        THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\n        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\n        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r\n        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\n        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\r\n        SOFTWARE.",
    "summary": "Ultra simple API key rotation for bypassing rate limits",
    "version": "0.0.3",
    "project_urls": {
        "Homepage": "https://github.com/PrimeevolutionZ/apikeyrotator",
        "Issues": "https://github.com/PrimeevolutionZ/apikeyrotator/issues",
        "Repository": "https://github.com/PrimeevolutionZ/apikeyrotator"
    },
    "split_keywords": [
        "api",
        " rotation",
        " rate limit",
        " requests"
    ],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "6ebcd918eee208bc016787dd741a628dff02dcb3ad7df163138eeb4cd10c090a",
                "md5": "25d6bcc976e9d417bb06c0e38c81feb5",
                "sha256": "d514e0c0a53a6e3f86365dd280100bf19226583d812c82b23df657445a131c6e"
            },
            "downloads": -1,
            "filename": "apikeyrotator-0.0.3-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "25d6bcc976e9d417bb06c0e38c81feb5",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": null,
            "size": 10174,
            "upload_time": "2025-09-19T11:24:38",
            "upload_time_iso_8601": "2025-09-19T11:24:38.234794Z",
            "url": "https://files.pythonhosted.org/packages/6e/bc/d918eee208bc016787dd741a628dff02dcb3ad7df163138eeb4cd10c090a/apikeyrotator-0.0.3-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "b137f3c46aee7e126080982df14202a36e0b14f05af920f6c95a70cfbe342dfd",
                "md5": "bb9a29b4b19c3f7b3a31ab1c30e4d1fe",
                "sha256": "b9862977349047925e95a5e7c9219e2753016fabc330d38aecb5fc0fba056f6d"
            },
            "downloads": -1,
            "filename": "apikeyrotator-0.0.3.tar.gz",
            "has_sig": false,
            "md5_digest": "bb9a29b4b19c3f7b3a31ab1c30e4d1fe",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": null,
            "size": 11405,
            "upload_time": "2025-09-19T11:24:39",
            "upload_time_iso_8601": "2025-09-19T11:24:39.622062Z",
            "url": "https://files.pythonhosted.org/packages/b1/37/f3c46aee7e126080982df14202a36e0b14f05af920f6c95a70cfbe342dfd/apikeyrotator-0.0.3.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-09-19 11:24:39",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "PrimeevolutionZ",
    "github_project": "apikeyrotator",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": false,
    "lcname": "apikeyrotator"
}
        
Elapsed time: 2.25708s