Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support AliasGenerator usage #8282

Merged
merged 13 commits into from Dec 4, 2023
1 change: 1 addition & 0 deletions docs/api/aliases.md
@@ -0,0 +1 @@
::: pydantic.aliases
2 changes: 0 additions & 2 deletions docs/api/fields.md
Expand Up @@ -4,8 +4,6 @@
members:
- Field
- FieldInfo
- AliasChoices
- AliasPath
- PrivateAttr
- ModelPrivateAttr
- computed_field
Expand Down
159 changes: 158 additions & 1 deletion docs/concepts/alias.md
Expand Up @@ -3,9 +3,164 @@ An alias is an alternative name for a field, used when serializing and deseriali
You can specify an alias in the following ways:

* `alias` on the [`Field`][pydantic.fields.Field]
* must be a `str`
* `validation_alias` on the [`Field`][pydantic.fields.Field]
* can be an instance of `str`, [`AliasPath`][pydantic.aliases.AliasPath], or [`AliasChoices`][pydantic.aliases.AliasChoices]
* `serialization_alias` on the [`Field`][pydantic.fields.Field]
* must be a `str`
* `alias_generator` on the [`Config`][pydantic.config.ConfigDict.alias_generator]
* can be a callable or an instance of [`AliasGenerator`][pydantic.aliases.AliasGenerator]

