diff --git a/.gitignore b/.gitignore index 5a383df4..6cd3acfa 100644 --- a/.gitignore +++ b/.gitignore @@ -15,9 +15,9 @@ .serena .windsurf .zed-ai -AGENTS.md -CLAUDE.md -GEMINI.md +AGENTS.local.md +CLAUDE.local.md +GEMINI.local.md # Cache __pycache__ diff --git a/.rules.md b/.rules.md new file mode 100644 index 00000000..d7bc89e5 --- /dev/null +++ b/.rules.md @@ -0,0 +1,98 @@ +# Coding guidelines + +This file provides guidance to programming agents when working with code in this repository. + +## Project Overview + +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`. + +## Development Commands + +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+. + +```bash +uv run poe install-dev # Install dev deps + git hooks +uv run poe check-code # Run all checks (lint, type-check, unit-tests, docstring check) +uv run poe lint # Ruff format check + ruff check +uv run poe format # Auto-fix lint issues and format +uv run poe type-check # Run ty type checker +uv run poe unit-tests # Run unit tests +uv run poe check-async-docstrings # Verify async docstrings match sync +uv run poe fix-async-docstrings # Auto-fix async docstrings + +# Run a single test +uv run pytest tests/unit/test_file.py +uv run pytest tests/unit/test_file.py::test_name +``` + +Integration tests require `APIFY_TEST_USER_API_TOKEN` and `APIFY_TEST_USER_2_API_TOKEN` env vars. + +## Architecture + +### Client Hierarchy + +`ApifyClient`/`ApifyClientAsync` are the entry points. They provide methods that return resource sub-clients: + +``` +ApifyClient +├── .actor(id) → ActorClient (single resource operations) +├── .actors() → ActorCollectionClient (list/create) +├── .dataset(id) → DatasetClient +├── .datasets() → DatasetCollectionClient +├── .run(id) → RunClient +├── .runs() → RunCollectionClient +└── ... (schedules, tasks, webhooks, key-value stores, request queues, etc.) +``` + +Each resource client can create child clients (e.g., `ActorClient.builds()` → `BuildCollectionClient`). + +### Sync/Async Symmetry + +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`. + +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`. + +### Dependency Injection via ClientRegistry + +`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. + +### HTTP Client Abstraction + +- `HttpClient`/`HttpClientAsync` — abstract base classes in `_http_clients/_base.py` +- `ImpitHttpClient`/`ImpitHttpClientAsync` — default implementation (Rust-based Impit) +- `HttpResponse` — Protocol (not a concrete class) for response objects +- Users can plug in custom HTTP clients via `ApifyClient.with_custom_http_client()` + +### Base Classes + +- `ResourceClientBase` — shared URL building, parameter handling, metaclass-based logging +- `ResourceClient` / `ResourceClientAsync` — sync/async base classes with HTTP call methods +- `WithLogDetailsClient` metaclass — auto-wraps public methods for structured logging + +### Data Models + +`_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. + +## Code Conventions + +- **Line length**: 120 characters +- **Linting/formatting**: Ruff with nearly all rules enabled (see `pyproject.toml`) +- **Type checking**: ty (Astral's type checker) +- **Docstrings**: Google style format, single backticks for inline code references (`` `ApifyClient` `` not ``` ``ApifyClient`` ```) +- **Imports**: `from __future__ import annotations` used throughout +- **Commits**: [Conventional Commits](https://www.conventionalcommits.org/) format (feat, fix, refactor, etc.) + +## Testing + +- **Unit tests** (`tests/unit/`): Use `pytest-httpserver` to mock HTTP. No network required. +- **Integration tests** (`tests/integration/`): Hit the live Apify API. +- **Async**: `asyncio_mode = "auto"` — async tests run automatically without markers. +- **Parallelism**: Tests run in parallel via `pytest-xdist`. + +Unit test pattern: +```python +def test_example(httpserver: HTTPServer) -> None: + httpserver.expect_request('/v2/endpoint').respond_with_json({'data': ...}) + client = ApifyClient(token='test', api_url=httpserver.url_for('/').removesuffix('/')) + # assert client behavior +``` diff --git a/AGENTS.md b/AGENTS.md new file mode 120000 index 00000000..45ace44f --- /dev/null +++ b/AGENTS.md @@ -0,0 +1 @@ +.rules.md \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md new file mode 120000 index 00000000..45ace44f --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +.rules.md \ No newline at end of file diff --git a/GEMINI.md b/GEMINI.md new file mode 120000 index 00000000..45ace44f --- /dev/null +++ b/GEMINI.md @@ -0,0 +1 @@ +.rules.md \ No newline at end of file