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

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions