diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bc21ab9..9cff0e0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -27,7 +27,7 @@ jobs: python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - name: Set up uv uses: astral-sh/setup-uv@v7 with: diff --git a/.github/workflows/build_sphinx.yml b/.github/workflows/build_sphinx.yml index b108e12..dd38cf0 100644 --- a/.github/workflows/build_sphinx.yml +++ b/.github/workflows/build_sphinx.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - name: Set up uv uses: astral-sh/setup-uv@v7 with: diff --git a/.github/workflows/install.yml b/.github/workflows/install.yml index 2de3faa..8d89bb0 100644 --- a/.github/workflows/install.yml +++ b/.github/workflows/install.yml @@ -27,7 +27,7 @@ jobs: python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - name: Set up uv uses: astral-sh/setup-uv@v7 with: diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index a6e7183..80db437 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -29,7 +29,7 @@ jobs: python-version: ["3.12"] steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - name: Set up uv uses: astral-sh/setup-uv@v7 with: diff --git a/.github/workflows/sphinx.yml b/.github/workflows/sphinx.yml index 1b800ee..f6ef4c3 100644 --- a/.github/workflows/sphinx.yml +++ b/.github/workflows/sphinx.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - name: Set up uv uses: astral-sh/setup-uv@v7 with: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8411026..de47ad5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -32,7 +32,7 @@ jobs: python-version: ["3.12"] steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - name: Set up uv uses: astral-sh/setup-uv@v7 with: @@ -44,7 +44,7 @@ jobs: run: make tests - name: Upload coverage to Codecov - uses: codecov/codecov-action@v5 + uses: codecov/codecov-action@v7 with: token: ${{ secrets.CODECOV_TOKEN }} files: ${{github.workspace}}/coverage/coverage.xml diff --git a/.github/workflows/zai-code-bot.yml b/.github/workflows/zai-code-bot.yml index 2b7d256..e9dd0b3 100644 --- a/.github/workflows/zai-code-bot.yml +++ b/.github/workflows/zai-code-bot.yml @@ -35,7 +35,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Run Z.ai Bot uses: AndreiDrang/zai-code-bot@main diff --git a/.gitignore b/.gitignore index 66e8ffd..491bf47 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ instance/* !instance/.gitignore .webassets-cache .env +.pypi-token ### Flask.Python Stack ### # Byte-compiled / optimized / DLL files @@ -430,3 +431,4 @@ env/ /.coverage /src/python3_capsolver.egg-info/ /docs/_build/ +.osgrep diff --git a/AGENTS.md b/AGENTS.md index ab09713..f4d2995 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,72 +1,69 @@ -# PROJECT KNOWLEDGE BASE +# AGENTS.md -**Generated:** 2026-03-15 -**Commit:** b797332 -**Branch:** main +## Repository overview -## OVERVIEW -Python 3.8+ library for Capsolver service API. Dual sync (`requests`) / async (`aiohttp`) support. `msgspec` for serialization, `tenacity` for retries. +Python 3.8+ client library for the Capsolver captcha-solving API. Single-package `src/` layout, dual sync (`requests`) / async (`aiohttp`) execution, `msgspec` for serialization, `tenacity` for async retries. -## STRUCTURE -``` +## Where to work + +```text ./ -├── src/python3_capsolver/ # Main library (service implementations) -│ ├── core/ # Base classes, instruments, serializers -│ └── *.py # Service-specific (ReCaptcha, Cloudflare, etc.) -├── tests/ # Pytest suite (matches source structure) -├── docs/ # Sphinx documentation -├── ARCHITECTURE.md # System architecture (matklad-style) -└── pyproject.toml # Build, uv, pytest, black/isort config +├── src/python3_capsolver/ # Main library +│ ├── core/ # Base classes, instruments, serializers, enums +│ ├── control.py # Raw API: get_balance, create_task, get_task_result +│ ├── recaptcha.py # ReCaptcha V2/V3/Enterprise +│ ├── cloudflare.py # Cloudflare Turnstile/Challenge +│ └── *.py # Other captcha services (see package AGENTS.md) +├── tests/ # Pytest suite mirroring source structure +│ ├── conftest.py # BaseTest class, fixtures, rate-limiting delays +│ └── test_*.py # One file per service + test_core.py + test_instrument.py +├── docs/ # Sphinx documentation (make doc) +├── ARCHITECTURE.md # Layered architecture, data flow, invariants +├── pyproject.toml # Build, deps, black/isort/pytest config +└── Makefile # make tests, make refactor, make build, make doc ``` -## WHERE TO LOOK -| Task | Location | Notes | -|------|----------|-------| -| **Architecture** | `ARCHITECTURE.md` | Layered design, invariants, life of a request | -| **Base Logic** | `src/python3_capsolver/core/` | `base.py`, `serializer.py`, `enum.py`, instruments | -| **Service Implementations** | `src/python3_capsolver/*.py` | `recaptcha.py`, `cloudflare.py`, `control.py` | -| **Tests** | `tests/` | `conftest.py` (BaseTest, fixtures), per-service tests | -| **Configuration** | `pyproject.toml` | uv, black (120), isort, pytest (asyncio auto) | -| **Commands** | `Makefile` | `make tests`, `make build`, `make upload` | - -## CONVENTIONS -- **Toolchain**: `uv` for package management (`uv sync`, `uv run`, `uv build`, `uv publish`) -- **Formatter**: `black` (line-length 120), `isort` (profile "black") -- **Cleanup**: `autoflake` (remove unused imports/variables) -- **Serialization**: `msgspec` (not `json`) for performance -- **Concurrency**: Dual sync/async required for all instruments -- **Retries**: `tenacity` (async), `requests.Retry` (sync) — 5 attempts, exponential backoff -- **Testing**: pytest 7.0+, `pytest-asyncio` (auto mode), rate-limiting fixtures (1s func, 2s class) - -## ANTI-PATTERNS (THIS PROJECT) -- **Empty `__init__.py` files**: `src/python3_capsolver/__init__.py` only exports `__version__`; `core/__init__.py` is completely empty. Users must import via full paths (`from python3_capsolver.recaptcha import ReCaptcha`) -- **AGENTS.md in package dirs**: Will ship with distribution unless excluded in `pyproject.toml` -- **No CLI entry points**: Library-only, no console_scripts defined - -## UNIQUE STYLES -- **Service Pattern**: Each captcha service inherits from `CaptchaParams` with `captcha_handler()` (sync) + `aio_captcha_handler()` (async) -- **Task Payload**: Dict merged with internal params, passed to `create_task()` API -- **Context Managers**: All services support `with` / `async with` for session cleanup -- **Test Duplication**: Every sync test (`def test_*`) has async counterpart (`async def test_aio_*`) - -## COMMANDS +## Architecture and boundaries + +Four layers with strict dependency direction (top → bottom only): + +1. **Service layer** (`src/python3_capsolver/*.py`) — thin wrappers inheriting `CaptchaParams`, zero HTTP logic +2. **Base layer** (`core/base.py`) — `CaptchaParams` merges payloads, delegates to instruments +3. **Instrument layer** (`core/*_instrument.py`) — `SIOCaptchaInstrument` (sync) and `AIOCaptchaInstrument` (async) handle all HTTP, retries, and polling +4. **Support layer** (`core/serializer.py`, `core/enum.py`, `core/const.py`) — `msgspec.Struct` classes, enums, constants + +**Forbidden**: service files importing `requests`/`aiohttp` directly; support layer depending on upper layers. + +## Change rules + +- Every new captcha service must inherit `CaptchaParams`, provide `captcha_handler()` + `aio_captcha_handler()`, and register its type in `CaptchaTypeEnm` +- All serialization must use `msgspec.Struct` — never raw `json` module +- Dual sync/async is mandatory for any new instrument or service +- Service classes are thin: only `__init__` with captcha-type-specific params; all HTTP goes through instruments +- Context manager support (`with` / `async with`) is required on all service classes + +## Validation + ```bash -# Development -uv sync --all-groups # Install all dependencies -uv run pytest tests/ # Run tests -uv run black src/ tests/ # Format -uv run isort src/ tests/ # Sort imports - -# Build & Publish -uv build # Build wheel/sdist -uv publish # Upload to PyPI - -# Documentation -cd docs/ && uv run --group docs make html -e +make tests # pytest + coverage (HTML + XML) +make refactor # autoflake + black + isort on src/ and tests/ +make lint # autoflake --check, black --check, isort --check-only +uv run pytest tests/ -k # run specific tests ``` -## NOTES -- **API Key**: Tests require `API_KEY` environment variable -- **Coverage**: HTML reports in `coverage/html/`, XML in `coverage/coverage.xml` -- **Python Support**: 3.8–3.12 (tested via `target-version = ['py310']`) -- **Dependencies**: `requests>=2.21.0`, `aiohttp>=3.9.2`, `msgspec>=0.18,<=0.21`, `tenacity>=8,<10` +Tests require the `API_KEY` environment variable. Rate-limiting fixtures (`delay_func` 1s, `delay_class` 2s) prevent API throttling. + +## Key docs + +- `ARCHITECTURE.md` — full system map, data flow, invariants +- `src/python3_capsolver/AGENTS.md` — service-level conventions +- `src/python3_capsolver/core/AGENTS.md` — core module internals +- `tests/AGENTS.md` — test patterns and fixtures + +## Repository-specific gotchas + +- **Empty `__init__.py` files**: users import via full path (`from python3_capsolver.recaptcha import ReCaptcha`), never from top-level package +- **`AGENTS.md` in package dirs**: these ship with the wheel unless excluded in `pyproject.toml` — do not add more inside `src/` +- **`control.py` is the largest file** (~431 lines) and provides direct API access without the captcha-handling abstraction +- **Toolchain is `uv`**: use `uv run`, `uv sync`, `uv build` — not bare `pip` or `pytest` +- **`captcha_instrument.py` is ~221 lines**: contains both `CaptchaInstrumentBase` and `FileInstrument`; edits here affect all services diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 94ea7e0..f3f6c7a 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -2,307 +2,195 @@ ## 1. High-Level Overview -`python3-capsolver` is a Python 3.8+ client library for the Capsolver captcha-solving service API. It provides a unified, type-safe interface for solving various captcha types (ReCaptcha, Cloudflare Turnstile, GeeTest, DataDome, AWS WAF, etc.) through both synchronous and asynchronous execution models. +python3-capsolver is a Python 3.8+ client library for the [Capsolver](https://capsolver.com) captcha-solving API (`Observed`: `pyproject.toml` lines 38–43, `src/python3_capsolver/core/const.py` line 18). It provides a unified interface for submitting captcha tasks to Capsolver's cloud service and polling for results, covering ReCaptcha, Cloudflare Turnstile, GeeTest, DataDome, AWS WAF, MtCaptcha, FriendlyCaptcha, Yandex SmartCaptcha, image-to-text OCR, and AI vision tasks (`Observed`: `src/python3_capsolver/core/enum.py` `CaptchaTypeEnm`, service files in `src/python3_capsolver/`). -**Observed**: The library follows a layered architecture where core infrastructure (`core/`) provides base classes, serialization, and HTTP instruments, while service-specific modules (e.g., `recaptcha.py`, `cloudflare.py`) implement concrete captcha types. Dual sync/async support is achieved through separate instrument classes (`SIOCaptchaInstrument`, `AIOCaptchaInstrument`) that share a common interface. +The library is a single-package SDK distributed via PyPI as `python3-capsolver`. It exposes a dual sync/async API — synchronous calls use `requests`, asynchronous calls use `aiohttp` — so consumers can integrate it into either threading or `asyncio` applications without switching libraries (`Observed`: `pyproject.toml` lines 86–91, `core/sio_captcha_instrument.py` imports `requests`, `core/aio_captcha_instrument.py` imports `aiohttp`). -**Evidence Anchors**: -- `pyproject.toml`: Dependencies (`requests`, `aiohttp`, `msgspec`, `tenacity`), build system (setuptools), Python >=3.8 -- `src/python3_capsolver/core/base.py`: `CaptchaParams` base class with `captcha_handler()` and `aio_captcha_handler()` -- `src/python3_capsolver/core/enum.py`: Type-safe enums for captcha types, endpoints, response statuses -- `src/python3_capsolver/*.py`: Service-specific implementations (10+ captcha types) -- `README.md`: Usage examples, feature list, supported captcha types +Architecturally the codebase is a layered library: thin per-captcha-type service classes at the top, a shared base class that merges payloads and delegates to HTTP instruments, and a support layer of serializers, enums, and constants at the bottom. There is no server, no CLI, and no database — the library is a pure API client. -**Inferred**: The library prioritizes performance (hence `msgspec` over `json`) and resilience (retry logic via `tenacity` and `requests.Retry`). The separation of concerns between core infrastructure and service implementations suggests an intentional design for extensibility. - -**Unknown**: Whether there are any plans to support additional captcha types beyond those currently implemented. +Evidence anchors: `pyproject.toml`, `src/python3_capsolver/core/base.py`, `src/python3_capsolver/core/enum.py`, `src/python3_capsolver/core/const.py`, `src/python3_capsolver/recaptcha.py`, `src/python3_capsolver/core/serializer.py`. ## 2. System Architecture (Logical) -### Logical Components - -The library consists of four logical layers: - -1. **Service Layer** (`src/python3_capsolver/*.py`): High-level classes for each captcha type (e.g., `ReCaptcha`, `Cloudflare`, `Control`). Each class encapsulates captcha-specific parameters and provides sync/async handlers. - -2. **Base Layer** (`src/python3_capsolver/core/base.py`): The `CaptchaParams` class serves as the common base for all service classes. It handles payload serialization, URL configuration, and delegates to appropriate instruments. - -3. **Instrument Layer** (`src/python3_capsolver/core/*instrument.py`): HTTP client abstractions that manage API communication: - - `CaptchaInstrumentBase`: Abstract base with retry logic and result polling - - `SIOCaptchaInstrument`: Synchronous implementation using `requests` - - `AIOCaptchaInstrument`: Asynchronous implementation using `aiohttp` - - `FileInstrument`: File/URL/base64 processing utilities - -4. **Support Layer** (`src/python3_capsolver/core/`): Utilities including: - - `serializer.py`: `msgspec.Struct` classes for request/response serialization - - `enum.py`: Type-safe enumerations - - `const.py`: Configuration constants (API URLs, retry settings) - - `utils.py`: Utility functions - - `context_instr.py`: Context manager mixins for session cleanup - -### Dependency Direction +Four layers with strict top-to-bottom dependency direction: ``` -Service Layer (recaptcha.py, cloudflare.py, ...) - ↓ -Base Layer (base.py: CaptchaParams) - ↓ -Instrument Layer (*instrument.py: HTTP clients) - ↓ -Support Layer (serializer, enum, const, utils) - ↓ -External Dependencies (requests, aiohttp, msgspec, tenacity) +┌─────────────────────────────────────────────┐ +│ Service layer │ +│ (recaptcha.py, cloudflare.py, control.py, │ +│ gee_test.py, aws_waf.py, ...) │ +│ Thin wrappers: __init__ only, zero HTTP │ +└──────────────────┬──────────────────────────┘ + │ inherits CaptchaParams + ▼ +┌─────────────────────────────────────────────┐ +│ Base layer │ +│ (core/base.py: CaptchaParams) │ +│ Merges task payloads, delegates to │ +│ sync/async instruments │ +└──────────────────┬──────────────────────────┘ + │ creates instrument instances + ▼ +┌─────────────────────────────────────────────┐ +│ Instrument layer │ +│ (core/sio_captcha_instrument.py — sync, │ +│ core/aio_captcha_instrument.py — async, │ +│ core/captcha_instrument.py — base + files) │ +│ All HTTP, retries, and result-polling │ +└──────────────────┬──────────────────────────┘ + │ uses + ▼ +┌─────────────────────────────────────────────┐ +│ Support layer │ +│ (core/serializer.py — msgspec.Struct, │ +│ core/enum.py — CaptchaTypeEnm etc., │ +│ core/const.py — URLs, retry config, │ +│ core/context_instr.py — context managers, │ +│ core/utils.py — attempt generator) │ +└─────────────────────────────────────────────┘ ``` -**Allowed Dependencies**: -- Service classes → `CaptchaParams` (inheritance) + enums -- `CaptchaParams` → Instruments + serializers -- Instruments → Serializers + constants + external HTTP libraries -- Support layer → External libraries only (no internal dependencies) +**Dependency direction:** Service → Base → Instrument → Support. Never upward (`Observed`: import graph in all source files follows this direction; no lower layer imports from an upper layer). -**Forbidden Dependencies** (Inferred from structure): -- Support layer → Service/Base layers (would create circular dependencies) -- Instruments → Service-specific logic (violates separation of concerns) -- Service classes → Direct HTTP calls (must go through instruments) +**What each layer does NOT depend on:** -### SSR/Hybrid Boundaries +- **Service layer** never imports `requests`, `aiohttp`, or any HTTP library directly (`Observed`: all service files import only from `core.base` and `core.enum`). +- **Base layer** never performs HTTP calls itself — it delegates to instrument instances (`Observed`: `core/base.py` has no HTTP imports). +- **Support layer** never depends on service, base, or instrument layers (`Observed`: `serializer.py`, `enum.py`, `const.py`, `utils.py`, `context_instr.py` import only stdlib and `msgspec`/`tenacity`). -Not applicable. This is a pure Python library with no frontend/web rendering components. - -### Monorepo Status - -Not applicable. Single-package repository with standard `src/` layout. +**Key boundary:** `control.py` is architecturally distinct from the other service files. It bypasses the create-then-poll abstraction and calls instrument methods directly (`get_balance`, `create_task`, `get_task_result`) (`Observed`: `control.py` imports `AIOCaptchaInstrument` and `SIOCaptchaInstrument` directly, unlike other services which rely solely on `CaptchaParams.captcha_handler()` / `aio_captcha_handler()`). ## 3. Code Map (Physical) ``` -python3-capsolver/ -├── src/python3_capsolver/ # Main library package -│ ├── core/ # Core infrastructure (stable, rarely changes) -│ │ ├── base.py # CaptchaParams base class -│ │ ├── captcha_instrument.py # Base + File instrument -│ │ ├── sio_captcha_instrument.py # Sync HTTP client -│ │ ├── aio_captcha_instrument.py # Async HTTP client -│ │ ├── serializer.py # msgspec serialization -│ │ ├── enum.py # Type-safe enums -│ │ ├── const.py # Constants -│ │ └── utils.py # Utilities -│ │ -│ ├── control.py # Direct API methods (balance, task management) -│ ├── recaptcha.py # ReCaptcha V2/V3/Enterprise -│ ├── cloudflare.py # Cloudflare Turnstile/Challenge -│ ├── gee_test.py # GeeTest V3/V4 -│ ├── datadome_slider.py # DataDome slider captcha -│ ├── mt_captcha.py # MtCaptcha -│ ├── aws_waf.py # AWS WAF bypass -│ ├── friendly_captcha.py # FriendlyCaptcha -│ ├── yandex.py # Yandex SmartCaptcha -│ ├── image_to_text.py # OCR text extraction -│ ├── vision_engine.py # AI-based image recognition -│ ├── __init__.py # Package entry (exports version) -│ └── __version__.py # Version string -│ -├── tests/ # Pytest test suite (mirrors src/ structure) -│ ├── conftest.py # Pytest fixtures and configuration -│ ├── test_control.py # Control class tests -│ ├── test_core.py # Core module tests -│ ├── test_recaptcha.py # ReCaptcha tests -│ ├── test_cloudflare.py # Cloudflare tests -│ ├── test_instrument.py # Instrument tests -│ └── ... # One test file per service +. +├── src/python3_capsolver/ # Library source (setuptools package root) +│ ├── core/ # Shared infrastructure +│ │ ├── base.py # CaptchaParams — base class all services inherit +│ │ ├── captcha_instrument.py # CaptchaInstrumentBase (abstract) + FileInstrument +│ │ ├── sio_captcha_instrument.py # Sync instrument: requests-based HTTP + polling +│ │ ├── aio_captcha_instrument.py # Async instrument: aiohttp-based HTTP + polling +│ │ ├── serializer.py # msgspec.Struct request/response models +│ │ ├── enum.py # CaptchaTypeEnm, ResponseStatusEnm, EndpointPostfixEnm +│ │ ├── const.py # REQUEST_URL, RETRIES, ASYNC_RETRIES, APP_ID +│ │ ├── context_instr.py # SIOContextManager, AIOContextManager mixins +│ │ └── utils.py # attempts_generator (retry loop control) +│ ├── control.py # Raw API access (balance, create_task, get_task_result) +│ ├── recaptcha.py # ReCaptcha V2/V3/Enterprise solver +│ ├── cloudflare.py # Cloudflare Turnstile/Challenge solver +│ ├── gee_test.py # GeeTest V3/V4 solver +│ ├── datadome_slider.py # DataDome slider solver +│ ├── mt_captcha.py # MtCaptcha solver +│ ├── aws_waf.py # AWS WAF bypass solver +│ ├── friendly_captcha.py # FriendlyCaptcha solver +│ ├── yandex.py # Yandex SmartCaptcha solver +│ ├── image_to_text.py # OCR image-to-text solver +│ ├── vision_engine.py # AI vision engine solver +│ ├── __init__.py # Exports __version__ only — no re-exports +│ └── __version__.py # Version string │ -├── docs/ # Sphinx documentation -│ ├── source/ # Documentation source files -│ └── AGENTS.md # Documentation module context +├── tests/ # Pytest integration test suite (mirrors src/ layout) +│ ├── conftest.py # BaseTest class, rate-limiting fixtures +│ ├── files/ # Test assets (captcha images) +│ └── test_*.py # One test file per service + test_core + test_instrument │ -├── .github/workflows/ # CI/CD pipelines -│ ├── build.yml # Package build -│ ├── test.yml # Test execution -│ ├── lint.yml # Code quality checks -│ ├── sphinx.yml # Documentation build -│ └── install.yml # Installation verification -│ -├── pyproject.toml # Build, dependency, tool configuration -├── Makefile # Developer convenience commands -├── README.md # Usage guide and quick start -├── AGENTS.md # Project knowledge base (for LLMs) -└── uv.lock # Dependency lock file (uv package manager) +├── docs/ # Sphinx documentation source +├── pyproject.toml # Build config, dependencies, tool settings +├── Makefile # make tests, refactor, lint, build, doc +└── .coveragerc # Coverage: measures python3_capsolver/ only ``` -**Where to Look**: -- **Adding a new captcha type**: Create new file in `src/python3_capsolver/`, inherit from `CaptchaParams`, follow pattern in `recaptcha.py` -- **Changing API communication**: Modify instrument classes in `src/python3_capsolver/core/` -- **Updating serialization**: Edit `src/python3_capsolver/core/serializer.py` -- **Adding captcha types**: Update `CaptchaTypeEnm` in `src/python3_capsolver/core/enum.py` -- **Test patterns**: See `tests/test_recaptcha.py` for service tests, `tests/test_core.py` for core tests - -## 4. Life of a Request / Primary Data Flow - -### Synchronous Flow (e.g., ReCaptcha solving) - -1. **Initialization** (`recaptcha.py:ReCaptcha.__init__()`): - - User instantiates `ReCaptcha(api_key="...", captcha_type=CaptchaTypeEnm.ReCaptchaV2Task)` - - Calls `CaptchaParams.__init__()` which: - - Serializes API key into `create_task_payload` (via `RequestCreateTaskSer`) - - Initializes `task_params` with captcha type (via `TaskSer`) - - Creates `get_result_params` (via `RequestGetTaskResultSer`) - -2. **Handler Invocation** (`recaptcha.py:ReCaptcha.captcha_handler()` via inheritance): - - User calls `.captcha_handler(task_payload={"websiteURL": "...", "websiteKey": "..."})` - - Updates `self.task_params` with user-provided payload - - Instantiates `SIOCaptchaInstrument(captcha_params=self)` - -3. **Task Creation** (`sio_captcha_instrument.py:SIOCaptchaInstrument.processing_captcha()`): - - Sends POST to `{request_url}/createTask` with serialized payload - - Receives `taskId` in response - -4. **Result Polling** (`sio_captcha_instrument.py`): - - Polls `{request_url}/getTaskResult` with `taskId` at intervals (`sleep_time`, default 5s) - - Retries on transient failures (via `requests.Retry` adapter, 5 attempts) - - Continues until status is `ready`, `failed`, or retry limit exceeded - -5. **Response**: - - Returns dict with `errorId`, `taskId`, `status`, `solution` fields - - User extracts `solution` object containing captcha solution - -### Asynchronous Flow +**Where is X?** -Identical to sync flow, except: -- Uses `AIOCaptchaInstrument` with `aiohttp.ClientSession` -- Retries via `tenacity` (async retries decorator) -- Handler is `await solver.aio_captcha_handler(task_payload)` +- A new captcha type → create a file in `src/python3_capsolver/`, inherit `CaptchaParams`, register the type in `core/enum.py` `CaptchaTypeEnm`. +- HTTP retry configuration → `core/const.py` (`RETRIES`, `ASYNC_RETRIES`). +- API payload schemas → `core/serializer.py` (`msgspec.Struct` classes). +- The create-task → poll-result loop → `core/sio_captcha_instrument.py` (sync) and `core/aio_captcha_instrument.py` (async). +- File/image preprocessing (base64 encoding, URL download) → `core/captcha_instrument.py` `FileInstrument`. -### Context Manager Flow - -Both sync and async classes support context managers for automatic session cleanup: +## 4. Life of a Request / Primary Data Flow -```python -# Sync -with ReCaptcha(api_key="...") as solver: - result = solver.captcha_handler(task_payload) +This is a library (SDK), so the primary flow is the user calling a solver class. Typical path for a captcha-solving call: -# Async -async with ReCaptcha(api_key="...") as solver: - result = await solver.aio_captcha_handler(task_payload) +``` +1. User instantiates a service class + from python3_capsolver.recaptcha import ReCaptcha + solver = ReCaptcha(api_key="CAI-...", captcha_type=CaptchaTypeEnm.ReCaptchaV2TaskProxyLess) + +2. User calls captcha_handler() or aio_captcha_handler() with task-specific params + result = solver.captcha_handler(task_payload={"websiteURL": "...", "websiteKey": "..."}) + +3. CaptchaParams.captcha_handler() (core/base.py) + → merges task_payload into self.task_params + → creates SIOCaptchaInstrument(self) [or AIOCaptchaInstrument for async] + +4. SIOCaptchaInstrument.processing_captcha() (core/sio_captcha_instrument.py) + → builds RequestCreateTaskSer payload via msgspec Struct.to_dict() + → POSTs to https://api.capsolver.com/createTask + → if task is immediately ready: returns response + → otherwise: enters polling loop + → sleeps sleep_time seconds + → POSTs to https://api.capsolver.com/getTaskResult + → repeats up to 15 attempts (attempts_generator default) + → returns on status "ready" or "failed" + +5. Returns dict with full API response including solution ``` -**Evidence Anchors**: -- `src/python3_capsolver/core/base.py`: Lines 25-41 (initialization), 43-61 (sync handler), 63-80 (async handler) -- `src/python3_capsolver/core/sio_captcha_instrument.py`: `processing_captcha()` method -- `src/python3_capsolver/core/aio_captcha_instrument.py`: Async `processing_captcha()` method -- `src/python3_capsolver/core/context_instr.py`: `SIOContextManager`, `AIOContextManager` mixins +`control.py` follows a different, shorter path — it calls `SIOCaptchaInstrument.send_post_request()` or `AIOCaptchaInstrument.send_post_request()` directly for one-shot API calls like `get_balance`, bypassing the create-then-poll cycle (`Observed`: `control.py` lines 34–54, 49–54). ## 5. Architectural Invariants & Constraints -### Invariant 1: Dual Sync/Async Support +- **Rule:** Service files must never import `requests` or `aiohttp` directly. + - **Rationale:** All HTTP is encapsulated in the instrument layer; services are pure parameter holders. + - **Enforcement / Signals (Observed):** No service file (e.g. `recaptcha.py`, `cloudflare.py`) contains an HTTP library import. Violation would be visible on review. -- **Rule**: Every captcha-solving operation must provide both synchronous and asynchronous implementations. -- **Rationale**: Users may operate in sync or async codebases; the library must support both without forcing a choice. -- **Enforcement / Signals**: - - `CaptchaParams` base class defines both `captcha_handler()` and `aio_captcha_handler()` methods - - Separate instrument classes (`SIOCaptchaInstrument`, `AIOCaptchaInstrument`) implement each mode - - Tests include both sync and async variants (e.g., `test_recaptcha.py`) +- **Rule:** All serialization must use `msgspec.Struct` with `to_dict()`, never the `json` module directly. + - **Rationale:** Consistent serialization and type safety across the library. + - **Enforcement / Signals (Observed):** `core/serializer.py` defines `MyBaseModel(Struct)` with `to_dict()`. All instruments call `.to_dict()` on structs. -### Invariant 2: Service Classes Are Thin Wrappers +- **Rule:** Every captcha service must support both sync (`captcha_handler`) and async (`aio_captcha_handler`) execution. + - **Rationale:** The library's core value proposition is dual-mode operation. + - **Enforcement / Signals (Observed):** Both methods are defined in `CaptchaParams` (`core/base.py`) and inherited by all service classes. Test suite includes paired sync/async tests. -- **Rule**: Service-specific classes (e.g., `ReCaptcha`, `Cloudflare`) must not contain HTTP logic or API communication code. They only specify captcha type and inherit behavior from `CaptchaParams`. -- **Rationale**: Separation of concerns—API communication is infrastructure, captcha parameters are domain logic. This enables easy addition of new captcha types. -- **Enforcement / Signals**: - - All service classes inherit from `CaptchaParams` and typically only define `__init__()` (see `recaptcha.py:79-81`, 3 lines total) - - Code review / lint checks would flag HTTP calls in service files +- **Rule:** Every service class must support context manager protocols (`with` / `async with`). + - **Rationale:** Resource cleanup pattern for library consumers. + - **Enforcement / Signals (Observed):** `CaptchaParams` inherits both `SIOContextManager` and `AIOContextManager` mixins from `core/context_instr.py`. -### Invariant 3: Serialization via msgspec +- **Rule:** New captcha types must be registered in `CaptchaTypeEnm` (`core/enum.py`). + - **Rationale:** Single source of truth for type dispatch; the base class uses enum values to build API payloads. + - **Enforcement / Signals (Observed):** `core/base.py` constructs `TaskSer(type=captcha_type.value)` from the enum. -- **Rule**: All request/response serialization must use `msgspec.Struct` classes, not raw dicts or `json` module. -- **Rationale**: Performance—`msgspec` is significantly faster than `json` for serialization/deserialization. -- **Enforcement / Signals**: - - `src/python3_capsolver/core/serializer.py` defines `RequestCreateTaskSer`, `CaptchaResponseSer`, etc. - - Dependencies list `msgspec` but not alternative serializers - - `pyproject.toml` explicitly notes "msgspec preferred over json for performance" (AGENTS.md) +- **Rule:** Retry configuration is centralized in `core/const.py`, not hardcoded in instruments. + - **Rationale:** Single point of change for retry behavior across sync and async paths. + - **Enforcement / Signals (Observed):** `RETRIES` and `ASYNC_RETRIES` are defined in `const.py` and imported by both instruments and `FileInstrument`. -### Invariant 4: Retry Logic Is Mandatory +- **Rule:** Dependency direction is strictly top-down: Service → Base → Instrument → Support. + - **Rationale:** Prevents circular dependencies and keeps the support layer reusable. + - **Enforcement / Signals (Observed):** Import statements in all files respect this direction. No file in `core/` imports from a service file. -- **Rule**: All HTTP requests must include retry logic with exponential backoff. -- **Rationale**: Captcha-solving is time-sensitive and external API calls may fail transiently; automatic retries improve reliability. -- **Enforcement / Signals**: - - `SIOCaptchaInstrument` uses `requests.adapters.HTTPAdapter(max_retries=RETRIES)` (see `const.py` for retry config) - - `AIOCaptchaInstrument` uses `tenacity` async retries (`ASYNC_RETRIES` in `const.py`) - - Instruments are the only classes performing HTTP operations; base layer enforces their use +- **Rule:** `__init__.py` files are intentionally empty (or export only `__version__`); users import via full path. + - **Rationale:** Explicit imports avoid namespace pollution and circular re-exports. + - **Enforcement / Signals (Observed):** `src/python3_capsolver/__init__.py` exports `__version__` only; `core/__init__.py` is empty. Docstrings show full-path imports. -### Invariant 5: Type Safety via Enums +- **Rule:** Tests require `API_KEY` environment variable and include rate-limiting delays. + - **Rationale:** Integration tests hit the live Capsolver API; delays prevent throttling. + - **Enforcement / Signals (Observed):** `tests/conftest.py` reads `os.environ["API_KEY"]` and defines `delay_func` (1s) and `delay_class` (2s) fixtures. -- **Rule**: Captcha types, endpoint names, and response statuses must use enumerations (`CaptchaTypeEnm`, `EndpointPostfixEnm`, `ResponseStatusEnm`), not raw strings. -- **Rationale**: Prevents typos, enables IDE autocomplete, documents valid values explicitly. -- **Enforcement / Signals**: - - `CaptchaParams.__init__()` accepts `captcha_type: CaptchaTypeEnm` (typed parameter) - - All service examples in docstrings use enum values (e.g., `CaptchaTypeEnm.ReCaptchaV2Task`) - - `enum.py` defines all valid values in one location - -### Invariant 6: No Direct HTTP Calls in Service Layer +## 6. Documentation Strategy -- **Rule**: Service classes (`recaptcha.py`, `cloudflare.py`, etc.) must not import or use `requests`/`aiohttp` directly. All HTTP operations go through instruments. -- **Rationale**: Maintains separation of concerns, enables consistent retry/error-handling logic. -- **Enforcement / Signals**: - - Service classes import only from `.core.base` and `.core.enum` - - Instruments encapsulate all HTTP session management - - Static analysis / linting would catch violations +`ARCHITECTURE.md` (this file) is the global map of the repository: layers, dependency direction, invariants, and the code map. It answers "where is X?" and "what depends on what?". -### Invariant 7: Context Manager Support +Module-level detail lives in `AGENTS.md` files co-located with the code: +- `src/python3_capsolver/AGENTS.md` — service-layer conventions, file listing, and per-service change rules. +- `src/python3_capsolver/core/AGENTS.md` — core module internals: instrument responsibilities, serialization patterns, and safe-change guidance. +- `tests/AGENTS.md` — test patterns, fixtures, and how to add tests for new services. -- **Rule**: All captcha-solving classes must support Python context managers (`with` / `async with`) for automatic resource cleanup. -- **Rationale**: Ensures HTTP sessions are properly closed, prevents resource leaks. -- **Enforcement / Signals**: - - `CaptchaParams` inherits from `SIOContextManager` and `AIOContextManager` (see `base.py:14`) - - Context managers defined in `src/python3_capsolver/core/context_instr.py` - - Usage examples in docstrings show context manager patterns +API documentation is generated by Sphinx from `docs/` (`Observed`: `docs/conf.py`, `docs/Makefile`). Build with `make doc`. -## 6. Documentation Strategy +Build, test, and lint commands are documented in the root `Makefile` and in `AGENTS.md` at repository root. The root `AGENTS.md` also contains a condensed version of the architecture summary and validation commands. -### Documentation Hierarchy - -**ARCHITECTURE.md** (this document): -- High-level system map and component relationships -- Architectural invariants and constraints -- Primary data flow and execution paths -- Physical code map ("where is X?") -- Stable information unlikely to change frequently - -**Module-Level AGENTS.md/README.md** files: -- `AGENTS.md` (root): Project overview, structure, conventions, commands -- `src/python3_capsolver/AGENTS.md`: Package-level context, service list, usage patterns -- `src/python3_capsolver/core/AGENTS.md`: Core module details, class responsibilities, conventions -- Module docstrings: Class/function-level documentation with examples - -**External Documentation**: -- Sphinx documentation in `docs/` directory (generated from docstrings) -- README.md: Quick start, installation, usage examples -- CHANGELOG.md: Version history and breaking changes - -### What Belongs Where - -| Information Type | Location | -|------------------|----------| -| System architecture, boundaries | ARCHITECTURE.md | -| Dependency rules, invariants | ARCHITECTURE.md | -| "Where is X?" questions | ARCHITECTURE.md (Code Map) | -| Module purpose, structure | `/AGENTS.md` | -| Class/function usage examples | Docstrings (inline) | -| Installation, quick start | README.md | -| API parameter details | Sphinx docs (auto-generated) | -| Version changes | CHANGELOG.md | -| Development workflows | AGENTS.md (COMMANDS section) | - -### Documentation Conventions - -- **AGENTS.md files**: Generated knowledge base for LLMs and developers, following consistent template (OVERVIEW, STRUCTURE, WHERE TO LOOK, CONVENTIONS, COMMANDS) -- **Docstrings**: Google-style with Args, Returns, Examples, Notes sections -- **Type hints**: Full type annotations on all public APIs -- **Code examples**: Both sync and async variants shown in docstrings - -### Evidence Anchors for Documentation - -- Root `AGENTS.md`: Lines 1-41 (project structure, conventions, commands) -- `src/python3_capsolver/AGENTS.md`: Service pattern, dual handlers, retry logic -- `src/python3_capsolver/core/AGENTS.md`: Instrument patterns, serialization, retries -- `pyproject.toml`: Tool configuration (black, isort, pytest) -- `docs/`: Sphinx documentation source (auto-generated from docstrings) +What belongs where: +- **Global architecture** (this file): layers, boundaries, invariants, physical layout, primary data flow. +- **Module `AGENTS.md`**: local file listing, local boundaries, and specific change rules for that module. +- **Sphinx `docs/`**: public API reference, usage examples, and user-facing guides. diff --git a/Makefile b/Makefile index 0882373..96d59fe 100644 --- a/Makefile +++ b/Makefile @@ -27,8 +27,11 @@ lint: install build: uv build +# PyPI upload token: create one at https://pypi.org/manage/account/token/ +# and save it to the gitignored .pypi-token file: echo pypi-xxxx > .pypi-token upload: - uv publish + @test -f .pypi-token || { echo "missing .pypi-token (see Makefile comment)"; exit 1; } + @ UV_PUBLISH_TOKEN=`cat .pypi-token` uv publish tests: install uv run coverage run --rcfile=.coveragerc -m pytest --verbose --showlocals --disable-warnings \ diff --git a/README.es-ES.md b/README.es-ES.md new file mode 100644 index 0000000..1f7bd2f --- /dev/null +++ b/README.es-ES.md @@ -0,0 +1,164 @@ + + +# python3-capsolver + +[![Capsolver.png](https://s.vyjava.xyz/files/2024/12-December/17/109278aa/Capsolver.png)](https://vyjava.xyz/dashboard/image/109278aa-961a-4503-bed0-0a9c838dcef2) + +
+ +[![PyPI version](https://badge.fury.io/py/python3-capsolver.svg)](https://badge.fury.io/py/python3-capsolver) +[![Python versions](https://img.shields.io/pypi/pyversions/python3-capsolver.svg?logo=python&logoColor=FBE072)](https://badge.fury.io/py/python3-capsolver) +[![Downloads](https://static.pepy.tech/badge/python3-capsolver/month)](https://pepy.tech/project/python3-capsolver) +[![Static Badge](https://img.shields.io/badge/docs-Sphinx-green?label=Documentation&labelColor=gray)](https://andreidrang.github.io/python3-capsolver/) + +[![Maintainability](https://api.codeclimate.com/v1/badges/3c30167b5fb37a0775ea/maintainability)](https://codeclimate.com/github/AndreiDrang/python3-capsolver/maintainability) +[![Codacy Badge](https://app.codacy.com/project/badge/Grade/323d4eda0fe1477bbea8fe8902b9e97e)](https://www.codacy.com/gh/AndreiDrang/python3-capsolver/dashboard?utm_source=github.com&utm_medium=referral&utm_content=AndreiDrang/python3-capsolver&utm_campaign=Badge_Grade) +[![codecov](https://codecov.io/gh/AndreiDrang/python3-capsolver/branch/main/graph/badge.svg?token=2L4VVIF4G8)](https://codecov.io/gh/AndreiDrang/python3-capsolver) + +[![Sphinx build](https://github.com/AndreiDrang/python3-capsolver/actions/workflows/sphinx.yml/badge.svg?branch=release)](https://github.com/AndreiDrang/python3-capsolver/actions/workflows/sphinx.yml) +[![Build](https://github.com/AndreiDrang/python3-capsolver/actions/workflows/build.yml/badge.svg?branch=main)](https://github.com/AndreiDrang/python3-capsolver/actions/workflows/build.yml) +[![Installation](https://github.com/AndreiDrang/python3-capsolver/actions/workflows/install.yml/badge.svg?branch=main)](https://github.com/AndreiDrang/python3-capsolver/actions/workflows/install.yml) +[![Tests](https://github.com/AndreiDrang/python3-capsolver/actions/workflows/test.yml/badge.svg?branch=main)](https://github.com/AndreiDrang/python3-capsolver/actions/workflows/test.yml) +[![Lint](https://github.com/AndreiDrang/python3-capsolver/actions/workflows/lint.yml/badge.svg?branch=main)](https://github.com/AndreiDrang/python3-capsolver/actions/workflows/lint.yml) + + +Biblioteca de Python 3 para la API del servicio [Capsolver](https://dashboard.capsolver.com/passport/register?inviteCode=kQTn-tG07Jb1). +Probada en sistemas operativos basados en UNIX. + +La biblioteca está destinada a desarrolladores de software y se utiliza para trabajar con la API del servicio [Capsolver](https://dashboard.capsolver.com/passport/register?inviteCode=kQTn-tG07Jb1). + +## Características +- **Herramientas Modernas**: Utiliza `uv` para una gestión rápida y confiable de dependencias y aislamiento de entornos. +- **Soporte Síncrono y Asíncrono**: Soporte completo para operaciones tanto síncronas (`requests`) como asíncronas (`aiohttp`). +- **Seguridad de Tipos**: Enums para tipos de captcha y estados de respuesta. +- **Resiliencia**: Reintentos integrados utilizando `tenacity`. +- **Rendimiento**: Serialización JSON de alta velocidad con `msgspec`. +- **Cobertura**: Compatible con ReCaptcha (V2/V3), Cloudflare, DataDome, GeeTest, MtCaptcha, AWS WAF, Yandex e ImageToText. + +## ¿Cómo instalarlo? + +Recomendamos utilizar la versión más reciente de Python. `python3-capsolver` es compatible con Python 3.8+. + +### Desarrollo (usando uv) + +Este proyecto utiliza [uv](https://github.com/astral-sh/uv) para una gestión rápida y confiable de dependencias. + +```bash +# Install uv (if not already installed) +curl -LsSf https://astral.sh/uv/install.sh | sh + +# Install project dependencies +uv sync --all-groups +``` + +### Producción (pip) + +```bash +pip install python3-capsolver +``` + +## ¿Cómo usarlo? + +La documentación detallada está disponible en el [sitio web](https://andreidrang.github.io/python3-capsolver/). + +### Inicio Rápido + +#### Ejemplo Síncrono (ImageToText) +```python +from python3_capsolver.image_to_text import ImageToText + +# 1. Initialize with API Key +solver = ImageToText(api_key="YOUR_API_KEY") + +# 2. Solve +result = solver.captcha_handler( + task_payload={ + "body": "base64_encoded_image_string" + } +) + +# 3. Check result +if result["errorId"] == 0: + print("Solution:", result["solution"]) +else: + print("Error:", result["errorCode"]) +``` + +#### Ejemplo Asíncrono (ReCaptcha) +```python +import asyncio +from python3_capsolver.recaptcha import ReCaptcha +from python3_capsolver.core.enum import CaptchaTypeEnm + +async def main(): + # 1. Initialize + solver = ReCaptcha( + api_key="YOUR_API_KEY", + captcha_type=CaptchaTypeEnm.ReCaptchaV2TaskProxyLess + ) + + # 2. Solve + result = await solver.aio_captcha_handler( + task_payload={ + "websiteURL": "https://example.com", + "websiteKey": "SITE_KEY" + } + ) + + print(result) + +if __name__ == "__main__": + asyncio.run(main()) +``` + +## Tipos de Captcha Soportados +- **ReCaptcha**: V2 (Task/Enterprise), V3 (Task/Enterprise) +- **HCaptcha**: Task, Enterprise +- **Cloudflare**: Turnstile +- **GeeTest**: V3, V4 +- **DataDome**: Slider +- **MtCaptcha** +- **AWS WAF** +- **Yandex SmartCaptcha** +- **ImageToText**: CAPTCHAS de imágenes generales + +## Documentación y Contexto (Para LLMs) +- **Estructura del Proyecto**: Consulta `AGENTS.md` en la raíz y los subdirectorios para conocer la arquitectura interna. +- **Puntos de Entrada**: `src/python3_capsolver/*.py` contiene clases específicas de cada servicio (p. ej., `ReCaptcha`, `HCaptcha`). +- **Lógica Central**: `src/python3_capsolver/core/base.py` maneja el ciclo de comunicación con la API. +- **Enums**: Utiliza `python3_capsolver.core.enum` para parámetros con seguridad de tipos. + +## ¿Cómo realizar pruebas? + +El proyecto utiliza `uv` para tareas de pruebas y desarrollo. + +```bash +# 1. Set API_KEY in your environment (get this value from your account) +export API_KEY="your_api_key_here" + +# 2. Run tests (uses uv internally) +make tests + +# Other useful make targets: +# make install # Install dependencies with uv sync +# make lint # Run linting checks +# make build # Build the package +# make doc # Generate documentation +``` + +Todos los comandos `make` utilizan automáticamente `uv` para ejecutar los comandos en el entorno aislado. + + +### Registro de Cambios + +Consulta [CHANGELOG.md](https://github.com/AndreiDrang/python3-capsolver/blob/main/CHANGELOG.md) para el historial de versiones y cambios detallados. + +### ¿Cómo obtener la clave API para usar la biblioteca? +1. En la página: https://dashboard.capsolver.com/overview/user-center +2. Búscala: [![img.png](https://s.vyjava.xyz/files/2024/12-December/17/ae8d4fbf/img.png)](https://vyjava.xyz/dashboard/image/ae8d4fbf-7451-441d-8984-79b1a7adbe27) + +### Contacto + +Si tienes alguna pregunta, envía un mensaje al chat de [Telegram](https://t.me/pythoncaptcha). + +O envía un correo a python-captcha@pm.me diff --git a/pyproject.toml b/pyproject.toml index abaf1ca..13fdd95 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -86,7 +86,7 @@ classifiers = [ dependencies = [ "requests>=2.21.0", "aiohttp>=3.9.2", - "msgspec>=0.18,<=0.21", + "msgspec>=0.18", "tenacity>=8,<10" ] diff --git a/src/python3_capsolver/AGENTS.md b/src/python3_capsolver/AGENTS.md index ead5de2..c9b9e5c 100644 --- a/src/python3_capsolver/AGENTS.md +++ b/src/python3_capsolver/AGENTS.md @@ -1,55 +1,39 @@ -# PYTHON3_CAPSOLVER PACKAGE +# AGENTS.md -**Generated:** 2026-03-15 -**Commit:** b797332 +## Scope -## OVERVIEW -Main library package containing service-specific captcha solving implementations. Provides high-level interfaces for various captcha types (ReCaptcha, Cloudflare Turnstile, DataDome, etc.) through a unified API. +Service implementations for individual captcha types. Each file is a self-contained solver class. -Each service class inherits from `core.CaptchaParams` and implements synchronous (`requests`) and asynchronous (`aiohttp`) solving methods with automatic retry logic and response polling. +## What lives here -## STRUCTURE -``` +```text src/python3_capsolver/ -├── core/ # Base classes, instruments, serializers -├── control.py # Direct API access (get_balance, create_task, get_task_result) +├── core/ # Base classes, instruments, serializers (own AGENTS.md) +├── control.py # Raw API: get_balance, create_task, get_task_result ├── recaptcha.py # ReCaptcha V2/V3/Enterprise ├── cloudflare.py # Cloudflare Turnstile/Challenge -├── vision_engine.py # AI-based image recognition -├── image_to_text.py # OCR text extraction -├── datadome_slider.py # DataDome slider captcha -├── mt_captcha.py # MtCaptcha solver -├── gee_test.py # GeeTest solver +├── gee_test.py # GeeTest V3/V4 +├── datadome_slider.py # DataDome slider +├── mt_captcha.py # MtCaptcha ├── aws_waf.py # AWS WAF bypass -├── friendly_captcha.py # FriendlyCaptcha solver -├── yandex.py # Yandex captcha solver -├── __init__.py # Exports only __version__ (minimal) +├── friendly_captcha.py # FriendlyCaptcha +├── yandex.py # Yandex SmartCaptcha +├── image_to_text.py # OCR text extraction +├── vision_engine.py # AI-based image recognition +├── __init__.py # Exports only __version__ └── __version__.py # Version string ``` -## WHERE TO LOOK -| Task | Location | Notes | -|------|----------|-------| -| **Direct API Access** | `control.py` | `Control.get_balance()`, `create_task()`, `get_task_result()` | -| **ReCaptcha** | `recaptcha.py` | V2, V3, Enterprise variants | -| **Cloudflare** | `cloudflare.py` | Turnstile, Challenge modes | -| **Image-based** | `vision_engine.py`, `image_to_text.py` | AI recognition, OCR | -| **Other Services** | `*.py` | DataDome, GeeTest, AWS WAF, etc. | -| **Base Logic** | `core/` | `CaptchaParams`, instruments, serializers | - -## CONVENTIONS -- **Service Pattern**: Each service class inherits from `CaptchaParams` with `api_key` and `captcha_type` params -- **Dual Handlers**: All services provide `captcha_handler()` (sync) and `aio_captcha_handler()` (async) -- **Retry Logic**: Built-in exponential backoff with configurable `sleep_time` (default: 5s) -- **Task Payload**: Passed via `task_payload` dict, merged with internal task params -- **Response Structure**: Returns dict with `errorId`, `taskId`, `status`, `solution` fields -- **Context Managers**: Support `with` and `async with` for session cleanup - -## ANTI-PATTERNS (THIS PACKAGE) -- **Minimal `__init__.py`**: Does NOT export service classes. Users cannot do `from python3_capsolver import ReCaptcha` — must use full path `from python3_capsolver.recaptcha import ReCaptcha` -- **AGENTS.md in package dir**: Will ship with distribution unless excluded in `pyproject.toml` - -## UNIQUE STYLES -- **control.py (431 lines)**: Largest file, provides raw API access without abstraction -- **Service files**: 2-5k lines each, focused on single captcha type -- **No type stubs**: Type hints inline, no `.pyi` files +## Local boundaries and invariants + +- Service classes inherit `CaptchaParams` and define only `__init__` with captcha-type-specific params +- No HTTP imports (`requests`, `aiohttp`) allowed in service files — all HTTP goes through `core/` instruments +- Every service must provide both `captcha_handler()` (sync) and `aio_captcha_handler()` (async) via inheritance +- Every service must support context managers (`with` / `async with`) via `SIOContextManager` + `AIOContextManager` mixins + +## Safe change rules + +- To add a new captcha type: create `new_service.py`, inherit `CaptchaParams`, add type to `CaptchaTypeEnm` in `core/enum.py`, add serializer structs if needed in `core/serializer.py` +- `control.py` is unique: it provides raw API methods (`get_balance`, `create_task`, `get_task_result`) without the create-then-poll abstraction — do not convert it to the standard pattern +- Users import via full path (`from python3_capsolver.recaptcha import ReCaptcha`) — do not add re-exports to `__init__.py` +- This file ships inside the wheel; keep it concise and avoid sensitive information diff --git a/src/python3_capsolver/__version__.py b/src/python3_capsolver/__version__.py index c68196d..a955fda 100644 --- a/src/python3_capsolver/__version__.py +++ b/src/python3_capsolver/__version__.py @@ -1 +1 @@ -__version__ = "1.2.0" +__version__ = "1.2.1" diff --git a/src/python3_capsolver/core/AGENTS.md b/src/python3_capsolver/core/AGENTS.md index d52b85d..1b89540 100644 --- a/src/python3_capsolver/core/AGENTS.md +++ b/src/python3_capsolver/core/AGENTS.md @@ -1,46 +1,37 @@ -# CORE MODULE +# AGENTS.md -**Generated:** 2026-03-15 -**Commit:** b797332 +## Scope -## OVERVIEW -Core module provides foundational classes for synchronous (`requests`) and asynchronous (`aiohttp`) captcha solving operations. +Core infrastructure shared by all captcha services: base classes, HTTP instruments, serialization, enums, and constants. -Base classes establish patterns for Sync/Async instruments, enabling dual concurrency support across the library. Serialization leverages `msgspec` for high-performance JSON handling. +## What lives here -## STRUCTURE -``` -src/python3_capsolver/core/ -├── base.py # CaptchaParams entry class (Sync/Async handlers) -├── captcha_instrument.py # CaptchaInstrumentBase, FileInstrument (9.3k lines) -├── aio_captcha_instrument.py # AIOCaptchaInstrument (async implementation) -├── sio_captcha_instrument.py # SIOCaptchaInstrument (sync implementation) -├── serializer.py # Request/Response msgspec Struct classes -├── enum.py # EndpointPostfixEnm, CaptchaTypeEnm, ResponseStatusEnm -├── const.py # API URLs, retry configurations -├── utils.py # Utility functions (attempts_generator) -├── context_instr.py # Context manager instrumentation -└── __init__.py # Empty (anti-pattern) +```text +core/ +├── base.py # CaptchaParams: merges payloads, delegates to instruments +├── captcha_instrument.py # CaptchaInstrumentBase + FileInstrument (~221 lines) +├── sio_captcha_instrument.py # SIOCaptchaInstrument: sync HTTP via requests +├── aio_captcha_instrument.py # AIOCaptchaInstrument: async HTTP via aiohttp + tenacity +├── serializer.py # msgspec.Struct classes for API payloads/responses +├── enum.py # CaptchaTypeEnm, ResponseStatusEnm, EndpointPostfixEnm +├── const.py # REQUEST_URL, RETRIES, sleep intervals, status codes +├── context_instr.py # SIOContextManager, AIOContextManager mixins +├── utils.py # attempts_generator and helpers +└── __init__.py # Empty — import via full path ``` -## WHERE TO LOOK -| Task | File | Notes | -|------|------|-------| -| **Entry Point** | `base.py` | `CaptchaParams` class with `captcha_handler()` and `aio_captcha_handler()` | -| **Base Classes** | `captcha_instrument.py` | `CaptchaInstrumentBase` for instruments, `FileInstrument` for file processing | -| **Sync Instrument** | `sio_captcha_instrument.py` | `SIOCaptchaInstrument` - requests-based implementation | -| **Async Instrument** | `aio_captcha_instrument.py` | `AIOCaptchaInstrument` - aiohttp-based implementation | -| **Serialization** | `serializer.py` | `PostRequestSer`, `CaptchaResponseSer`, `MyBaseModel.to_dict()` | -| **Enums** | `enum.py` | `CaptchaTypeEnm`, `ResponseStatusEnm`, `EndpointPostfixEnm` | -| **Configuration** | `const.py` | `REQUEST_URL`, `RETRIES`, `VALID_STATUS_CODES` | - -## CONVENTIONS -- **Base Classes**: All instruments inherit from `CaptchaInstrumentBase` -- **Dual Support**: Every captcha operation provides both sync and async methods -- **Serialization**: `msgspec.Struct` classes with `to_dict()` method for API payloads -- **Retries**: `tenacity` for async, `requests.Retry` for sync (5 attempts, exponential backoff) -- **File Processing**: `FileInstrument` handles local files, URLs, and base64 in both sync/async contexts -- **Session Management**: Instruments maintain their own HTTP sessions with retry adapters - -## ANTI-PATTERNS (THIS MODULE) -- **Empty `__init__.py`**: Does NOT re-export base classes. Users must import via full path `from python3_capsolver.core.base import CaptchaParams` +## Local boundaries and invariants + +- `captcha_instrument.py` contains both `CaptchaInstrumentBase` (abstract) and `FileInstrument` (file/URL/base64 processing) — edits here affect every service +- Instruments are the only place `requests` and `aiohttp` are imported — service layer must never touch HTTP libraries +- All serialization uses `msgspec.Struct` with `to_dict()` — never use the `json` module directly +- Enums in `enum.py` are the single source of truth for captcha types, response statuses, and endpoint names +- `base.py:CaptchaParams` is the single entry point; service classes inherit it and add only type-specific `__init__` params + +## Safe change rules + +- Adding a new captcha type requires updating `CaptchaTypeEnm` in `enum.py` and optionally adding structs to `serializer.py` +- Changes to `captcha_instrument.py` are high-impact: it is shared by all services and both sync/async paths +- Retry configuration lives in `const.py` (`RETRIES`, `ASYNC_RETRIES`) — do not hardcode retry counts in instruments +- `context_instr.py` provides `__enter__`/`__exit__` and `__aenter__`/`__aexit__` — all services depend on these mixins +- `__init__.py` is intentionally empty; do not add re-exports diff --git a/tests/AGENTS.md b/tests/AGENTS.md index 0a6fcdc..09f9c8f 100644 --- a/tests/AGENTS.md +++ b/tests/AGENTS.md @@ -1,52 +1,41 @@ -# TESTS SUITE +# AGENTS.md -**Generated:** 2026-03-15 -**Commit:** b797332 +## Scope -## OVERVIEW -Pytest-based test suite validating API integration, dual-mode (sync/async) operations, and error handling for all captcha services. +Pytest integration test suite. Tests call the live Capsolver API and require `API_KEY` in the environment. -## STRUCTURE -``` +## What lives here + +```text tests/ -├── conftest.py # BaseTest class, fixtures, test utilities -├── test_*.py # Test modules (match source structure) -└── files/ # Test assets (captcha_example.jpeg) +├── conftest.py # BaseTest class, rate-limiting fixtures +├── test_core.py # CaptchaParams, enums, context managers +├── test_instrument.py # FileInstrument, CaptchaInstrumentBase +├── test_control.py # Control: get_balance, create_task, get_task_result +├── test_recaptcha.py # ReCaptcha V2/V3/Enterprise +├── test_cloudflare.py # Cloudflare Turnstile/Challenge +├── test_gee_test.py # GeeTest V3/V4 +├── test_datadome.py # DataDome slider +├── test_mt_captcha.py # MtCaptcha +├── test_aws_waf.py # AWS WAF bypass +├── test_friendly.py # FriendlyCaptcha +├── test_yandex.py # Yandex SmartCaptcha +├── test_image_to_text.py # OCR text extraction +├── test_vision_engine.py # AI-based image recognition +└── files/ # Test assets (captcha_example.jpeg) ``` -## WHERE TO LOOK -| Task | Location | Notes | -|------|----------|-------| -| **Base Test Class** | `tests/conftest.py` | `BaseTest` with utilities (`get_random_string`, `read_image`) | -| **Fixtures** | `tests/conftest.py` | `delay_func` (1s), `delay_class` (2s) for rate limiting | -| **Core Tests** | `tests/test_core.py` | Base logic, retries, enums, context managers | -| **Service Tests** | `tests/test_*.py` | Per-service tests (recaptcha, cloudflare, datadome, etc.) | -| **Instrument Tests** | `tests/test_instrument.py` | File processing, instruments | -| **Pytest Config** | `pyproject.toml` | `asyncio_mode = "auto"`, testpaths | - -## CONVENTIONS -- **Framework**: Pytest 7.0+ with `pytest-asyncio` (auto mode). -- **Dual Testing**: Every sync test (`def test_*`) has async counterpart (`async def test_aio_*`). -- **Parametrization**: Use `@pytest.mark.parametrize` for multiple captcha types in single test. -- **Base Class**: All tests inherit from `BaseTest` for common utilities and rate limiting. -- **Context Managers**: Test both `with` and `async with` patterns for resource cleanup. -- **Rate Limiting**: Default delays (1s function, 2s class) to avoid API throttling. -- **Error Testing**: Verify `errorId`, `errorCode`, and `solution=None` for invalid keys. - -## COMMANDS -```bash -# Run all tests -uv run pytest tests/ - -# Run with coverage -make tests # Runs coverage + HTML + XML reports - -# Coverage reports -coverage/html/ # HTML report -coverage/coverage.xml # XML for CI -``` +## Local boundaries and invariants + +- All test classes inherit `BaseTest` from `conftest.py` — provides `API_KEY`, `sleep_time`, `get_random_string()`, `read_image()` +- Every sync test (`def test_*`) has a corresponding async test (`async def test_aio_*`) +- Rate-limiting fixtures are mandatory: `delay_func` (1s, function scope) and `delay_class` (2s, class scope) prevent API throttling +- Tests are integration tests against the live API — they will fail without a valid `API_KEY` env var +- `pytest-asyncio` runs in `auto` mode (configured in `pyproject.toml`) + +## Safe change rules -## NOTES -- **API Key**: Requires `API_KEY` environment variable -- **Test Files**: 15 test modules, ~1.5k lines total -- **Rate Limiting**: Built-in delays prevent API throttling during test runs +- When adding a new captcha service, create a matching `test_.py` inheriting `BaseTest` with both sync and async test methods +- Do not remove or reduce rate-limiting fixtures — the API will throttle without them +- Coverage is configured in `.coveragerc`: measures `python3_capsolver/` only, omits `tests/` +- Run specific tests with `uv run pytest tests/ -k `, full suite with `make tests` diff --git a/uv.lock b/uv.lock index 969c12b..287f312 100644 --- a/uv.lock +++ b/uv.lock @@ -1315,11 +1315,11 @@ wheels = [ [[package]] name = "idna" -version = "3.11" +version = "3.15" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } +sdist = { url = "https://files.pythonhosted.org/packages/82/77/7b3966d0b9d1d31a36ddf1746926a11dface89a83409bf1483f0237aa758/idna-3.15.tar.gz", hash = "sha256:ca962446ea538f7092a95e057da437618e886f4d349216d2b1e294abfdb65fdc", size = 199245, upload-time = "2026-05-12T22:45:57.011Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, + { url = "https://files.pythonhosted.org/packages/d2/23/408243171aa9aaba178d3e2559159c24c1171a641aa83b67bdd3394ead8e/idna-3.15-py3-none-any.whl", hash = "sha256:048adeaf8c2d788c40fee287673ccaa74c24ffd8dcf09ffa555a2fbb59f10ac8", size = 72340, upload-time = "2026-05-12T22:45:55.733Z" }, ] [[package]] @@ -1706,8 +1706,6 @@ name = "msgspec" version = "0.20.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.11'", - "python_full_version == '3.10.*'", "python_full_version == '3.9.*'", ] sdist = { url = "https://files.pythonhosted.org/packages/ea/9c/bfbd12955a49180cbd234c5d29ec6f74fe641698f0cd9df154a854fc8a15/msgspec-0.20.0.tar.gz", hash = "sha256:692349e588fde322875f8d3025ac01689fead5901e7fb18d6870a44519d62a29", size = 317862, upload-time = "2025-11-24T03:56:28.934Z" } @@ -1770,6 +1768,99 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/02/59/079f33cd092ee42c9b97a59daa2115e7550a7eba98781ef6657e3d710d56/msgspec-0.20.0-cp39-cp39-win_arm64.whl", hash = "sha256:f7cd0e89b86a16005745cb99bd1858e8050fc17f63de571504492b267bca188a", size = 173927, upload-time = "2025-11-24T03:56:27.52Z" }, ] +[[package]] +name = "msgspec" +version = "0.22.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.11'", + "python_full_version == '3.10.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/d0/e6/6dcf9306ff3c5e486578f3bf29ed11dfbdbbc2a8bf0caf7e07d392887fda/msgspec-0.22.0.tar.gz", hash = "sha256:0a13624a4969159fe35d8c2a3d377b2b61bbd8585e327440d5e52725affcce38", size = 343188, upload-time = "2026-09-29T14:14:11.422Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/5e/78d4fa2073bb3a891753e7f915d51094e2ded5aa5e9b20402518929b373e/msgspec-0.22.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f3413e3647275f787b21b4dfb4836a59a1a5acf1018ab1d45843b1d7edf15c22", size = 200076, upload-time = "2026-09-29T14:12:07.599Z" }, + { url = "https://files.pythonhosted.org/packages/38/f8/59701da04584af4ccd55f42200da303ebf146cd6867186a8b9b1e127a4a2/msgspec-0.22.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:38c5b9bd347bc9abbcee40752be3c5117854e891ea7a1881a56d4b3dec58c5e7", size = 192337, upload-time = "2026-09-29T14:12:09.198Z" }, + { url = "https://files.pythonhosted.org/packages/eb/dd/bd4131da741aa349656fe32a5cca0c4266c58d7b5ad75485bed29565f7cd/msgspec-0.22.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:57c282f474e17acf6bcf84f393c73afd45d6eba47cccff8b76b79c4fbb8a3b54", size = 222888, upload-time = "2026-09-29T14:12:10.691Z" }, + { url = "https://files.pythonhosted.org/packages/c6/46/01fe71c42b3342f00e2dd6c5a8837f5dc4d0e1596b4c74c054fb13075201/msgspec-0.22.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:12a887c4c06e4a771a2db32c9a80c7bb21866b12458025f636dcdc2253331c28", size = 227838, upload-time = "2026-09-29T14:12:12.178Z" }, + { url = "https://files.pythonhosted.org/packages/62/8f/1a459825e0a5510de882af461459bd7f0525342b3c0bf1000e27be7aeef5/msgspec-0.22.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a6c8a3f210421e29d8f7e9815f106cf59d758665b7fe5428e61152ce24fe65d7", size = 235818, upload-time = "2026-09-29T14:12:13.586Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2e/9d37b6f1190101b452f6c455e8715cc9960afad231e18cf9545af58710b9/msgspec-0.22.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ebd211d7af79ed8710c64e9e8d4c0d02749bc20170e7ab4e1c5801ca7c99d25b", size = 228019, upload-time = "2026-09-29T14:12:15.156Z" }, + { url = "https://files.pythonhosted.org/packages/c1/d5/33723137c96b8f244d8e6fc57a0a8d3b57b3599ce9b4a4dd58dc55a46d1c/msgspec-0.22.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:27d9ef46c80884f9c4f323e0b18bec464287e872121e70f2cbe47335780bf597", size = 236406, upload-time = "2026-09-29T14:12:16.908Z" }, + { url = "https://files.pythonhosted.org/packages/44/4a/f0e4a9ab970ce0a31f191acb772d3e1af67eeb73e1d73b70c079252aed02/msgspec-0.22.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ec108e96fdaa8fdbe5bb993ec97a9d1faa69b3a521eecd71a6e5acbe0e29ae69", size = 231028, upload-time = "2026-09-29T14:12:18.497Z" }, + { url = "https://files.pythonhosted.org/packages/0a/e8/3de7345a8944a5bcfc9dd861d30fcea5f20f51057bcafacbbff9164e55fc/msgspec-0.22.0-cp310-cp310-win_amd64.whl", hash = "sha256:21c887d4de397355f6635c2a037b1c067882dac5d132a1793d63bbf7cf5ca78e", size = 190753, upload-time = "2026-09-29T14:12:20.291Z" }, + { url = "https://files.pythonhosted.org/packages/66/c9/f0d3bd2dfc3753806ab70b8d00a1613019c39148a87da797771d7f72a0a9/msgspec-0.22.0-cp310-cp310-win_arm64.whl", hash = "sha256:4a663a8d7f6ad56ac1dbcba91e046ba8ebab7773ae72ef3dd3c47f8226919184", size = 188939, upload-time = "2026-09-29T14:12:21.645Z" }, + { url = "https://files.pythonhosted.org/packages/9d/22/45c17acb1a85360b10afb95f66777f76bc2634993c66db8b7833832bd343/msgspec-0.22.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fb1e129b81ac8fcf9ec649b081c6c8da1c7ea6f87cab336d46386abc2cd855c1", size = 198231, upload-time = "2026-09-29T14:12:23.016Z" }, + { url = "https://files.pythonhosted.org/packages/34/79/1cf725694125051e866066d74e6199206838d1465cbfc35081dc29b6e366/msgspec-0.22.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dce29a04966e31abf9b83b697c6d672486526dc5d03fcd6970cb56d5dc1fbeea", size = 190911, upload-time = "2026-09-29T14:12:24.636Z" }, + { url = "https://files.pythonhosted.org/packages/bc/b2/e0ace038031a2988aa2e85c431c4d7aef734fbba4749ace6bc5bf310b769/msgspec-0.22.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b962000e11dd34fb210a5a2c57a8a62b2d92b381c8cb3b05c075a83e38f8d645", size = 220343, upload-time = "2026-09-29T14:12:26.111Z" }, + { url = "https://files.pythonhosted.org/packages/7b/e6/16ddb09185d79dc00177994cf0bdb1cd8e5cc44a1d1bfba61bdda5f382cb/msgspec-0.22.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a6db3806b3b76ca78064255eac6fa101a8a64fe6f698d80fbaf81fdfa21217d4", size = 225251, upload-time = "2026-09-29T14:12:27.559Z" }, + { url = "https://files.pythonhosted.org/packages/16/c2/a6af0d38fb0e72f02851ed084c4b8175140cfaf3eaf48b38da0c3941db26/msgspec-0.22.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a88d939d3fe4b8c7314645ebcd6e86c8c8a512ea7820d6550355973e803bc0f1", size = 233488, upload-time = "2026-09-29T14:12:28.996Z" }, + { url = "https://files.pythonhosted.org/packages/0b/9b/b1c4208cdf487e2ba7af145f721b279444ff76af05a9f8fce992ed0588ee/msgspec-0.22.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0b31746da07cba0e330c6433a94a4699ad77d3aeb9638d1a320a7686b69f6249", size = 225688, upload-time = "2026-09-29T14:12:30.351Z" }, + { url = "https://files.pythonhosted.org/packages/83/54/b9240d908674ef7c41d02cb909731ad6d9931c23bd6a27d8d10776c6f964/msgspec-0.22.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:6ae370f92f3517f0e6f209ba7cc649c957b444868439197e046be07154667551", size = 234250, upload-time = "2026-09-29T14:12:31.887Z" }, + { url = "https://files.pythonhosted.org/packages/df/c0/d498798aaab3bd191a33955de47b40f07fae7667d86a33b705443a7e9491/msgspec-0.22.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9a696f23f7c1ffb31fae308502e01a3965c3891d5c400f01d0d1096dbe77519e", size = 228337, upload-time = "2026-09-29T14:12:33.365Z" }, + { url = "https://files.pythonhosted.org/packages/fa/51/5e9ae5a5ddc254e15435749328161e95598750e5df644bb00fa9e2297122/msgspec-0.22.0-cp311-cp311-win_amd64.whl", hash = "sha256:024138c51afd335d0b4dce401be33902caafac2b64f8c9f2509a378986175d98", size = 190962, upload-time = "2026-09-29T14:12:34.847Z" }, + { url = "https://files.pythonhosted.org/packages/12/38/fb64a18543bcbebc53a375cb00b1c93bf264a0b6c7bbe9e38b37cc5f0768/msgspec-0.22.0-cp311-cp311-win_arm64.whl", hash = "sha256:4600dbec738ed74e4c9bd35503e84701200ea7db344cfdeda80677b3ee53eb64", size = 189458, upload-time = "2026-09-29T14:12:36.277Z" }, + { url = "https://files.pythonhosted.org/packages/a4/87/3e017dca361d09ed1cd09dc981a6df21b32e830fbec3470f7486d38b6be5/msgspec-0.22.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ab1e9e7531e353653b906cdd12a0220cc288a1e8e3436aabc65f4508d91b14d9", size = 201301, upload-time = "2026-09-29T14:12:38.048Z" }, + { url = "https://files.pythonhosted.org/packages/fb/02/109165edaafb895668d87177972a32ade9126a54f3736123d8e44be9096d/msgspec-0.22.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b60b43425a47eb9cfe987f6874e354ca7c760e58e295b4e2273ff03574df28a1", size = 193044, upload-time = "2026-09-29T14:12:39.46Z" }, + { url = "https://files.pythonhosted.org/packages/54/a5/65de05f8804492f76ea121b21a125cdf1d97ec461c677bfa0ba354d6fbdd/msgspec-0.22.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5a169b5b03f0f2c7a296c002647db1dab75d2cd501bca34e32b71cab0261b56", size = 224035, upload-time = "2026-09-29T14:12:40.876Z" }, + { url = "https://files.pythonhosted.org/packages/4a/cc/aa1a47f8c92280d37498a5ea56a2a36606d034383e3e6472d64cbb56cf85/msgspec-0.22.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:99c401861c5bb3a57f7d6423ea7ed4352cd57aa3f04f4fbe9f3e3e4564a10f08", size = 230377, upload-time = "2026-09-29T14:12:42.796Z" }, + { url = "https://files.pythonhosted.org/packages/61/50/f8bcdb3d613a4a4b92704297a12eba5c985cf572a64ee1a004d265759c69/msgspec-0.22.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:08826f5e5b0fa2f7a88592c396a243cfcc63d37e19f9d4fbe3b3f1be2fbdc404", size = 237390, upload-time = "2026-09-29T14:12:44.282Z" }, + { url = "https://files.pythonhosted.org/packages/cf/8a/473fa423f8fdd1b810b8652594323d7301df6920b62844d860daa0feff34/msgspec-0.22.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21460f54cee9208239b1a8421fdf25bffc77293e1daba88f585711ad839b9758", size = 227733, upload-time = "2026-09-29T14:12:45.839Z" }, + { url = "https://files.pythonhosted.org/packages/03/1d/272ce23adae6c71b3f763aed3ee6e115cccc56124ed8ee0e3e3d2681e2c8/msgspec-0.22.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:cfc3d9557de9c806318725b702f3e664db33167bb42892079b693c69893fd33b", size = 236783, upload-time = "2026-09-29T14:12:47.234Z" }, + { url = "https://files.pythonhosted.org/packages/f6/26/29e0b9a8605c8819a3c718158e345a616ac42c092dd7d7ab248c2f2b0a72/msgspec-0.22.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0b25dcbc108783cb72503ed705b9fbb8c3cb02ee5801923f44b5f038c91cc365", size = 232728, upload-time = "2026-09-29T14:12:48.792Z" }, + { url = "https://files.pythonhosted.org/packages/e1/a6/99597c281d716da6c662b48dcc3f734669f716b41d5df2af367dac9e7c21/msgspec-0.22.0-cp312-cp312-win_amd64.whl", hash = "sha256:6ad64f5c260866b0d543f89f50cee43628989c1433c5de7ce820281fa28a2611", size = 192885, upload-time = "2026-09-29T14:12:50.274Z" }, + { url = "https://files.pythonhosted.org/packages/46/80/85fff923d448b886ec3a85900c578d9367f08dad54fe48879495b4c6d055/msgspec-0.22.0-cp312-cp312-win_arm64.whl", hash = "sha256:0922714feff5300aacd8ecd65fa828317ce4bf5212b3139258c0bfc0253cd80e", size = 191223, upload-time = "2026-09-29T14:12:51.699Z" }, + { url = "https://files.pythonhosted.org/packages/7f/62/5374fba2ede0408f4bd8b9b3a6c8464f8d0ea7ae9a2a064bd81ca492bd1e/msgspec-0.22.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f13c127a945479bc9db057eb253b8851075c8e1ae07ffc967bfa1c5676203a86", size = 201355, upload-time = "2026-09-29T14:12:53.145Z" }, + { url = "https://files.pythonhosted.org/packages/cc/e3/357baa8d2a9164a98dfd7ef9d3a58125df0ed981be909945bdd337be7194/msgspec-0.22.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5aa24eb475d070ecbbe5b21080fc3ce4b0b76c60de25cfe0c9678d8fb44bb42f", size = 193097, upload-time = "2026-09-29T14:12:54.52Z" }, + { url = "https://files.pythonhosted.org/packages/fa/1b/9cc07718d1dee8ed5e89a265801d565bc0f15ead435ccb198f9c7bf92574/msgspec-0.22.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:627bfdfe5a4b3d916b3360b30f4cddeee3a084f56593e33527c6872fa8322ff9", size = 224112, upload-time = "2026-09-29T14:12:55.983Z" }, + { url = "https://files.pythonhosted.org/packages/46/64/f33fdfe95aca76601194a7064d14816c7c22c4eccc1b03a5335785895fa3/msgspec-0.22.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c6c310ef83e7e291b01a63298828f848348bb99e84a1098c4b3923c05674d032", size = 230472, upload-time = "2026-09-29T14:12:57.648Z" }, + { url = "https://files.pythonhosted.org/packages/8e/b3/8ceaa9981c230adf43c45a6e8da25da23a381eddc7ed05aeaca1d5e7928b/msgspec-0.22.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7c1e76c6bd523141b9c05c2f8a70979cd0efedbd68855a66f292f8892c0b8fc7", size = 237382, upload-time = "2026-09-29T14:12:59.414Z" }, + { url = "https://files.pythonhosted.org/packages/88/a6/7b5c4fb39e0bf2dabc8be923c33c39b07ba769a0ce6f0afbbdfaadb1f2f2/msgspec-0.22.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bc374dedd5f85a5f4de2386dc5f737894ccb8c1ac18e9566ce66fd9839e6285d", size = 227717, upload-time = "2026-09-29T14:13:00.88Z" }, + { url = "https://files.pythonhosted.org/packages/b8/5b/2334ee638880e756c8bc54a1177bd65877c786433693a43594ef5ecbe2d8/msgspec-0.22.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:feafe612034d49e9144340c0b5168ee4e22c2af4aaa2c1db11ae84e1aac9543b", size = 236781, upload-time = "2026-09-29T14:13:02.468Z" }, + { url = "https://files.pythonhosted.org/packages/6c/e5/b4c5323b17ecfce45350695d40fc93e16856db957a53cbcf2f53007d6e12/msgspec-0.22.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6f48317f05312bfdf78248f53933f830f07ab75cc1c813ac3ca4220cb3b5b019", size = 232777, upload-time = "2026-09-29T14:13:04.025Z" }, + { url = "https://files.pythonhosted.org/packages/01/33/e591f9d3d8d6c9cfc02ae95f3e3c44920f2d18050f3f252c244e0f293a0e/msgspec-0.22.0-cp313-cp313-win_amd64.whl", hash = "sha256:0739b068f31f2004a364f97679ba91f2f5ecd6ec2a5b4b890188ab5c57d20672", size = 192829, upload-time = "2026-09-29T14:13:05.519Z" }, + { url = "https://files.pythonhosted.org/packages/d1/cd/a011a5b8732cd781e2ea6da5b38d71ae4a9a329338411d1f008a58f5edbf/msgspec-0.22.0-cp313-cp313-win_arm64.whl", hash = "sha256:508278300dd4efbd21cd3a4b2b016160a5feac98bc880d3673f6c06697baaf62", size = 191258, upload-time = "2026-09-29T14:13:06.909Z" }, + { url = "https://files.pythonhosted.org/packages/53/f9/ac027b35477e6b83bcee32b3d9675b37abfa130f098dd6500fa67d768852/msgspec-0.22.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:221cbcbfa4478152b91d37dcfd4830e2be92773e8139e883f43773450ebacef8", size = 201276, upload-time = "2026-09-29T14:13:08.311Z" }, + { url = "https://files.pythonhosted.org/packages/13/6b/2bffffa31662b1353a62e672442865d51c291ad778352fd490de16361dc6/msgspec-0.22.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:dd9568695911055440d2bb7099ed9098fc181d335daa772d0eb3fe8f31ba4efb", size = 193233, upload-time = "2026-09-29T14:13:09.943Z" }, + { url = "https://files.pythonhosted.org/packages/14/bc/4066416ff6aa918d1ef9295edee0041e4629e4079ad3839bdd8a68fd87f0/msgspec-0.22.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f039ef5207b847f075a0a43020ee6140cd47505f890e47e157f2deb485c2dc96", size = 225101, upload-time = "2026-09-29T14:13:11.391Z" }, + { url = "https://files.pythonhosted.org/packages/63/ba/a8d390d5bd4c7d9ccde87c95cf071ada934cc9ca2c6af4d3d50b38f2d718/msgspec-0.22.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e4f7e09cceac7dbf4c0761b8ae7df51c55b5df5e9af7aff2c895aac1ebea015", size = 230505, upload-time = "2026-09-29T14:13:12.869Z" }, + { url = "https://files.pythonhosted.org/packages/9c/89/979664fdc913c624ef88a139b40e3a95ddf2a47c89e8b5c4147f69ee9c48/msgspec-0.22.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:614e2c827e0a3f934f3cf0cf4ba65210df8132b75a69a8a1f51bb3b2caf0ac5a", size = 237382, upload-time = "2026-09-29T14:13:14.317Z" }, + { url = "https://files.pythonhosted.org/packages/07/3f/7d44c614376ae008ac6099be5f589b322c4ad44e32c6dbb0edd256215028/msgspec-0.22.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa3689b9dfcc663358ef23ba4299d7460f01108515b041a7d30d05908ac9c32f", size = 228962, upload-time = "2026-09-29T14:13:15.763Z" }, + { url = "https://files.pythonhosted.org/packages/0b/59/bf8504e6f63f6769d01fb66f8bd856cf0ed39a07fde354f440d711640054/msgspec-0.22.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:d2f950239ff1fc7322c6f9634807310265149cb168270d3ddcdda5b6ada13a28", size = 236691, upload-time = "2026-09-29T14:13:17.195Z" }, + { url = "https://files.pythonhosted.org/packages/2b/40/5a9d2bde12af16a22ddbf371990a81d3e3c0dcd4bb4ef3b3f9616b033c14/msgspec-0.22.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3c789b5ccd07c0a3c09767108ee06e089b2875f2309a4569c2648f30a8d31dfa", size = 232750, upload-time = "2026-09-29T14:13:18.691Z" }, + { url = "https://files.pythonhosted.org/packages/75/5d/c0e6bdb81a87f6bd56a663a330c271af7670490c80d8d635d9fa21ad1adf/msgspec-0.22.0-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:a66b1766311e42371e509c996c3933b161c7ae0eabdf361af5316dec197e1022", size = 136814, upload-time = "2026-09-29T14:13:20.415Z" }, + { url = "https://files.pythonhosted.org/packages/b9/c0/b0cfc6d33608e5ea8871f3be31f9146c56699e737a7d8862bf018484f278/msgspec-0.22.0-cp314-cp314-win_amd64.whl", hash = "sha256:749899563d26b211379f142b8ffd7e2d7da149a51717798f0ce994dce50324f0", size = 197097, upload-time = "2026-09-29T14:13:21.869Z" }, + { url = "https://files.pythonhosted.org/packages/42/1f/571f7fe7c725380605d680fc4c0084212b23d2dfcf6be0f2277f14462c56/msgspec-0.22.0-cp314-cp314-win_arm64.whl", hash = "sha256:10d0d1d464960d99a949f7ca01ef8928e51c472433a5f5ab74b2d695fb830652", size = 196779, upload-time = "2026-09-29T14:13:23.62Z" }, + { url = "https://files.pythonhosted.org/packages/ab/f3/3c87372bac651b37911e0dc6926c3958949d3fcb8cec1016adbc44d948b2/msgspec-0.22.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:e79725246291516a7359caad5fb743ddc0ec66ed40d2381fb846325b5031504e", size = 205214, upload-time = "2026-09-29T14:13:25.158Z" }, + { url = "https://files.pythonhosted.org/packages/43/4c/fbccd6e0fbbdf10c4d9b6bac8a26148dd5483b3ffff6d6c5a376ff1f5cb1/msgspec-0.22.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:38f7022fbe91954b31afe3888a0af1b652e0f370fafdeb1d425f4a814d789c9f", size = 196941, upload-time = "2026-09-29T14:13:26.637Z" }, + { url = "https://files.pythonhosted.org/packages/55/04/8db7186d3ae8818356bc623cc132db8b77da37ce4b1345f35719c8ad5726/msgspec-0.22.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b6d3ca19a8ff28d0a67a1824e2bff7ec649ec795c80a265f20ade4caa63080de", size = 229934, upload-time = "2026-09-29T14:13:28.285Z" }, + { url = "https://files.pythonhosted.org/packages/17/24/a249f3491cabbe77cc65a1a6f87c128582aa39357227149be61cac8e554f/msgspec-0.22.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8b98ae215a102cbf6635f7df45f5c4af12f77fad1f7b71b9808fcf868a5735d", size = 234378, upload-time = "2026-09-29T14:13:29.821Z" }, + { url = "https://files.pythonhosted.org/packages/87/ee/6dbcb1b5de8e9d47e8f0fde9a288628dc178c1749a570b98251218fa10c4/msgspec-0.22.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e0aa0cc3f18c35bab79bd7b87fde95d6274a9deddeebd1ea541f8066a5073165", size = 243118, upload-time = "2026-09-29T14:13:31.544Z" }, + { url = "https://files.pythonhosted.org/packages/79/03/7dd2d0ca988600e01fc00ad0cf20d1d44bc59369a913c988654c65f6582b/msgspec-0.22.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8c8e84789918fbc15a503b92a829115ddd7567ecd3e4778bd418c56abbb86c11", size = 234557, upload-time = "2026-09-29T14:13:33.068Z" }, + { url = "https://files.pythonhosted.org/packages/74/e2/43f3c63bff1650efcaaea31466246e28b46927323fc9ff416c68cc6e4047/msgspec-0.22.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:3ca7d4cd69fbb66bd2da6211d3e79d40542d196c16c6d99bf838f76767ad35be", size = 241288, upload-time = "2026-09-29T14:13:34.532Z" }, + { url = "https://files.pythonhosted.org/packages/8b/70/11b93815a59674f33182dc3e873d343ca0b37e25be52ecb28f52092f1fed/msgspec-0.22.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:28f53f3604dd3e70225f7563c831628dbb03299b428f8e62aadb4b628e386874", size = 236432, upload-time = "2026-09-29T14:13:36.083Z" }, + { url = "https://files.pythonhosted.org/packages/b7/82/7aad0f033f8dcb3f23868773c2ede803ae162a784828ccde75aa3f9b2f9d/msgspec-0.22.0-cp314-cp314t-win_amd64.whl", hash = "sha256:7293dee54de040cfa225c22151cc3d72f17cd674b5ebcb52f38fb9f5701592e6", size = 202062, upload-time = "2026-09-29T14:13:37.955Z" }, + { url = "https://files.pythonhosted.org/packages/e3/45/cf52577926d73e2369e25927e389cb4ea1461169c489f46d3248159b5be7/msgspec-0.22.0-cp314-cp314t-win_arm64.whl", hash = "sha256:c3c510aba9015c085e514b75a9b3f1ed7c4591ae5e379655821b8bba51f30cc7", size = 201686, upload-time = "2026-09-29T14:13:39.42Z" }, + { url = "https://files.pythonhosted.org/packages/c8/63/d93937e2aae34ff1ea33b62799d1963cacc1bf432d196d6130039657a122/msgspec-0.22.0-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:263e110955ed76fe0af2d79f819903b50a70dc0e7a752eb7aabe79d2e0a084fb", size = 202241, upload-time = "2026-09-29T14:13:40.919Z" }, + { url = "https://files.pythonhosted.org/packages/3b/e2/46ece11a244cd56432eb2362ffbb8014f3f02963136d84d941f71fdc2a3f/msgspec-0.22.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:c6f06576eced70462179a4b4638e84cf69fdbba37f44d13a64a21739c131a830", size = 194232, upload-time = "2026-09-29T14:13:42.454Z" }, + { url = "https://files.pythonhosted.org/packages/cf/b1/1c385f2f93006cdc2af1511cc512c347cb22e2d4f11952c205230aedf586/msgspec-0.22.0-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8d67582478b0eaabb899f2fb255c878ee7de57dff80eb73ab24f1865524ec441", size = 226524, upload-time = "2026-09-29T14:13:43.876Z" }, + { url = "https://files.pythonhosted.org/packages/dc/fb/c80c8842d40347cacf89a60a4986b849dae1a6dfd25830441efdd6faa65b/msgspec-0.22.0-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:71cbbdb39631064e2f2f9e9ac2b1b69931d72276eb5f9da4ed025726296bdbb6", size = 231816, upload-time = "2026-09-29T14:13:45.329Z" }, + { url = "https://files.pythonhosted.org/packages/73/ac/90bbcfd890b4bda90c93f7e1b7fc24e84b270420486d9d43ae31443d15ab/msgspec-0.22.0-cp315-cp315-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8f0a5c25516e2034b2db7767081759ff8996e214def9c43b3055f61e1be1caad", size = 244241, upload-time = "2026-09-29T14:13:46.851Z" }, + { url = "https://files.pythonhosted.org/packages/72/9a/eabdb5f1b5e6013b0e2f9f2a95790587f6864aa9ca37f9d7dece65b53878/msgspec-0.22.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:a1dab6a99c759d1391ab2993388c1892746a697254f4b5dc6c059ca6e3bfbc8b", size = 230198, upload-time = "2026-09-29T14:13:48.296Z" }, + { url = "https://files.pythonhosted.org/packages/e9/89/9f080532d4ac52f416dd7318e55c2053cc071853d17d58e24897a5b553bf/msgspec-0.22.0-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:a52eba5c9528fd181fcec39d22b67aaa1dccc6cfe8e24d3f5d41130e6d04289d", size = 242949, upload-time = "2026-09-29T14:13:49.829Z" }, + { url = "https://files.pythonhosted.org/packages/11/df/6baf9b2f3523ebe2b820820c7929fd72ec5f483a93147130338ecc353fac/msgspec-0.22.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:1e547966017265c0d23342bcf2e027305dde40ea042d16694a9b96b4f696a052", size = 233914, upload-time = "2026-09-29T14:13:51.5Z" }, + { url = "https://files.pythonhosted.org/packages/bb/37/9cf650779c8c1e53291ef184c838703930a4cabb1fb37e222c85a7d49fa9/msgspec-0.22.0-cp315-cp315-win_amd64.whl", hash = "sha256:0067057df265795f742658b15dbe53f3b6f21d19dcfa53676db11088cfa41e0a", size = 197910, upload-time = "2026-09-29T14:13:53.071Z" }, + { url = "https://files.pythonhosted.org/packages/f5/ce/2f78c93d4f69e0167a19c2d40d4fbf7bbd6f074e1047536735832a4368ee/msgspec-0.22.0-cp315-cp315-win_arm64.whl", hash = "sha256:05dbc8268e50c9232ec72b9af1c7b13049aade4d1197764e38c427048706e046", size = 197590, upload-time = "2026-09-29T14:13:54.47Z" }, + { url = "https://files.pythonhosted.org/packages/3f/bf/282e9a443058b85b8f706c9a651e2d8cdd11cc09d16e8fa347b6c57b75bb/msgspec-0.22.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:b3113ebcceeb7693a915183c73d92c10bf5c62851dd187cab43bd025fb587419", size = 206298, upload-time = "2026-09-29T14:13:55.913Z" }, + { url = "https://files.pythonhosted.org/packages/ef/2d/2e694fa46f55319007f72013b17341ea3868be1c77e7a597176b202dda92/msgspec-0.22.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:0dfadea8bdcfafc614bd031de55a8ede22b43445cfff6d8b77cc0c07d3edc8a8", size = 198145, upload-time = "2026-09-29T14:13:57.412Z" }, + { url = "https://files.pythonhosted.org/packages/5b/2e/2fa279cb57cb47175ae604d572787f903d4ad3f0afa867201bbd99e6647e/msgspec-0.22.0-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d7a738826936c72348c613061d260446f13c82b6fd7d5d7705b6911ab8dca2f3", size = 232362, upload-time = "2026-09-29T14:13:58.817Z" }, + { url = "https://files.pythonhosted.org/packages/a0/58/a7e759b11b28441c27f803b29d9b5f4b5ad85150c89354b5ede1baca9258/msgspec-0.22.0-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f2ddea9d78d09460f06c26a7a508adcd049761c3208776162b8eb79b8a032cff", size = 235885, upload-time = "2026-09-29T14:14:00.381Z" }, + { url = "https://files.pythonhosted.org/packages/86/56/8d7ee098e94cbd9f35fa643dc497e06a4a6307b9f562cfbe48103fc3b209/msgspec-0.22.0-cp315-cp315t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:884c28c80b0a511595b29a9b04a3a230c3797369e4a033e6d5c6d9b5427f8e09", size = 248155, upload-time = "2026-09-29T14:14:01.945Z" }, + { url = "https://files.pythonhosted.org/packages/b9/6d/1cabb4b8a5dbf696e2b24df9e482b2e0333bb3b1b13ebb5433813e6616ec/msgspec-0.22.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:f7a923bcde480065c8e25967464cfb2a687ee67000bb43157e2d57e40eca7305", size = 236416, upload-time = "2026-09-29T14:14:03.363Z" }, + { url = "https://files.pythonhosted.org/packages/ba/43/8bf0f558eb369f1f2d494b3d5ab9d0ae0907d07ecc0cdbe11b6768b02867/msgspec-0.22.0-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:65eea14bc65ccfeb8f3af62cb204841871e2961f002d7fa87dbe0f79dacf1c1c", size = 247292, upload-time = "2026-09-29T14:14:04.829Z" }, + { url = "https://files.pythonhosted.org/packages/81/33/2fbaadf98b5510cac4bb56d2b03937e0b1fb4bfcd1ae6aba20361f299583/msgspec-0.22.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:0666a1520cab86796612e794e71107e0fbf5e8ff3ddcdfcfff8f1d94b860d2f1", size = 238220, upload-time = "2026-09-29T14:14:06.408Z" }, + { url = "https://files.pythonhosted.org/packages/f1/cc/b6be6041098ab859a8472983ccc2c08339fc2ef53f28d4f5fe7f4f34276b/msgspec-0.22.0-cp315-cp315t-win_amd64.whl", hash = "sha256:885c6e0c89d6103648525fe62aa78d600054dedf7b3713d23b15d7ddb6d66a13", size = 202939, upload-time = "2026-09-29T14:14:08.079Z" }, + { url = "https://files.pythonhosted.org/packages/5a/c1/664578dd98be70cd4ab1a9dcf3a181b1376b83c65ec41ee162130b58c8c0/msgspec-0.22.0-cp315-cp315t-win_arm64.whl", hash = "sha256:268594d0bae5510572599a6ab0364dd9de43c867d24a30856cd9f5edb63d8dc6", size = 202117, upload-time = "2026-09-29T14:14:09.891Z" }, +] + [[package]] name = "multidict" version = "6.1.0" @@ -2602,7 +2693,8 @@ dependencies = [ { name = "aiohttp", version = "3.10.11", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "aiohttp", version = "3.13.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, { name = "msgspec", version = "0.18.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, - { name = "msgspec", version = "0.20.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, + { name = "msgspec", version = "0.20.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "msgspec", version = "0.22.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "requests", version = "2.32.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "requests", version = "2.32.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, { name = "tenacity", version = "9.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, @@ -2641,7 +2733,7 @@ docs = [ [package.metadata] requires-dist = [ { name = "aiohttp", specifier = ">=3.9.2" }, - { name = "msgspec", specifier = ">=0.18,<=0.21" }, + { name = "msgspec", specifier = ">=0.18" }, { name = "requests", specifier = ">=2.21.0" }, { name = "tenacity", specifier = ">=8,<10" }, ]