Typorithm · medium

TypeAdapter validates a batch without a wrapper model

pythonpydanticindependent-practice
Saved on this device
Full lesson · 4 learning steps Open any exercise for focused practice
1

Step 1 of 4 · intro card

TypeAdapter validates a batch without a wrapper model

0%

Concept and contract

Reuse an adapter for an existing type and distinguish item validity from duplicate-work policy.

Not every data boundary needs an object with a named field. An endpoint or file may legitimately contain a list of identifiers. TypeAdapter provides validation and serialization for a supported type expression without forcing that list into an artificial BaseModel wrapper.

The teaching batch contains at most three positive, strict integer IDs. Item metadata constrains each integer, and outer metadata bounds the list. The adapter is created once and reused by the inspection function. This avoids repeatedly rebuilding the same type boundary, but the lesson does not claim a measured performance improvement for a real application.

Mechanism and worked example

The Item alias rejects booleans, numeric strings and values below one.

The Item alias rejects booleans, numeric strings and values below one. Batch adds the collection limit, and adapter.validate_python checks the complete list. A valid list is returned in its submitted order so a caller can relate each accepted item to its original position.

Test a valid pair, a negative item and a numeric string. The last two fail different parts of the contract. An empty list remains permitted in the baseline; the contract has no minimum length. Do not infer additional policies from the word batch when the annotation does not declare them.

Complete worked example

python

from typing import Annotated
from pydantic import Field, TypeAdapter, ValidationError

Item = Annotated[int, Field(strict=True, ge=1)]
Batch = Annotated[list[Item], Field(max_length=3)]
adapter = TypeAdapter(Batch)

def inspect_example(payload):
    try:
        values = adapter.validate_python(payload)
        return {"value": values}
    except ValidationError as error:
        return {"error": sorted({e["type"] for e in error.errors()})}
Reasoning
The Item alias rejects booleans, numeric strings and values below one. Batch adds the collection limit, and adapter.validate_python checks the complete list. A valid list is returned in its submitted order so a caller can relate each accepted item to its original position. Test a valid pair, a negative item and a numeric string. The last two fail different parts of the contract. An empty list remains permitted in the baseline; the contract has no minimum length. Do not infer additional policies from the word batch when the annotation does not declare them.

Focused decision: use the complete example for context

python

from typing import Annotated
from pydantic import Field, TypeAdapter

Item = Annotated[int, Field(strict=True, ge=1)]
Batch = Annotated[list[Item], Field(max_length=3)]
adapter = TypeAdapter(Batch)

def parse_ids(payload):
    return adapter.validate_python(payload)
Focused decision
Reconstruct this focused decision after the warm-up. The complete imports, model or application, and test client remain in the worked example. Typing records practice; only the downloadable computer lab executes Python.

Counterexamples and common mistakes

Replacing Item with a plain int removes both the strictness and the positive bound, so a negative identifier or a converted numeric string can be accepted.

Replacing Item with a plain int removes both the strictness and the positive bound, so a negative identifier or a converted numeric string can be accepted. The counterexample demonstrates this real widening. A typed collection is only as precise as its item contract and the metadata actually attached to it.

Even a correctly validated list can contain the same identifier twice. Duplicated identity is a business rule, not automatically an error in list validation. Converting the list to a set would hide duplicated work and change ordering. Decide whether duplicate requests should be rejected, deduplicated visibly, or handled idempotently at another boundary.

Controlled mistake: observe the actual wrong behavior

python

from typing import Annotated
from pydantic import Field, TypeAdapter, ValidationError

Item = int
Batch = Annotated[list[Item], Field(max_length=3)]
adapter = TypeAdapter(Batch)

def inspect_example(payload):
    try:
        values = adapter.validate_python(payload)
        return {"value": values}
    except ValidationError as error:
        return {"error": sorted({e["type"] for e in error.errors()})}
Reasoning
Replacing Item with a plain int removes both the strictness and the positive bound, so a negative identifier or a converted numeric string can be accepted. The counterexample demonstrates this real widening. A typed collection is only as precise as its item contract and the metadata actually attached to it. Even a correctly validated list can contain the same identifier twice. Duplicated identity is a business rule, not automatically an error in list validation. Converting the list to a set would hide duplicated work and change ordering. Decide whether duplicate requests should be rejected, deduplicated visibly, or handled idempotently at another boundary.

