Skip to content

Commit 2fd66d0

Browse files
vdusekclaude
andauthored
feat: generate TypedDict types for input-side models (#738)
## Summary Adds `TypedDict` counterparts for input-side models so users passing plain dicts to resource-client methods get full type-checker support without importing Pydantic models. - Generate TypedDicts alongside Pydantic models via a second `datamodel-code-generator` pass (`--output-model-type typing.TypedDict`) in `poe generate-models`. - Split generated content into `_models_generated.py` / `_typeddicts_generated.py`. Hand-written `_models.py` / `_typeddicts.py` now only hold shapes not exposed by the OpenAPI spec (e.g. `RequestInput`, `RequestInputDict`). - Extend `scripts/postprocess_generated_models.py` to trim the TypedDict file to input-relevant classes (plus transitive deps), rename them with a `Dict` suffix, and add `@docs_group('Typed dicts')`. - Widen resource-client input parameters (`actor`, `task`, `task_collection`, `request_queue`) to accept `TypedDict | PydanticModel` unions. - Update `.rules.md` and `manual_regenerate_models.yaml` to reflect the new layout. Closes #666. --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 7ac3253 commit 2fd66d0

65 files changed

Lines changed: 4763 additions & 4307 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/manual_regenerate_models.yaml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# This workflow regenerates Pydantic models (src/apify_client/_models.py) from the OpenAPI spec.
1+
# This workflow regenerates Pydantic models and TypedDicts (src/apify_client/_{models,typeddicts}_generated.py) from the OpenAPI spec.
22
#
33
# It can be triggered in two ways:
44
# 1. Automatically via workflow_dispatch from the apify-docs CI pipeline.
@@ -108,7 +108,7 @@ jobs:
108108
id: commit
109109
uses: EndBug/add-and-commit@v10
110110
with:
111-
add: src/apify_client/_models.py
111+
add: 'src/apify_client/_*_generated.py'
112112
author_name: apify-service-account
113113
author_email: apify-service-account@users.noreply.github.com
114114
message: ${{ env.TITLE }}
@@ -130,9 +130,9 @@ jobs:
130130
else
131131
if [[ -n "$DOCS_PR_NUMBER" ]]; then
132132
DOCS_PR_URL="https://github.com/apify/apify-docs/pull/${DOCS_PR_NUMBER}"
133-
BODY="This PR updates the auto-generated Pydantic models based on OpenAPI specification changes in [apify-docs PR #${DOCS_PR_NUMBER}](${DOCS_PR_URL})."
133+
BODY="This PR updates the auto-generated Pydantic models and TypedDicts based on OpenAPI specification changes in [apify-docs PR #${DOCS_PR_NUMBER}](${DOCS_PR_URL})."
134134
else
135-
BODY="This PR updates the auto-generated Pydantic models from the [published OpenAPI specification](https://docs.apify.com/api/openapi.json)."
135+
BODY="This PR updates the auto-generated Pydantic models and TypedDicts from the [published OpenAPI specification](https://docs.apify.com/api/openapi.json)."
136136
fi
137137
138138
PR_URL=$(gh pr create \

.rules.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ uv run poe type-check # Run ty type checker
1919
uv run poe unit-tests # Run unit tests
2020
uv run poe check-docstrings # Verify async docstrings match sync
2121
uv run poe fix-docstrings # Auto-fix async docstrings
22-
uv run poe generate-models # Regenerate _models.py from live OpenAPI spec
22+
uv run poe generate-models # Regenerate _models_generated.py and _typeddicts_generated.py from live OpenAPI spec
2323
uv run poe generate-models-from-file <path> # Regenerate from a local OpenAPI spec file
2424

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

7474
### Data Models
7575

76-
`src/apify_client/_models.py` is **auto-generated** — do not edit it manually.
76+
`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.
7777

7878
- 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`)
7979
- After generation, `scripts/postprocess_generated_models.py` is run to apply additional fixes

docs/02_concepts/code/03_nested_async.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from apify_client import ApifyClientAsync
2-
from apify_client._models import ActorJobStatus
2+
from apify_client._models_generated import ActorJobStatus
33

44
TOKEN = 'MY-APIFY-TOKEN'
55

docs/02_concepts/code/03_nested_sync.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from apify_client import ApifyClient
2-
from apify_client._models import ActorJobStatus
2+
from apify_client._models_generated import ActorJobStatus
33

44
TOKEN = 'MY-APIFY-TOKEN'
55

docs/04_upgrading/upgrading_to_v3.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ run.status
5555
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:
5656

5757
```python
58-
from apify_client._models import Run
58+
from apify_client._models_generated import Run
5959

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

8888
```python
89-
from apify_client._types import RequestInput
89+
from apify_client._models import RequestInput
9090

9191
# Option 1: dict (still works)
9292
rq_client.add_request({

pyproject.toml

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ indent-style = "space"
152152
"**/docs/03_guides/code/05_custom_http_client_{async,sync}.py" = [
153153
"ARG002", # Unused method argument
154154
]
155-
"src/apify_client/_models.py" = [
155+
"src/apify_client/_*_generated.py" = [
156156
"D", # Everything from the pydocstyle
157157
"E501", # Line too long
158158
"ERA001", # Commented-out code
@@ -210,7 +210,7 @@ context = 7
210210
# https://koxudaxi.github.io/datamodel-code-generator/
211211
[tool.datamodel-codegen]
212212
input_file_type = "openapi"
213-
output = "src/apify_client/_models.py"
213+
output = "src/apify_client/_models_generated.py"
214214
target_python_version = "3.11"
215215
output_model_type = "pydantic_v2.BaseModel"
216216
use_schema_description = true
@@ -272,8 +272,22 @@ shell = "./build_api_reference.sh && corepack enable && yarn && uv run yarn star
272272
cwd = "website"
273273

274274
[tool.poe.tasks.generate-models]
275-
shell = "uv run datamodel-codegen --url https://docs.apify.com/api/openapi.json && python scripts/postprocess_generated_models.py"
275+
shell = """
276+
uv run datamodel-codegen --url https://docs.apify.com/api/openapi.json \
277+
&& uv run datamodel-codegen --url https://docs.apify.com/api/openapi.json \
278+
--output src/apify_client/_typeddicts_generated.py \
279+
--output-model-type typing.TypedDict \
280+
--no-use-closed-typed-dict \
281+
&& python scripts/postprocess_generated_models.py
282+
"""
276283

277284
[tool.poe.tasks.generate-models-from-file]
278-
shell = "uv run datamodel-codegen --input $input_file && python scripts/postprocess_generated_models.py"
285+
shell = """
286+
uv run datamodel-codegen --input $input_file \
287+
&& uv run datamodel-codegen --input $input_file \
288+
--output src/apify_client/_typeddicts_generated.py \
289+
--output-model-type typing.TypedDict \
290+
--no-use-closed-typed-dict \
291+
&& python scripts/postprocess_generated_models.py
292+
"""
279293
args = [{ name = "input-file", positional = true, required = true }]

0 commit comments

Comments
 (0)