Skip to content

Add typed Actor input: get_input(model) + Pydantic ↔ input-schema generation #989

Description

@vdusek

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

  • get_input(model: type[T]) -> T overload validating via Pydantic; -> Any default unchanged.
  • Code-first ActorInput with to_input_schema() emitting the Apify input-schema v1 dialect.
  • Schema-first path from an existing input_schema.json to a typed model (factory and/or codegen).
  • No existing public signature changes.
  • Docs guide covers both directions.

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

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request.t-toolingIssues with this label are in the ownership of the tooling team.

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions