diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 97ca5e3..bc21ab9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -29,7 +29,7 @@ jobs: steps: - uses: actions/checkout@v6 - name: Set up uv - uses: astral-sh/setup-uv@v5 + uses: astral-sh/setup-uv@v7 with: enable-cache: true - name: Set up Python ${{ matrix.python-version }} diff --git a/.github/workflows/build_sphinx.yml b/.github/workflows/build_sphinx.yml index 2f97dfb..b108e12 100644 --- a/.github/workflows/build_sphinx.yml +++ b/.github/workflows/build_sphinx.yml @@ -16,7 +16,7 @@ jobs: steps: - uses: actions/checkout@v6 - name: Set up uv - uses: astral-sh/setup-uv@v5 + uses: astral-sh/setup-uv@v7 with: enable-cache: true - name: Set up Python 3.11 diff --git a/.github/workflows/install.yml b/.github/workflows/install.yml index 928f621..2de3faa 100644 --- a/.github/workflows/install.yml +++ b/.github/workflows/install.yml @@ -29,7 +29,7 @@ jobs: steps: - uses: actions/checkout@v6 - name: Set up uv - uses: astral-sh/setup-uv@v5 + uses: astral-sh/setup-uv@v7 with: enable-cache: true - name: Set up Python ${{ matrix.python-version }} diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index fb1c193..a6e7183 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -31,7 +31,7 @@ jobs: steps: - uses: actions/checkout@v6 - name: Set up uv - uses: astral-sh/setup-uv@v5 + uses: astral-sh/setup-uv@v7 with: enable-cache: true - name: Set up Python ${{ matrix.python-version }} diff --git a/.github/workflows/sphinx.yml b/.github/workflows/sphinx.yml index 4c3efdd..1b800ee 100644 --- a/.github/workflows/sphinx.yml +++ b/.github/workflows/sphinx.yml @@ -16,7 +16,7 @@ jobs: steps: - uses: actions/checkout@v6 - name: Set up uv - uses: astral-sh/setup-uv@v5 + uses: astral-sh/setup-uv@v7 with: enable-cache: true - name: Set up Python 3.11 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a3b6175..8411026 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -34,7 +34,7 @@ jobs: steps: - uses: actions/checkout@v6 - name: Set up uv - uses: astral-sh/setup-uv@v5 + uses: astral-sh/setup-uv@v7 with: enable-cache: true - name: Set up Python ${{ matrix.python-version }} diff --git a/.github/workflows/zai-code-bot.yml b/.github/workflows/zai-code-bot.yml new file mode 100644 index 0000000..2b7d256 --- /dev/null +++ b/.github/workflows/zai-code-bot.yml @@ -0,0 +1,45 @@ +name: Z.ai Code Bot + +on: + pull_request: + types: + - opened + - synchronize + - reopened + - ready_for_review + branches: + - main + - prod + issue_comment: + types: + - created + pull_request_review_comment: + types: + - created + +permissions: + contents: read + pull-requests: write + issues: write + +concurrency: + group: zai-bot-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + zai-bot: + if: | + (github.event_name == 'pull_request' && github.event.pull_request.draft == false) || + (github.event_name == 'issue_comment' && github.event.issue.pull_request) || + (github.event_name == 'pull_request_review_comment' && github.event.pull_request.draft == false) + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Run Z.ai Bot + uses: AndreiDrang/zai-code-bot@main + with: + ZAI_API_KEY: ${{ secrets.ZAI_API_KEY }} + ZAI_MODEL: ${{ vars.ZAI_MODEL || 'glm-5' }} + GITHUB_TOKEN: ${{ github.token }} diff --git a/AGENTS.md b/AGENTS.md index f7b51ce..ab09713 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,41 +1,72 @@ # PROJECT KNOWLEDGE BASE -**Generated:** 2026-01-13 +**Generated:** 2026-03-15 +**Commit:** b797332 +**Branch:** main ## OVERVIEW -Python 3.8+ library for Capsolver service API. Supports both synchronous (`requests`) and asynchronous (`aiohttp`) operations. Uses `msgspec` for high-performance JSON serialization. +Python 3.8+ library for Capsolver service API. Dual sync (`requests`) / async (`aiohttp`) support. `msgspec` for serialization, `tenacity` for retries. ## STRUCTURE ``` ./ -├── src/python3_capsolver/ # Main library package -│ ├── core/ # Base classes, serializers, instruments -│ └── *.py # Service-specific implementations (ReCaptcha, Cloudflare, etc.) -├── tests/ # Pytest suite -└── docs/ # Sphinx documentation +├── 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 ``` ## WHERE TO LOOK | Task | Location | Notes | |------|----------|-------| -| **Base Logic** | `src/python3_capsolver/core/` | `base.py`, `serializer.py`, `enum.py` | -| **Service Implementations** | `src/python3_capsolver/*.py` | `recaptcha.py`, `cloudflare.py`, etc. | -| **Tests** | `tests/` | Matches source structure | -| **Configuration** | `pyproject.toml` | Build, dependency, tool config | +| **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 -- **Formatter**: `black` (line-length 120), `isort` (profile "black"). -- **Serialization**: `msgspec` preferred over `json` for performance. -- **Concurrency**: Dual support (Sync/Async) required for all instruments. -- **Retries**: `tenacity` library used for resilience. +- **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 ```bash -make tests # Run test suite -pip install . # Install package locally +# 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 ``` ## NOTES -- Dependencies: `requests`, `aiohttp`, `msgspec`, `tenacity`. -- Requires `API_KEY` in environment for tests. - +- **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` diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 0000000..94ea7e0 --- /dev/null +++ b/ARCHITECTURE.md @@ -0,0 +1,308 @@ +# Architecture + +## 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. + +**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. + +**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 + +**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. + +## 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 + +``` +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) +``` + +**Allowed Dependencies**: +- Service classes → `CaptchaParams` (inheritance) + enums +- `CaptchaParams` → Instruments + serializers +- Instruments → Serializers + constants + external HTTP libraries +- Support layer → External libraries only (no internal dependencies) + +**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) + +### SSR/Hybrid Boundaries + +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. + +## 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 +│ +├── docs/ # Sphinx documentation +│ ├── source/ # Documentation source files +│ └── AGENTS.md # Documentation module context +│ +├── .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) +``` + +**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 + +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)` + +### Context Manager Flow + +Both sync and async classes support context managers for automatic session cleanup: + +```python +# Sync +with ReCaptcha(api_key="...") as solver: + result = solver.captcha_handler(task_payload) + +# Async +async with ReCaptcha(api_key="...") as solver: + result = await solver.aio_captcha_handler(task_payload) +``` + +**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 + +## 5. Architectural Invariants & Constraints + +### Invariant 1: Dual Sync/Async Support + +- **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`) + +### Invariant 2: Service Classes Are Thin Wrappers + +- **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 + +### Invariant 3: Serialization via msgspec + +- **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) + +### Invariant 4: Retry Logic Is Mandatory + +- **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 + +### Invariant 5: Type Safety via Enums + +- **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 + +- **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 + +### Invariant 7: Context Manager Support + +- **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 + +## 6. Documentation Strategy + +### 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) diff --git a/README.md b/README.md index 6fdfd29..bcbf653 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ Tested on UNIX based OS. The library is intended for software developers and is used to work with the [Capsolver](https://dashboard.capsolver.com/passport/register?inviteCode=kQTn-tG07Jb1) service API. ## Features +- **Modern Tooling**: Uses `uv` for fast, reliable dependency management and environment isolation. - **Sync & Async Support**: Full support for both synchronous (`requests`) and asynchronous (`aiohttp`) operations. - **Type Safety**: Enums for captcha types and response statuses. - **Resilience**: Built-in retries using `tenacity`. @@ -34,9 +35,21 @@ The library is intended for software developers and is used to work with the [Ca ## How to install? -We recommend using the latest version of Python. `python3-capsolver` supports Python 3.7+. +We recommend using the latest version of Python. `python3-capsolver` supports Python 3.8+. -### pip +### Development (using uv) + +This project uses [uv](https://github.com/astral-sh/uv) for fast, reliable dependency management. + +```bash +# Install uv (if not already installed) +curl -LsSf https://astral.sh/uv/install.sh | sh + +# Install project dependencies +uv sync --all-groups +``` + +### Production (pip) ```bash pip install python3-capsolver @@ -115,13 +128,28 @@ if __name__ == "__main__": ## How to test? -1. You need set ``API_KEY`` in your environment(get this value from you account). -2. Run command ``make tests``, from root directory. +The project uses `uv` for testing and development tasks. + +```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 +``` + +All `make` commands automatically use `uv` to run commands in the isolated environment. ### Changelog -Check [releases page](https://github.com/AndreiDrang/python3-capsolver/releases). +See [CHANGELOG.md](https://github.com/AndreiDrang/python3-capsolver/blob/main/CHANGELOG.md) for version history and detailed changes. ### How to get API Key to work with the library 1. On the page - https://dashboard.capsolver.com/overview/user-center diff --git a/src/captcha_example.jpeg b/src/captcha_example.jpeg new file mode 100644 index 0000000..74935ee Binary files /dev/null and b/src/captcha_example.jpeg differ diff --git a/src/python3_capsolver/AGENTS.md b/src/python3_capsolver/AGENTS.md index 95beba7..ead5de2 100644 --- a/src/python3_capsolver/AGENTS.md +++ b/src/python3_capsolver/AGENTS.md @@ -1,6 +1,7 @@ # PYTHON3_CAPSOLVER PACKAGE -**Generated:** 2026-01-13 +**Generated:** 2026-03-15 +**Commit:** b797332 ## 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. @@ -11,9 +12,9 @@ Each service class inherits from `core.CaptchaParams` and implements synchronous ``` src/python3_capsolver/ ├── core/ # Base classes, instruments, serializers -├── control.py # Control class for direct API methods -├── recaptcha.py # ReCaptcha V2/V3/Enterprise implementations -├── cloudflare.py # Cloudflare Turnstile/Challenge solver +├── control.py # Direct API access (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 @@ -21,7 +22,9 @@ src/python3_capsolver/ ├── gee_test.py # GeeTest solver ├── aws_waf.py # AWS WAF bypass ├── friendly_captcha.py # FriendlyCaptcha solver -└── yandex.py # Yandex captcha solver +├── yandex.py # Yandex captcha solver +├── __init__.py # Exports only __version__ (minimal) +└── __version__.py # Version string ``` ## WHERE TO LOOK @@ -41,3 +44,12 @@ src/python3_capsolver/ - **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 diff --git a/src/python3_capsolver/core/AGENTS.md b/src/python3_capsolver/core/AGENTS.md index f91cf1d..d52b85d 100644 --- a/src/python3_capsolver/core/AGENTS.md +++ b/src/python3_capsolver/core/AGENTS.md @@ -1,6 +1,7 @@ # CORE MODULE -**Generated:** 2026-01-13 +**Generated:** 2026-03-15 +**Commit:** b797332 ## OVERVIEW Core module provides foundational classes for synchronous (`requests`) and asynchronous (`aiohttp`) captcha solving operations. @@ -11,13 +12,15 @@ Base classes establish patterns for Sync/Async instruments, enabling dual concur ``` src/python3_capsolver/core/ ├── base.py # CaptchaParams entry class (Sync/Async handlers) -├── captcha_instrument.py # CaptchaInstrumentBase, FileInstrument +├── 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) +├── utils.py # Utility functions (attempts_generator) +├── context_instr.py # Context manager instrumentation +└── __init__.py # Empty (anti-pattern) ``` ## WHERE TO LOOK @@ -38,3 +41,6 @@ src/python3_capsolver/core/ - **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` diff --git a/src/recaptchav2_classification.png b/src/recaptchav2_classification.png new file mode 100644 index 0000000..b2ac9a7 Binary files /dev/null and b/src/recaptchav2_classification.png differ diff --git a/tests/AGENTS.md b/tests/AGENTS.md index 3f65515..0a6fcdc 100644 --- a/tests/AGENTS.md +++ b/tests/AGENTS.md @@ -1,5 +1,8 @@ # TESTS SUITE +**Generated:** 2026-03-15 +**Commit:** b797332 + ## OVERVIEW Pytest-based test suite validating API integration, dual-mode (sync/async) operations, and error handling for all captcha services. @@ -8,7 +11,7 @@ Pytest-based test suite validating API integration, dual-mode (sync/async) opera tests/ ├── conftest.py # BaseTest class, fixtures, test utilities ├── test_*.py # Test modules (match source structure) -└── files/ # Test assets (e.g., captcha_example.jpeg) +└── files/ # Test assets (captcha_example.jpeg) ``` ## WHERE TO LOOK @@ -29,3 +32,21 @@ tests/ - **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 +``` + +## 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 diff --git a/tests/test_control.py b/tests/test_control.py index 4b940d5..0328f4c 100644 --- a/tests/test_control.py +++ b/tests/test_control.py @@ -1,4 +1,5 @@ import pytest +from unittest.mock import patch, MagicMock, AsyncMock from tests.conftest import BaseTest from python3_capsolver.control import Control @@ -36,3 +37,139 @@ def test_get_balance_api_key_err(self): async def test_aio_get_balance_api_key_err(self): with pytest.raises(ValueError): await Control(api_key=self.get_random_string(36)).aio_get_balance() + + +class TestControlMock(BaseTest): + """ + Mocked Unit tests for Control class + """ + + @patch("python3_capsolver.core.sio_captcha_instrument.requests.Session.post") + def test_create_task(self, mock_post): + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = {"errorId": 0, "taskId": "test-task-id"} + mock_post.return_value = mock_response + + control = Control(api_key="test-key") + result = control.create_task({"type": "ImageToTextTask", "body": "base64..."}) + + assert result["taskId"] == "test-task-id" + assert result["errorId"] == 0 + mock_post.assert_called_once() + + @patch("python3_capsolver.core.aio_captcha_instrument.aiohttp.ClientSession.post") + async def test_aio_create_task(self, mock_post): + mock_resp = MagicMock() + mock_resp.status = 200 + mock_resp.json = AsyncMock( + return_value={"errorId": 0, "taskId": "test-task-id"} + ) + # Mock async context manager + mock_resp.__aenter__.return_value = mock_resp + mock_post.return_value = mock_resp + + control = Control(api_key="test-key") + result = await control.aio_create_task( + {"type": "ImageToTextTask", "body": "base64..."} + ) + + assert result["taskId"] == "test-task-id" + assert result["errorId"] == 0 + + @patch("python3_capsolver.core.sio_captcha_instrument.requests.Session.post") + def test_get_task_result(self, mock_post): + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = { + "errorId": 0, + "status": "ready", + "solution": {"text": "abc"}, + } + mock_post.return_value = mock_response + + control = Control(api_key="test-key") + result = control.get_task_result(task_id="test-id") + + assert result["status"] == "ready" + assert result["solution"]["text"] == "abc" + + @patch("python3_capsolver.core.aio_captcha_instrument.aiohttp.ClientSession.post") + async def test_aio_get_task_result(self, mock_post): + mock_resp = MagicMock() + mock_resp.status = 200 + mock_resp.json = AsyncMock( + return_value={ + "errorId": 0, + "status": "ready", + "solution": {"text": "abc"}, + } + ) + mock_resp.__aenter__.return_value = mock_resp + mock_post.return_value = mock_resp + + control = Control(api_key="test-key") + result = await control.aio_get_task_result(task_id="test-id") + + assert result["status"] == "ready" + assert result["solution"]["text"] == "abc" + + @patch("python3_capsolver.core.sio_captcha_instrument.requests.Session.post") + def test_get_token(self, mock_post): + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = {"errorId": 0, "taskId": "token-task-id"} + mock_post.return_value = mock_response + + control = Control(api_key="test-key") + result = control.get_token( + {"type": "ReCaptchaV3TaskProxyLess", "websiteURL": "..."} + ) + + assert result["taskId"] == "token-task-id" + + @patch("python3_capsolver.core.aio_captcha_instrument.aiohttp.ClientSession.post") + async def test_aio_get_token(self, mock_post): + mock_resp = MagicMock() + mock_resp.status = 200 + mock_resp.json = AsyncMock( + return_value={"errorId": 0, "taskId": "token-task-id"} + ) + mock_resp.__aenter__.return_value = mock_resp + mock_post.return_value = mock_resp + + control = Control(api_key="test-key") + result = await control.aio_get_token( + {"type": "ReCaptchaV3TaskProxyLess", "websiteURL": "..."} + ) + + assert result["taskId"] == "token-task-id" + + @patch("python3_capsolver.core.sio_captcha_instrument.requests.Session.post") + def test_feedback_task(self, mock_post): + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = {"errorId": 0, "message": "okay"} + mock_post.return_value = mock_response + + control = Control(api_key="test-key") + result = control.feedback_task( + task_id="test-id", result_payload={"invalid": True} + ) + + assert result["message"] == "okay" + + @patch("python3_capsolver.core.aio_captcha_instrument.aiohttp.ClientSession.post") + async def test_aio_feedback_task(self, mock_post): + mock_resp = MagicMock() + mock_resp.status = 200 + mock_resp.json = AsyncMock(return_value={"errorId": 0, "message": "okay"}) + mock_resp.__aenter__.return_value = mock_resp + mock_post.return_value = mock_resp + + control = Control(api_key="test-key") + result = await control.aio_feedback_task( + task_id="test-id", result_payload={"invalid": True} + ) + + assert result["message"] == "okay"