Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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__
Expand Down
98 changes: 98 additions & 0 deletions .rules.md
Original file line number Diff line number Diff line change
@@ -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
```
1 change: 1 addition & 0 deletions AGENTS.md
1 change: 1 addition & 0 deletions CLAUDE.md
1 change: 1 addition & 0 deletions GEMINI.md