Add typed, backward-compatible input to Actor: a Pydantic-validating get_input overload, plus
generation in both directions between a Pydantic model and .actor/input_schema.json.
Problem
Actor.get_input() -> Any (_actor.py:723) returns the raw input as a plain dict — no types, no
validation against .actor/input_schema.json. So: no autocomplete or static checks, a mistyped key
silently falls back to a default, invalid input only fails deep inside a (paid) run, and the model
and the schema — describing the same input from two sides — drift apart when hand-maintained.
Today's recommended path (docs/03_guides/11_pydantic.mdx) is to write the schema, then hand-write
a matching model. We want typed input to be first-class and to drop that duplication.
Proposed design
Three additive, backward-compatible pieces; the -> Any default of get_input() never changes.
1. Pydantic-validating get_input overload
@overload
async def get_input(self) -> Any: ... # unchanged default
@overload
async def get_input(self, model: type[T]) -> T: ... # validates via Pydantic
Parses the input into a validated instance (missing input → empty object, so all-optional models
yield defaults); raises ValidationError on invalid input. No-arg call keeps returning the raw dict.
2. Code-first: model → input schema. ActorInput, a preconfigured BaseModel (camelCase
aliases, populate-by-name, ignore-extra). Subclass it, validate with get_input(MyInput), and emit
.actor/input_schema.json via MyInput.to_input_schema() (Apify v1 dialect;
Field(json_schema_extra={...}) escape hatch). One source of truth — schema and validation can't drift.
3. Schema-first: input schema → model (new). Many Actors already ship a hand-written
input_schema.json; rewriting it as code is a migration barrier. Add the inverse of
to_input_schema(), reusing the same dialect mapping. Two shapes, left open:
(a) runtime factory ActorInput.from_input_schema(schema) — no build step, but dynamic (no static
types); (b) codegen emitting a .py ActorInput subclass — real static types, but a build step and
generated code (likely in apify-cli).
Acceptance criteria
Out of scope
Output (ActorOutput / set_output / output schema) is dropped — OUTPUT is a soft convention,
not a platform-enforced contract (discussion).
Notes
Pieces 1 & 2 are prototyped on branch feat/typed-io (src/apify/_models.py, the get_input
overload, the guide rewrite, tests/unit/test_models.py). Risk: low for the overload; medium for
the dialect mapping in both directions (must match the platform dialect exactly).
✍️ Drafted by Claude Code
Add typed, backward-compatible input to
Actor: a Pydantic-validatingget_inputoverload, plusgeneration in both directions between a Pydantic model and
.actor/input_schema.json.Problem
Actor.get_input() -> Any(_actor.py:723) returns the raw input as a plaindict— no types, novalidation against
.actor/input_schema.json. So: no autocomplete or static checks, a mistyped keysilently falls back to a default, invalid input only fails deep inside a (paid) run, and the model
and the schema — describing the same input from two sides — drift apart when hand-maintained.
Today's recommended path (
docs/03_guides/11_pydantic.mdx) is to write the schema, then hand-writea matching model. We want typed input to be first-class and to drop that duplication.
Proposed design
Three additive, backward-compatible pieces; the
-> Anydefault ofget_input()never changes.1. Pydantic-validating
get_inputoverloadParses the input into a validated instance (missing input → empty object, so all-optional models
yield defaults); raises
ValidationErroron invalid input. No-arg call keeps returning the raw dict.2. Code-first: model → input schema.
ActorInput, a preconfiguredBaseModel(camelCasealiases, populate-by-name, ignore-extra). Subclass it, validate with
get_input(MyInput), and emit.actor/input_schema.jsonviaMyInput.to_input_schema()(Apify v1 dialect;Field(json_schema_extra={...})escape hatch). One source of truth — schema and validation can't drift.3. Schema-first: input schema → model (new). Many Actors already ship a hand-written
input_schema.json; rewriting it as code is a migration barrier. Add the inverse ofto_input_schema(), reusing the same dialect mapping. Two shapes, left open:(a) runtime factory
ActorInput.from_input_schema(schema)— no build step, but dynamic (no statictypes); (b) codegen emitting a
.pyActorInputsubclass — real static types, but a build step andgenerated code (likely in
apify-cli).Acceptance criteria
get_input(model: type[T]) -> Toverload validating via Pydantic;-> Anydefault unchanged.ActorInputwithto_input_schema()emitting the Apify input-schema v1 dialect.input_schema.jsonto a typed model (factory and/or codegen).Out of scope
Output (
ActorOutput/set_output/ output schema) is dropped —OUTPUTis a soft convention,not a platform-enforced contract (discussion).
Notes
Pieces 1 & 2 are prototyped on branch
feat/typed-io(src/apify/_models.py, theget_inputoverload, the guide rewrite,
tests/unit/test_models.py). Risk: low for the overload; medium forthe dialect mapping in both directions (must match the platform dialect exactly).
✍️ Drafted by Claude Code