You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/03_guides/11_pydantic.mdx
+7-7Lines changed: 7 additions & 7 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -24,8 +24,8 @@ An Actor reads its input with <ApiLink to="class/Actor#get_input">`Actor.get_inp
24
24
{RawInputExample}
25
25
</RunnableCodeBlock>
26
26
27
-
- There are no type guarantees -`max_results` could just as easily arrive as the string `"10"` or `None`, and you'd only find out when something blows up later.
28
-
- There's no validation - nothing stops `max_results` from being `0` or `-5`, or `search_terms` from being empty.
27
+
- There are no type guarantees.`max_results` could just as easily arrive as the string `"10"` or `None`, and you'd only find out when something blows up later.
28
+
- There's no validation. Nothing stops `max_results` from being `0` or `-5`, or `search_terms` from being empty.
29
29
- A typo in a key (`maxResult` instead of `maxResults`) silently falls back to the default instead of failing.
30
30
- Defaults are scattered across the codebase, and your editor can't autocomplete the fields or catch mistakes.
31
31
@@ -41,15 +41,15 @@ The following Actor declares its input as a Pydantic `BaseModel`, validates the
41
41
42
42
A few things worth pointing out about the **model**:
43
43
44
-
-**Aliases bridge the naming conventions.** Apify input fields are conventionally `camelCase` (`maxResults`), while Python attributes are `snake_case` (`max_results`). Since every field follows that convention, `alias_generator=to_camel` derives the camelCase alias for the whole model at once, instead of spelling out `Field(alias=...)` on each field. `populate_by_name=True` lets the model accept either spelling - handy in tests.
44
+
-**Aliases bridge the naming conventions.** Apify input fields are conventionally `camelCase` (`maxResults`), while Python attributes are `snake_case` (`max_results`). Since every field follows that convention, `alias_generator=to_camel` derives the camelCase alias for the whole model at once, instead of spelling out `Field(alias=...)` on each field. `populate_by_name=True` lets the model accept either spelling, which is handy in tests.
45
45
-**Defaults and `required` fields are explicit.** A field without a default (`search_terms`) is required; one with a default (`max_results`) is optional. There's a single, obvious place where every default lives.
46
-
-**Constraints are declarative.**`ge=1, le=100` enforces a numeric range, `min_length=1` rejects an empty list, and `Literal['json', 'csv']` restricts a field to a fixed set of choices - mirroring an `enum` in the input schema.
46
+
-**Constraints are declarative.**`ge=1, le=100` enforces a numeric range, `min_length=1` rejects an empty list, and `Literal['json', 'csv']` restricts a field to a fixed set of choices, mirroring an `enum` in the input schema.
47
47
-**Custom validators handle the rest.** The `field_validator` normalizes the search terms (trimming whitespace, dropping empties) and rejects input that has nothing left, so the rest of your code never has to repeat those checks.
48
48
-**Unknown fields are ignored.**`extra='ignore'` means adding a new field to your input schema won't break an older Actor build that doesn't know about it yet. Use `extra='forbid'` instead if you'd rather reject anything unexpected.
49
49
50
50
And about the **validation** itself:
51
51
52
-
-`model_validate` parses the raw dictionary into a typed `ActorInput` instance, filling in defaults and guaranteeing every field is valid - or raising a `ValidationError` describing every problem at once.
52
+
-`model_validate` parses the raw dictionary into a typed `ActorInput` instance, filling in defaults and guaranteeing every field is valid, or raising a `ValidationError` describing every problem at once.
53
53
- Catching that error, logging a readable summary, and re-raising makes the Actor **fail fast** with a clear explanation right at the start, rather than crashing with an obscure error somewhere deep in the run. Because the body runs inside `async with Actor:`, the re-raised exception automatically marks the run as `FAILED`.
54
54
- The error messages refer to the fields by their input-schema aliases. For invalid input like `{"searchTerms": [], "maxResults": 999, "outputFormat": "xml"}`, the log shows exactly what's wrong:
55
55
@@ -64,11 +64,11 @@ And about the **validation** itself:
64
64
Input should be 'json' or 'csv' ...
65
65
```
66
66
67
-
Once validation passes, the rest of `main` works with `actor_input.search_terms`, `actor_input.max_results`, and `actor_input.output_format` - all correctly typed, with editor autocompletion and static type checking.
67
+
Once validation passes, the rest of `main` works with `actor_input.search_terms`, `actor_input.max_results`, and `actor_input.output_format`, all correctly typed, with editor autocompletion and static type checking.
68
68
69
69
## Relationship to the input schema
70
70
71
-
Pydantic validation **complements** the Actor's [input schema](https://docs.apify.com/platform/actors/development/input-schema) (`.actor/input_schema.json`) - it doesn't replace it. The two serve different layers:
71
+
Pydantic validation **complements** the Actor's [input schema](https://docs.apify.com/platform/actors/development/input-schema) (`.actor/input_schema.json`). It doesn't replace it. The two serve different layers:
72
72
73
73
- The **input schema** drives the Apify Console form, documents the fields for your users, and lets the platform validate input before the run even starts. Keep declaring your fields there.
74
74
- The **Pydantic model** validates the input again *inside your Python code*, where it gives you a typed object, IDE support, and richer rules (normalization, cross-field checks, custom formats) that the input schema can't express. It's also your safety net for runs started programmatically by [another Actor](../concepts/interacting-with-other-actors) or executed [locally](https://docs.apify.com/cli/docs/reference#apify-run), and for keeping the two definitions honest with each other.
0 commit comments