Independent practice: one bounded change

Reject duplicate IDs after the list has passed validation.

Reject duplicate IDs after the list has passed validation. Keep the current bounds, strict item policy and input order for accepted batches. Return the declared duplicate_id error when any identity occurs more than once; do not silently discard a repeated item.

The batch is already bounded to three values, so a set-based uniqueness comparison is easy to reason about. Add one short guard after validate_python. Test a repeated pair alongside an ordinary pair and a negative item. The task changes the domain rule without replacing the adapter or widening input types.

Step 1: Reject duplicate IDs after validation without changing item strictness, the size bound or successful ordering. Step 2: Retain the working imports and application structure. Change the stated policy, run the supplied practice cases, then explain the newly accepted or rejected boundary.

Practice starter: solve before reading the solution

python

from typing import Annotated
from pydantic import Field, TypeAdapter, ValidationError

Item = Annotated[int, Field(strict=True, ge=1)]
Batch = Annotated[list[Item], Field(max_length=3)]
adapter = TypeAdapter(Batch)

def inspect_example(payload):
    try:
        values = adapter.validate_python(payload)
        return {"value": values}
    except ValidationError as error:
        return {"error": sorted({e["type"] for e in error.errors()})}

# Independent task: Reject duplicate IDs after validation without changing item strictness, the size bound or successful ordering.
Reasoning
Reject duplicate IDs after validation without changing item strictness, the size bound or successful ordering.
  • Hint 1: Add the new rule after values has been validated.
  • Hint 2: Compare len(values) with len(set(values)); preserve values when unique.

Solution, tests and trade-offs

The solution compares the validated list length with the size of a set of its IDs.

The solution compares the validated list length with the size of a set of its IDs. Equal sizes preserve the ordered list; different sizes return duplicate_id. The practice cases prove that the newly rejected duplicate is distinguishable from an item-validation error.

Uniqueness within one submitted batch does not establish idempotency across requests. A service can receive the same batch twice, or two workers can act concurrently. A durable idempotency key and transaction boundary solve a different problem. Keep the vocabulary precise so a simple list check is not advertised as a complete exactly-once processing design.

Practice solution and acceptance checks

python

from typing import Annotated
from pydantic import Field, TypeAdapter, ValidationError

Item = Annotated[int, Field(strict=True, ge=1)]
Batch = Annotated[list[Item], Field(max_length=3)]
adapter = TypeAdapter(Batch)

def inspect_example(payload):
    try:
        values = adapter.validate_python(payload)
        if len(values) != len(set(values)):
            return {"error": ["duplicate_id"]}
        return {"value": values}
    except ValidationError as error:
        return {"error": sorted({e["type"] for e in error.errors()})}
Reasoning
The solution compares the validated list length with the size of a set of its IDs. Equal sizes preserve the ordered list; different sizes return duplicate_id. The practice cases prove that the newly rejected duplicate is distinguishable from an item-validation error. Uniqueness within one submitted batch does not establish idempotency across requests. A service can receive the same batch twice, or two workers can act concurrently. A durable idempotency key and transaction boundary solve a different problem. Keep the vocabulary precise so a simple list check is not advertised as a complete exactly-once processing design.

Interview reasoning and next step

In an interview, justify TypeAdapter when the external shape is a list or another type rather than a model object.

In an interview, justify TypeAdapter when the external shape is a list or another type rather than a model object. Then explain the separate dimensions: item type, per-item bounds, collection size, duplicate identity and cross-request behavior. Give one input that tests each relevant dimension.

The next topic controls the output boundary. Validation alone does not decide which fields may be serialized to another user. A checked private model can still contain data that is inappropriate for the public API, so the output projection needs its own explicit model and acceptance example.

Optional stretch — after the core task works: Add one independently reasoned boundary case and explain its expected value before running it.

Computer practice: https://applydjinn.com/typorithm/python-api-lab-20261105.zip. Use an isolated Python environment and the pinned dependencies. Run verify.py --track pydantic, then edit the working exercise baseline and run --practice pyd-type-adapter. The reference suite rejects meaningful counterexamples and checks values, types and unchanged inputs. All data is owned and all HTTP clients are in-process. No production database, feed, account or paid service is used.

Reuse an adapter for an existing type and distinguish item validity from duplicate-work policy.