|
| 1 | +# Coding guidelines |
| 2 | + |
| 3 | +This file provides guidance to programming agents when working with code in this repository. |
| 4 | + |
| 5 | +## Project Overview |
| 6 | + |
| 7 | +Python client for the [Apify API](https://docs.apify.com/api/v2). Provides `ApifyClient` (sync) and `ApifyClientAsync` (async) classes to interact with the Apify platform. Published on PyPI as `apify-client`. |
| 8 | + |
| 9 | +## Development Commands |
| 10 | + |
| 11 | +Uses [uv](https://docs.astral.sh/uv/) for project management and [Poe the Poet](https://poethepoet.natn.io/) as task runner. Requires Python 3.11+. |
| 12 | + |
| 13 | +```bash |
| 14 | +uv run poe install-dev # Install dev deps + git hooks |
| 15 | +uv run poe check-code # Run all checks (lint, type-check, unit-tests, docstring check) |
| 16 | +uv run poe lint # Ruff format check + ruff check |
| 17 | +uv run poe format # Auto-fix lint issues and format |
| 18 | +uv run poe type-check # Run ty type checker |
| 19 | +uv run poe unit-tests # Run unit tests |
| 20 | +uv run poe check-async-docstrings # Verify async docstrings match sync |
| 21 | +uv run poe fix-async-docstrings # Auto-fix async docstrings |
| 22 | + |
| 23 | +# Run a single test |
| 24 | +uv run pytest tests/unit/test_file.py |
| 25 | +uv run pytest tests/unit/test_file.py::test_name |
| 26 | +``` |
| 27 | + |
| 28 | +Integration tests require `APIFY_TEST_USER_API_TOKEN` and `APIFY_TEST_USER_2_API_TOKEN` env vars. |
| 29 | + |
| 30 | +## Architecture |
| 31 | + |
| 32 | +### Client Hierarchy |
| 33 | + |
| 34 | +`ApifyClient`/`ApifyClientAsync` are the entry points. They provide methods that return resource sub-clients: |
| 35 | + |
| 36 | +``` |
| 37 | +ApifyClient |
| 38 | +├── .actor(id) → ActorClient (single resource operations) |
| 39 | +├── .actors() → ActorCollectionClient (list/create) |
| 40 | +├── .dataset(id) → DatasetClient |
| 41 | +├── .datasets() → DatasetCollectionClient |
| 42 | +├── .run(id) → RunClient |
| 43 | +├── .runs() → RunCollectionClient |
| 44 | +└── ... (schedules, tasks, webhooks, key-value stores, request queues, etc.) |
| 45 | +``` |
| 46 | + |
| 47 | +Each resource client can create child clients (e.g., `ActorClient.builds()` → `BuildCollectionClient`). |
| 48 | + |
| 49 | +### Sync/Async Symmetry |
| 50 | + |
| 51 | +Every resource client has both sync and async variants in the **same file**. For example, `actor.py` contains both `ActorClient` and `ActorClientAsync`. Both share the same interface — async versions use `async/await`. |
| 52 | + |
| 53 | +Docstrings are written on sync clients and **automatically copied** to async clients via `scripts/check_async_docstrings.py`. When modifying docstrings, edit only the sync client and run `uv run poe fix-async-docstrings`. |
| 54 | + |
| 55 | +### Dependency Injection via ClientRegistry |
| 56 | + |
| 57 | +`ClientRegistry`/`ClientRegistryAsync` (in `_client_registry.py`) holds references to all resource client classes and is passed to each client. This avoids circular imports and lets resource clients create child clients without directly importing them. |
| 58 | + |
| 59 | +### HTTP Client Abstraction |
| 60 | + |
| 61 | +- `HttpClient`/`HttpClientAsync` — abstract base classes in `_http_clients/_base.py` |
| 62 | +- `ImpitHttpClient`/`ImpitHttpClientAsync` — default implementation (Rust-based Impit) |
| 63 | +- `HttpResponse` — Protocol (not a concrete class) for response objects |
| 64 | +- Users can plug in custom HTTP clients via `ApifyClient.with_custom_http_client()` |
| 65 | + |
| 66 | +### Base Classes |
| 67 | + |
| 68 | +- `ResourceClientBase` — shared URL building, parameter handling, metaclass-based logging |
| 69 | +- `ResourceClient` / `ResourceClientAsync` — sync/async base classes with HTTP call methods |
| 70 | +- `WithLogDetailsClient` metaclass — auto-wraps public methods for structured logging |
| 71 | + |
| 72 | +### Data Models |
| 73 | + |
| 74 | +`_models.py` is **auto-generated** from the OpenAPI spec using `datamodel-code-generator`. Do not edit manually; regenerate with `uv run poe generate-models`. Contains Pydantic v2 `BaseModel` classes for all API responses. |
| 75 | + |
| 76 | +## Code Conventions |
| 77 | + |
| 78 | +- **Line length**: 120 characters |
| 79 | +- **Linting/formatting**: Ruff with nearly all rules enabled (see `pyproject.toml`) |
| 80 | +- **Type checking**: ty (Astral's type checker) |
| 81 | +- **Docstrings**: Google style format, single backticks for inline code references (`` `ApifyClient` `` not ``` ``ApifyClient`` ```) |
| 82 | +- **Imports**: `from __future__ import annotations` used throughout |
| 83 | +- **Commits**: [Conventional Commits](https://www.conventionalcommits.org/) format (feat, fix, refactor, etc.) |
| 84 | + |
| 85 | +## Testing |
| 86 | + |
| 87 | +- **Unit tests** (`tests/unit/`): Use `pytest-httpserver` to mock HTTP. No network required. |
| 88 | +- **Integration tests** (`tests/integration/`): Hit the live Apify API. |
| 89 | +- **Async**: `asyncio_mode = "auto"` — async tests run automatically without markers. |
| 90 | +- **Parallelism**: Tests run in parallel via `pytest-xdist`. |
| 91 | + |
| 92 | +Unit test pattern: |
| 93 | +```python |
| 94 | +def test_example(httpserver: HTTPServer) -> None: |
| 95 | + httpserver.expect_request('/v2/endpoint').respond_with_json({'data': ...}) |
| 96 | + client = ApifyClient(token='test', api_url=httpserver.url_for('/').removesuffix('/')) |
| 97 | + # assert client behavior |
| 98 | +``` |
0 commit comments