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
4 changes: 2 additions & 2 deletions .rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ uv run poe type-check # Run ty type checker
uv run poe unit-tests # Run unit tests
uv run poe check-docstrings # Verify async docstrings match sync
uv run poe fix-docstrings # Auto-fix async docstrings
uv run poe generate-models # Regenerate _models_generated.py and _typeddicts_generated.py from live OpenAPI spec
uv run poe generate-models # Regenerate _models.py and _typeddicts.py from live OpenAPI spec
uv run poe generate-models-from-file <path> # Regenerate from a local OpenAPI spec file

# Run a single test
Expand Down Expand Up @@ -73,7 +73,7 @@ Docstrings are written on sync clients and **automatically copied** to async cli

### Data Models

`src/apify_client/_models_generated.py` and `src/apify_client/_typeddicts_generated.py` are **auto-generated** — do not edit them manually. The hand-maintained `src/apify_client/_models.py` and `src/apify_client/_typeddicts.py` hold only shapes that are not exposed by the OpenAPI spec (or that need local logic); import generated types directly from the `_*_generated` modules.
`src/apify_client/_models.py` and `src/apify_client/_typeddicts.py` are **auto-generated** — do not edit them manually. Every Pydantic model and TypedDict comes from the OpenAPI spec.

- Generated by `datamodel-code-generator` from the OpenAPI spec at `https://docs.apify.com/api/openapi.json` (config in `pyproject.toml` under `[tool.datamodel-codegen]`, aliases in `datamodel_codegen_aliases.json`)
- After generation, `scripts/postprocess_generated_models.py` is run to apply additional fixes
Expand Down
2 changes: 1 addition & 1 deletion docs/02_concepts/code/03_nested_async.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from apify_client import ApifyClientAsync
from apify_client._models_generated import ActorJobStatus
from apify_client._models import ActorJobStatus

TOKEN = 'MY-APIFY-TOKEN'

Expand Down
2 changes: 1 addition & 1 deletion docs/02_concepts/code/03_nested_sync.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from apify_client import ApifyClient
from apify_client._models_generated import ActorJobStatus
from apify_client._models import ActorJobStatus

TOKEN = 'MY-APIFY-TOKEN'

Expand Down
6 changes: 3 additions & 3 deletions docs/04_upgrading/upgrading_to_v3.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ run.status
Models also use `populate_by_name=True`, which means you can use either the Python field name or the camelCase alias when **constructing** a model:

```python
from apify_client._models_generated import Run
from apify_client._models import Run

# Both work when constructing models
Run(default_dataset_id='abc') # Python field name
Expand Down Expand Up @@ -86,7 +86,7 @@ rq_client.add_request({
After (v3) — both forms are accepted:

```python
from apify_client._models import RequestInput
from apify_client._models import RequestDraft

