# Output: Donald is 5 years old and has 3000 feathers.

print(
    describe_duck(
        {
            "name": "Daffy",
            "age": "3",  # Pylance will show the hint: Argument of type "Literal['3']" cannot be assigned to parameter "age" of type "int" in function "describe_duck"
            "feather_count": 5000,
        }
    )
)
```

In this example, we define a `DuckStats` `TypedDict` with three keys: `name`, `age`, and `feather_count`. The type hints in the `TypedDict` definition specify that the `name` key should have a string value, while the `age` and `feather_count` keys should have integer values.

When we pass a dictionary to the `describe_duck` function, the IDE will show us a hint if there is a type mismatch in the dictionary values. This can help us catch bugs early and ensure that the data we are working with has the correct types.

While we now have type hints for dictionaries, data passed to our functions from the outside world are still unvalidated. Users can pass in the wrong types of values and we won't find out until runtime. This brings us to Pydantic.

## Pydantic

Pydantic is a data validation library for Python that enforces type hints at runtime. It helps developers with the following:

1. Data Validation: Pydantic ensures that data conforms to the defined types and constraints.
2. Data Parsing: Pydantic can convert input data into the appropriate Python types.
3. Serialization: Pydantic makes it easy to convert Python objects into JSON-compatible formats.
4. Deserialization: It can transform JSON-like data into Python objects.

These Pydantic functionalities are particularly useful when working with APIs that send and receive JSON data, or when processing user inputs.

Here's how you can use Pydantic to define a data model for a duck:

```python
from pydantic import BaseModel, Field, ValidationError

class Duck(BaseModel):
    name: str
    age: int = Field(gt=0)
    feather_count: int | None = Field(default=None, ge=0)