For examples of how to use `alias`, `validation_alias`, and `serialization_alias`, see [Field aliases](../concepts/fields.md#field-aliases).

## `AliasPath` and `AliasChoices`

??? api "API Documentation"

[`pydantic.aliases.AliasPath`][pydantic.aliases.AliasPath]<br>
[`pydantic.aliases.AliasChoices`][pydantic.aliases.AliasChoices]<br>

Pydantic provides two special types for convenience when using `validation_alias`: `AliasPath` and `AliasChoices`.

The `AliasPath` is used to specify a path to a field using aliases. For example:

```py lint="skip"
from pydantic import BaseModel, Field, AliasPath


class User(BaseModel):
first_name: str = Field(validation_alias=AliasPath('names', 0))
last_name: str = Field(validation_alias=AliasPath('names', 1))

user = User.model_validate({'names': ['John', 'Doe']}) # (1)!
print(user)
#> first_name='John' last_name='Doe'
```

1. We are using `model_validate` to validate a dictionary using the field aliases.

You can see more details about [`model_validate`][pydantic.main.BaseModel.model_validate] in the API reference.

In the `'first_name'` field, we are using the alias `'names'` and the index `0` to specify the path to the first name.
In the `'last_name'` field, we are using the alias `'names'` and the index `1` to specify the path to the last name.

`AliasChoices` is used to specify a choice of aliases. For example:

```py lint="skip"
from pydantic import BaseModel, Field, AliasChoices


class User(BaseModel):
first_name: str = Field(validation_alias=AliasChoices('first_name', 'fname'))
last_name: str = Field(validation_alias=AliasChoices('last_name', 'lname'))

user = User.model_validate({'fname': 'John', 'lname': 'Doe'}) # (1)!
print(user)
#> first_name='John' last_name='Doe'
user = User.model_validate({'first_name': 'John', 'lname': 'Doe'}) # (2)!
print(user)
#> first_name='John' last_name='Doe'
```

1. We are using the second alias choice for both fields.
2. We are using the first alias choice for the field `'first_name'` and the second alias choice
for the field `'last_name'`.

You can also use `AliasChoices` with `AliasPath`:

```py lint="skip"
from pydantic import BaseModel, Field, AliasPath, AliasChoices


class User(BaseModel):
first_name: str = Field(validation_alias=AliasChoices('first_name', AliasPath('names', 0)))
last_name: str = Field(validation_alias=AliasChoices('last_name', AliasPath('names', 1)))


user = User.model_validate({'first_name': 'John', 'last_name': 'Doe'})
print(user)
#> first_name='John' last_name='Doe'
user = User.model_validate({'names': ['John', 'Doe']})
print(user)
#> first_name='John' last_name='Doe'
user = User.model_validate({'names': ['John'], 'last_name': 'Doe'})
print(user)
#> first_name='John' last_name='Doe'
```

## Using alias generators

You can use the `alias_generator` parameter of [`Config`][pydantic.config.ConfigDict.alias_generator] to specify
a callable (or group of callables, via `AliasGenerator`) that will generate aliases for all fields in a model.
This is useful if you want to use a consistent naming convention for all fields in a model, but doin't

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't is misspelled

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@shaunpatterson feel free to create a PR with a fix, thanks!

want to specify the alias for each field individually.

Note:
Pydantic offers three built-in alias generators that you can use out of the box:

* [`to_pascal`][pydantic.alias_generators.to_pascal]
* [`to_camel`][pydantic.alias_generators.to_camel]
* [`to_snake`][pydantic.alias_generators.to_snake]

### Using a callable

Here's a basic example using a callable:

```py
from pydantic import BaseModel, ConfigDict


class Tree(BaseModel):
model_config = ConfigDict(
alias_generator=lambda field_name: field_name.upper()
)

age: int
height: float
kind: str


t = Tree.model_validate({'AGE': 12, 'HEIGHT': 1.2, 'KIND': 'oak'})
print(t.model_dump(by_alias=True))
#> {'AGE': 12, 'HEIGHT': 1.2, 'KIND': 'oak'}
```

### Using an `AliasGenerator`

??? api "API Documentation"

[`pydantic.aliases.AliasGenerator`][pydantic.aliases.AliasGenerator]<br>


`AliasGenerator` is a class that allows you to specify multiple alias generators for a model.
You can use an `AliasGenerator` to specify different alias generators for validation and serialization.

This is particularly useful if you need to use different naming conventions for loading and saving data,
but you don't want to specify the validation and serialization aliases for each field individually.

For example:

```py
from pydantic import AliasGenerator, BaseModel, ConfigDict


class Tree(BaseModel):
model_config = ConfigDict(
alias_generator=AliasGenerator(
validation_alias=lambda field_name: field_name.upper(),
serialization_alias=lambda field_name: field_name.title(),
)
)

age: int
height: float
kind: str


t = Tree.model_validate({'AGE': 12, 'HEIGHT': 1.2, 'KIND': 'oak'})
print(t.model_dump(by_alias=True))
#> {'Age': 12, 'Height': 1.2, 'Kind': 'oak'}
```

## Alias Precedence

Expand Down Expand Up @@ -39,7 +194,9 @@ You may set `alias_priority` on a field to change this behavior:

* `alias_priority=2` the alias will *not* be overridden by the alias generator.
* `alias_priority=1` the alias *will* be overridden by the alias generator.
* `alias_priority` not set, the alias will be overridden by the alias generator.
* `alias_priority` not set:
* alias is set: the alias will *not* be overridden by the alias generator.
* alias is not set: the alias *will* be overridden by the alias generator.

The same precedence applies to `validation_alias` and `serialization_alias`.
See more about the different field aliases under [field aliases](../concepts/fields.md#field-aliases).
78 changes: 3 additions & 75 deletions docs/concepts/fields.md
Expand Up @@ -73,7 +73,7 @@ The `alias` parameter is used for both validation _and_ serialization. If you wa
_different_ aliases for validation and serialization respectively, you can use the`validation_alias`
and `serialization_alias` parameters, which will apply only in their respective use cases.

Here is some example usage of the `alias` parameter:
Here is an example of using the `alias` parameter:

```py
from pydantic import BaseModel, Field
Expand Down Expand Up @@ -245,80 +245,7 @@ print(user.model_dump(by_alias=True)) # (2)!
[`@typing.dataclass_transform`](https://docs.python.org/3/library/typing.html#typing.dataclass_transform)
decorator, such as Pyright.

### `AliasPath` and `AliasChoices`

??? api "API Documentation"

[`pydantic.fields.AliasPath`][pydantic.fields.AliasPath]<br>
[`pydantic.fields.AliasChoices`][pydantic.fields.AliasChoices]<br>

Pydantic provides two special types for convenience when using `validation_alias`: `AliasPath` and `AliasChoices`.

The `AliasPath` is used to specify a path to a field using aliases. For example:

```py lint="skip"
from pydantic import BaseModel, Field, AliasPath


class User(BaseModel):
first_name: str = Field(validation_alias=AliasPath('names', 0))
last_name: str = Field(validation_alias=AliasPath('names', 1))

user = User.model_validate({'names': ['John', 'Doe']}) # (1)!
print(user)
#> first_name='John' last_name='Doe'
```

1. We are using `model_validate` to validate a dictionary using the field aliases.

You can see more details about [`model_validate`][pydantic.main.BaseModel.model_validate] in the API reference.

In the `'first_name'` field, we are using the alias `'names'` and the index `0` to specify the path to the first name.
In the `'last_name'` field, we are using the alias `'names'` and the index `1` to specify the path to the last name.

`AliasChoices` is used to specify a choice of aliases. For example:

```py lint="skip"
from pydantic import BaseModel, Field, AliasChoices


class User(BaseModel):
first_name: str = Field(validation_alias=AliasChoices('first_name', 'fname'))
last_name: str = Field(validation_alias=AliasChoices('last_name', 'lname'))

user = User.model_validate({'fname': 'John', 'lname': 'Doe'}) # (1)!
print(user)
#> first_name='John' last_name='Doe'
user = User.model_validate({'first_name': 'John', 'lname': 'Doe'}) # (2)!
print(user)
#> first_name='John' last_name='Doe'
```

1. We are using the second alias choice for both fields.
2. We are using the first alias choice for the field `'first_name'` and the second alias choice
for the field `'last_name'`.

You can also use `AliasChoices` with `AliasPath`:

```py lint="skip"
from pydantic import BaseModel, Field, AliasPath, AliasChoices


class User(BaseModel):
first_name: str = Field(validation_alias=AliasChoices('first_name', AliasPath('names', 0)))
last_name: str = Field(validation_alias=AliasChoices('last_name', AliasPath('names', 1)))


user = User.model_validate({'first_name': 'John', 'last_name': 'Doe'})
print(user)
#> first_name='John' last_name='Doe'
user = User.model_validate({'names': ['John', 'Doe']})
print(user)
#> first_name='John' last_name='Doe'
user = User.model_validate({'names': ['John'], 'last_name': 'Doe'})
print(user)
#> first_name='John' last_name='Doe'
```
For more information on alias usage, see the [Alias] concepts page.

## Numeric Constraints

Expand Down Expand Up @@ -816,3 +743,4 @@ print(b.model_dump())
[Serialization]: serialization.md#model-and-field-level-include-and-exclude
[Customizing JSON Schema]: json_schema.md#field-level-customization
[annotated]: https://docs.python.org/3/library/typing.html#typing.Annotated
[Alias]: ../concepts/alias.md
1 change: 1 addition & 0 deletions mkdocs.yml
Expand Up @@ -88,6 +88,7 @@ nav:
- TypeAdapter: api/type_adapter.md
- Validate Call: api/validate_call.md
- Fields: api/fields.md
- Aliases: api/aliases.md
- Configuration: api/config.md
- JSON Schema: api/json_schema.md
- Errors: api/errors.md
Expand Down
15 changes: 10 additions & 5 deletions pydantic/__init__.py
Expand Up @@ -17,10 +17,11 @@

from . import dataclasses
from ._internal._generate_schema import GenerateSchema as GenerateSchema
from .aliases import AliasChoices, AliasGenerator, AliasPath
from .annotated_handlers import GetCoreSchemaHandler, GetJsonSchemaHandler
from .config import ConfigDict
from .errors import *
from .fields import AliasChoices, AliasPath, Field, PrivateAttr, computed_field
from .fields import Field, PrivateAttr, computed_field
from .functional_serializers import (
PlainSerializer,
SerializeAsAny,
Expand Down Expand Up @@ -92,11 +93,13 @@
'PydanticUndefinedAnnotation',
'PydanticInvalidForJsonSchema',
# fields
'AliasPath',
'AliasChoices',
'Field',
'computed_field',
'PrivateAttr',
# alias
'AliasChoices',
'AliasGenerator',
'AliasPath',
# main
'BaseModel',
'create_model',
Expand Down Expand Up @@ -240,11 +243,13 @@
'PydanticUndefinedAnnotation': (__package__, '.errors'),
'PydanticInvalidForJsonSchema': (__package__, '.errors'),
# fields
'AliasPath': (__package__, '.fields'),
'AliasChoices': (__package__, '.fields'),
'Field': (__package__, '.fields'),
'computed_field': (__package__, '.fields'),
'PrivateAttr': (__package__, '.fields'),
# alias
'AliasChoices': (__package__, '.aliases'),
'AliasGenerator': (__package__, '.aliases'),
'AliasPath': (__package__, '.aliases'),
# main
'BaseModel': (__package__, '.main'),
'create_model': (__package__, '.main'),
Expand Down
3 changes: 2 additions & 1 deletion pydantic/_internal/_config.py
Expand Up @@ -15,6 +15,7 @@
Self,
)

from ..aliases import AliasGenerator
from ..config import ConfigDict, ExtraValues, JsonDict, JsonEncoder, JsonSchemaExtraCallable
from ..errors import PydanticUserError
from ..warnings import PydanticDeprecatedSince20
Expand Down Expand Up @@ -55,7 +56,7 @@ class ConfigWrapper:
# whether to use the actual key provided in the data (e.g. alias or first alias for "field required" errors) instead of field_names
# to construct error `loc`s, default `True`
loc_by_alias: bool
alias_generator: Callable[[str], str] | None
alias_generator: Callable[[str], str] | AliasGenerator | None
ignored_types: tuple[type, ...]
allow_inf_nan: bool
json_schema_extra: JsonDict | JsonSchemaExtraCallable | None
Expand Down