# Option 1: dict (still works)
rq_client.add_request({
Expand All @@ -96,7 +96,7 @@ rq_client.add_request({
})

# Option 2: Pydantic model (new)
rq_client.add_request(RequestInput(
rq_client.add_request(RequestDraft(
url='https://example.com',
unique_key='https://example.com',
method='GET',
Expand Down
8 changes: 4 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ indent-style = "space"
"**/docs/03_guides/code/05_custom_http_client_{async,sync}.py" = [
"ARG002", # Unused method argument
]
"src/apify_client/_*_generated.py" = [
"src/apify_client/_{models,typeddicts}.py" = [
"D", # Everything from the pydocstyle
"E501", # Line too long
"ERA001", # Commented-out code
Expand Down Expand Up @@ -213,7 +213,7 @@ context = 7
# https://koxudaxi.github.io/datamodel-code-generator/
[tool.datamodel-codegen]
input_file_type = "openapi"
output = "src/apify_client/_models_generated.py"
output = "src/apify_client/_models.py"
target_python_version = "3.11"
output_model_type = "pydantic_v2.BaseModel"
use_schema_description = true
Expand Down Expand Up @@ -285,7 +285,7 @@ cwd = "website"
shell = """
uv run datamodel-codegen --url https://docs.apify.com/api/openapi.json \
&& uv run datamodel-codegen --url https://docs.apify.com/api/openapi.json \
--output src/apify_client/_typeddicts_generated.py \
--output src/apify_client/_typeddicts.py \
--output-model-type typing.TypedDict \
--no-use-closed-typed-dict \
&& python scripts/postprocess_generated_models.py
Expand All @@ -295,7 +295,7 @@ uv run datamodel-codegen --url https://docs.apify.com/api/openapi.json \
shell = """
uv run datamodel-codegen --input $input_file \
&& uv run datamodel-codegen --input $input_file \
--output src/apify_client/_typeddicts_generated.py \
--output src/apify_client/_typeddicts.py \
--output-model-type typing.TypedDict \
--no-use-closed-typed-dict \
&& python scripts/postprocess_generated_models.py
Expand Down
21 changes: 12 additions & 9 deletions scripts/postprocess_generated_models.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
"""Post-process datamodel-codegen output to fix known issues and prune the TypedDict file.

Applied to `_models_generated.py`:
Applied to `_models.py`:
- Fix discriminator field names that use camelCase instead of snake_case (known issue with
discriminators on schemas referenced from array items).
- Deduplicate the inlined `Type(StrEnum)` that comes from ErrorResponse.yaml; rewire to `ErrorType`.
- Add `@docs_group('Models')` to every model class (plus the required import).

Applied to `_typeddicts_generated.py`:
Applied to `_typeddicts.py`:
- Keep only the TypedDicts actually used as resource-client method inputs (plus their transitive
dependencies). The file is generated in full by datamodel-codegen; the trimming happens here.
- Rename every kept class to add a `Dict` suffix so it doesn't clash with the Pydantic model name
Expand All @@ -27,8 +27,8 @@

REPO_ROOT = Path(__file__).resolve().parent.parent
PACKAGE_DIR = REPO_ROOT / 'src' / 'apify_client'
MODELS_PATH = PACKAGE_DIR / '_models_generated.py'
TYPEDDICTS_PATH = PACKAGE_DIR / '_typeddicts_generated.py'
MODELS_PATH = PACKAGE_DIR / '_models.py'
TYPEDDICTS_PATH = PACKAGE_DIR / '_typeddicts.py'

# Map of camelCase discriminator values to their snake_case equivalents.
# Add new entries here as needed when the OpenAPI spec introduces new discriminators.
Expand All @@ -37,16 +37,19 @@
}

# TypedDicts accepted as inputs by resource-client methods. These are the roots of the reachability
# walk over `_typeddicts_generated.py`: anything not reachable from here (directly or transitively)
# walk over `_typeddicts.py`: anything not reachable from here (directly or transitively)
# is dropped so only the TypedDicts that are part of the public input surface — plus their nested
# shapes — survive. Names are the raw datamodel-codegen outputs (no `Dict` suffix yet); the suffix
# is added later by `rename_with_dict_suffix`. Update this set whenever a new `<Name>Dict | <Name>`
# union is introduced on a resource-client method signature.
RESOURCE_INPUT_TYPEDDICTS: frozenset[str] = frozenset(
{
'Request', # RequestQueueClient.update_request
'RequestDraft', # RequestQueueClient.add_request, batch_add_requests
'RequestDraftDelete', # RequestQueueClient.batch_delete_requests
'TaskInput', # Actor/Task start/call/update default input
'WebhookCreate', # Actor/Task start/call webhook list element
'WebhookRepresentation', # Actor/Task start/call ad-hoc webhook list element
}
)

Expand Down Expand Up @@ -254,7 +257,7 @@ def rename_with_dict_suffix(content: str, names: set[str]) -> str:


def postprocess_models(path: Path) -> bool:
"""Apply `_models_generated.py`-specific fixes. Returns True if the file changed."""
"""Apply `_models.py`-specific fixes. Returns True if the file changed."""
original = path.read_text()
fixed = fix_discriminators(original)
fixed = deduplicate_error_type_enum(fixed)
Expand All @@ -266,7 +269,7 @@ def postprocess_models(path: Path) -> bool:


def postprocess_typeddicts(path: Path) -> bool:
"""Apply `_typeddicts_generated.py`-specific fixes. Returns True if the file changed."""
"""Apply `_typeddicts.py`-specific fixes. Returns True if the file changed."""
original = path.read_text()
pruned, kept = prune_typeddicts(original, RESOURCE_INPUT_TYPEDDICTS)
renamed = rename_with_dict_suffix(pruned, kept)
Expand All @@ -291,13 +294,13 @@ def main() -> None:
changed.append(MODELS_PATH)
print(f'Fixed generated models in {MODELS_PATH}')
else:
print('No fixes needed for _models_generated.py')
print('No fixes needed for _models.py')

if postprocess_typeddicts(TYPEDDICTS_PATH):
changed.append(TYPEDDICTS_PATH)
print(f'Pruned and renamed TypedDicts in {TYPEDDICTS_PATH}')
else:
print('No fixes needed for _typeddicts_generated.py')
print('No fixes needed for _typeddicts.py')

if changed:
run_ruff(changed)
Expand Down
2 changes: 1 addition & 1 deletion src/apify_client/_consts.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from datetime import timedelta

from apify_client._models_generated import ActorJobStatus
from apify_client._models import ActorJobStatus

DEFAULT_API_URL = 'https://api.apify.com'
"""Default base URL for the Apify API."""
Expand Down
Loading
Loading