Skip to content

Commit 8598f3e

Browse files
committed
starting on alias API unification
1 parent a0e60bd commit 8598f3e

File tree

8 files changed

+40
-46
lines changed

8 files changed

+40
-46
lines changed

python/pydantic_core/core_schema.py

Lines changed: 14 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,6 @@ class CoreConfig(TypedDict, total=False):
5454
`field_names` to construct error `loc`s. Default is `True`.
5555
revalidate_instances: Whether instances of models and dataclasses should re-validate. Default is 'never'.
5656
validate_default: Whether to validate default values during validation. Default is `False`.
57-
populate_by_name: Whether an aliased field may be populated by its name as given by the model attribute,
58-
as well as the alias. (Replaces 'allow_population_by_field_name' in Pydantic v1.) Default is `False`.
5957
str_max_length: The maximum length for string fields.
6058
str_min_length: The minimum length for string fields.
6159
str_strip_whitespace: Whether to strip whitespace from string fields.
@@ -74,6 +72,9 @@ class CoreConfig(TypedDict, total=False):
7472
regex_engine: The regex engine to use for regex pattern validation. Default is 'rust-regex'. See `StringSchema`.
7573
cache_strings: Whether to cache strings. Default is `True`, `True` or `'all'` is required to cache strings
7674
during general validation since validators don't know if they're in a key or a value.
75+
validate_by_alias: Whether to validate by alias. Default is `True`.
76+
validate_by_name: Whether to validate by attribute name. Default is `False`. Replacement for `populate_by_name`.
77+
serialize_by_alias: Whether to serialize by alias. Default is `False`, expected to change to `True` in V3.
7778
"""
7879

7980
title: str
@@ -91,7 +92,6 @@ class CoreConfig(TypedDict, total=False):
9192
# whether to validate default values during validation, default False
9293
validate_default: bool
9394
# used on typed-dicts and arguments
94-
populate_by_name: bool # replaces `allow_population_by_field_name` in pydantic v1
9595
# fields related to string fields only
9696
str_max_length: int
9797
str_min_length: int
@@ -111,6 +111,9 @@ class CoreConfig(TypedDict, total=False):
111111
coerce_numbers_to_str: bool # default: False
112112
regex_engine: Literal['rust-regex', 'python-re'] # default: 'rust-regex'
113113
cache_strings: Union[bool, Literal['all', 'keys', 'none']] # default: 'True'
114+
validate_by_alias: bool # default: True
115+
validate_by_name: bool # default: False
116+
serialize_by_alias: bool # default: False
114117

115118

116119
IncExCall: TypeAlias = 'set[int | str] | dict[int | str, IncExCall] | None'
@@ -2888,7 +2891,6 @@ class TypedDictSchema(TypedDict, total=False):
28882891
# all these values can be set via config, equivalent fields have `typed_dict_` prefix
28892892
extra_behavior: ExtraBehavior
28902893
total: bool # default: True
2891-
populate_by_name: bool # replaces `allow_population_by_field_name` in pydantic v1
28922894
ref: str
28932895
metadata: dict[str, Any]
28942896
serialization: SerSchema
@@ -2904,7 +2906,6 @@ def typed_dict_schema(
29042906
extras_schema: CoreSchema | None = None,
29052907
extra_behavior: ExtraBehavior | None = None,
29062908
total: bool | None = None,
2907-
populate_by_name: bool | None = None,
29082909
ref: str | None = None,
29092910
metadata: dict[str, Any] | None = None,
29102911
serialization: SerSchema | None = None,
@@ -2938,7 +2939,6 @@ class MyTypedDict(TypedDict):
29382939
metadata: Any other information you want to include with the schema, not used by pydantic-core
29392940
extra_behavior: The extra behavior to use for the typed dict
29402941
total: Whether the typed dict is total, otherwise uses `typed_dict_total` from config
2941-
populate_by_name: Whether the typed dict should populate by name
29422942
serialization: Custom serialization schema
29432943
"""
29442944
return _dict_not_none(
@@ -2950,7 +2950,6 @@ class MyTypedDict(TypedDict):
29502950
extras_schema=extras_schema,
29512951
extra_behavior=extra_behavior,
29522952
total=total,
2953-
populate_by_name=populate_by_name,
29542953
ref=ref,
29552954
metadata=metadata,
29562955
serialization=serialization,
@@ -3012,9 +3011,7 @@ class ModelFieldsSchema(TypedDict, total=False):
30123011
computed_fields: list[ComputedField]
30133012
strict: bool
30143013
extras_schema: CoreSchema
3015-
# all these values can be set via config, equivalent fields have `typed_dict_` prefix
30163014
extra_behavior: ExtraBehavior
3017-
populate_by_name: bool # replaces `allow_population_by_field_name` in pydantic v1
30183015
from_attributes: bool
30193016
ref: str
30203017
metadata: dict[str, Any]
@@ -3029,7 +3026,6 @@ def model_fields_schema(
30293026
strict: bool | None = None,
30303027
extras_schema: CoreSchema | None = None,
30313028
extra_behavior: ExtraBehavior | None = None,
3032-
populate_by_name: bool | None = None,
30333029
from_attributes: bool | None = None,
30343030
ref: str | None = None,
30353031
metadata: dict[str, Any] | None = None,
@@ -3058,7 +3054,6 @@ def model_fields_schema(
30583054
ref: optional unique identifier of the schema, used to reference the schema in other places
30593055
metadata: Any other information you want to include with the schema, not used by pydantic-core
30603056
extra_behavior: The extra behavior to use for the typed dict
3061-
populate_by_name: Whether the typed dict should populate by name
30623057
from_attributes: Whether the typed dict should be populated from attributes
30633058
serialization: Custom serialization schema
30643059
"""
@@ -3070,7 +3065,6 @@ def model_fields_schema(
30703065
strict=strict,
30713066
extras_schema=extras_schema,
30723067
extra_behavior=extra_behavior,
3073-
populate_by_name=populate_by_name,
30743068
from_attributes=from_attributes,
30753069
ref=ref,
30763070
metadata=metadata,
@@ -3254,7 +3248,6 @@ class DataclassArgsSchema(TypedDict, total=False):
32543248
dataclass_name: Required[str]
32553249
fields: Required[list[DataclassField]]
32563250
computed_fields: list[ComputedField]
3257-
populate_by_name: bool # default: False
32583251
collect_init_only: bool # default: False
32593252
ref: str
32603253
metadata: dict[str, Any]
@@ -3267,7 +3260,6 @@ def dataclass_args_schema(
32673260
fields: list[DataclassField],
32683261
*,
32693262
computed_fields: list[ComputedField] | None = None,
3270-
populate_by_name: bool | None = None,
32713263
collect_init_only: bool | None = None,
32723264
ref: str | None = None,
32733265
metadata: dict[str, Any] | None = None,
@@ -3295,7 +3287,6 @@ def dataclass_args_schema(
32953287
dataclass_name: The name of the dataclass being validated
32963288
fields: The fields to use for the dataclass
32973289
computed_fields: Computed fields to use when serializing the dataclass
3298-
populate_by_name: Whether to populate by name
32993290
collect_init_only: Whether to collect init only fields into a dict to pass to `__post_init__`
33003291
ref: optional unique identifier of the schema, used to reference the schema in other places
33013292
metadata: Any other information you want to include with the schema, not used by pydantic-core
@@ -3307,7 +3298,6 @@ def dataclass_args_schema(
33073298
dataclass_name=dataclass_name,
33083299
fields=fields,
33093300
computed_fields=computed_fields,
3310-
populate_by_name=populate_by_name,
33113301
collect_init_only=collect_init_only,
33123302
ref=ref,
33133303
metadata=metadata,
@@ -3436,7 +3426,8 @@ def arguments_parameter(
34363426
class ArgumentsSchema(TypedDict, total=False):
34373427
type: Required[Literal['arguments']]
34383428
arguments_schema: Required[list[ArgumentsParameter]]
3439-
populate_by_name: bool
3429+
validate_by_name: bool
3430+
validate_by_alias: bool
34403431
var_args_schema: CoreSchema
34413432
var_kwargs_mode: VarKwargsMode
34423433
var_kwargs_schema: CoreSchema
@@ -3448,7 +3439,8 @@ class ArgumentsSchema(TypedDict, total=False):
34483439
def arguments_schema(
34493440
arguments: list[ArgumentsParameter],
34503441
*,
3451-
populate_by_name: bool | None = None,
3442+
validate_by_name: bool | None = None,
3443+
validate_by_alias: bool | None = None,
34523444
var_args_schema: CoreSchema | None = None,
34533445
var_kwargs_mode: VarKwargsMode | None = None,
34543446
var_kwargs_schema: CoreSchema | None = None,
@@ -3475,7 +3467,8 @@ def arguments_schema(
34753467
34763468
Args:
34773469
arguments: The arguments to use for the arguments schema
3478-
populate_by_name: Whether to populate by name
3470+
validate_by_name: Whether to populate by argument names, defaults to False.
3471+
validate_by_alias: Whether to populate by argument aliases, defaults to True.
34793472
var_args_schema: The variable args schema to use for the arguments schema
34803473
var_kwargs_mode: The validation mode to use for variadic keyword arguments. If `'uniform'`, every value of the
34813474
keyword arguments will be validated against the `var_kwargs_schema` schema. If `'unpacked-typed-dict'`,
@@ -3488,7 +3481,8 @@ def arguments_schema(
34883481
return _dict_not_none(
34893482
type='arguments',
34903483
arguments_schema=arguments,
3491-
populate_by_name=populate_by_name,
3484+
validate_by_name=validate_by_name,
3485+
validate_by_alias=validate_by_alias,
34923486
var_args_schema=var_args_schema,
34933487
var_kwargs_mode=var_kwargs_mode,
34943488
var_kwargs_schema=var_kwargs_schema,

src/validators/arguments.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use ahash::AHashSet;
88
use pyo3::IntoPyObjectExt;
99

1010
use crate::build_tools::py_schema_err;
11-
use crate::build_tools::{schema_or_config_same, ExtraBehavior};
11+
use crate::build_tools::ExtraBehavior;
1212
use crate::errors::{ErrorTypeDefaults, ValError, ValLineError, ValResult};
1313
use crate::input::{Arguments, BorrowInput, Input, KeywordArgs, PositionalArgs, ValidationMatch};
1414
use crate::lookup_key::LookupKey;
@@ -68,7 +68,7 @@ impl BuildValidator for ArgumentsValidator {
6868
) -> PyResult<CombinedValidator> {
6969
let py = schema.py();
7070

71-
let populate_by_name = schema_or_config_same(schema, config, intern!(py, "populate_by_name"))?.unwrap_or(false);
71+
let validate_by_name = config.get_as(intern!(py, "validate_by_name"))?.unwrap_or(false);
7272

7373
let arguments_schema: Bound<'_, PyList> = schema.get_as_req(intern!(py, "arguments_schema"))?;
7474
let mut parameters: Vec<Parameter> = Vec::with_capacity(arguments_schema.len());
@@ -102,7 +102,7 @@ impl BuildValidator for ArgumentsValidator {
102102
if mode == "keyword_only" || mode == "positional_or_keyword" {
103103
kw_lookup_key = match arg.get_item(intern!(py, "alias"))? {
104104
Some(alias) => {
105-
let alt_alias = if populate_by_name { Some(name.as_str()) } else { None };
105+
let alt_alias = if validate_by_name { Some(name.as_str()) } else { None };
106106
Some(LookupKey::from_py(py, &alias, alt_alias)?)
107107
}
108108
None => Some(LookupKey::from_string(py, &name)),

src/validators/dataclass.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ impl BuildValidator for DataclassArgsValidator {
5454
) -> PyResult<CombinedValidator> {
5555
let py = schema.py();
5656

57-
let populate_by_name = schema_or_config_same(schema, config, intern!(py, "populate_by_name"))?.unwrap_or(false);
57+
let validate_by_name = config.get_as(intern!(py, "validate_by_name"))?.unwrap_or(false);
5858

5959
let extra_behavior = ExtraBehavior::from_schema_or_config(py, schema, config, ExtraBehavior::Ignore)?;
6060

@@ -77,7 +77,7 @@ impl BuildValidator for DataclassArgsValidator {
7777

7878
let lookup_key = match field.get_item(intern!(py, "validation_alias"))? {
7979
Some(alias) => {
80-
let alt_alias = if populate_by_name { Some(name.as_str()) } else { None };
80+
let alt_alias = if validate_by_name { Some(name.as_str()) } else { None };
8181
LookupKey::from_py(py, &alias, alt_alias)?
8282
}
8383
None => LookupKey::from_string(py, &name),

src/validators/model_fields.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ impl BuildValidator for ModelFieldsValidator {
5151
let strict = is_strict(schema, config)?;
5252

5353
let from_attributes = schema_or_config_same(schema, config, intern!(py, "from_attributes"))?.unwrap_or(false);
54-
let populate_by_name = schema_or_config_same(schema, config, intern!(py, "populate_by_name"))?.unwrap_or(false);
54+
let validate_by_name = config.get_as(intern!(py, "validate_by_name"))?.unwrap_or(false);
5555

5656
let extra_behavior = ExtraBehavior::from_schema_or_config(py, schema, config, ExtraBehavior::Ignore)?;
5757

@@ -81,7 +81,7 @@ impl BuildValidator for ModelFieldsValidator {
8181

8282
let lookup_key = match field_info.get_item(intern!(py, "validation_alias"))? {
8383
Some(alias) => {
84-
let alt_alias = if populate_by_name { Some(field_name) } else { None };
84+
let alt_alias = if validate_by_name { Some(field_name) } else { None };
8585
LookupKey::from_py(py, &alias, alt_alias)?
8686
}
8787
None => LookupKey::from_string(py, field_name),

src/validators/typed_dict.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use pyo3::prelude::*;
33
use pyo3::types::{PyDict, PyString};
44

55
use crate::build_tools::py_schema_err;
6-
use crate::build_tools::{is_strict, schema_or_config, schema_or_config_same, ExtraBehavior};
6+
use crate::build_tools::{is_strict, schema_or_config, ExtraBehavior};
77
use crate::errors::LocItem;
88
use crate::errors::{ErrorTypeDefaults, ValError, ValLineError, ValResult};
99
use crate::input::BorrowInput;
@@ -55,7 +55,7 @@ impl BuildValidator for TypedDictValidator {
5555

5656
let total =
5757
schema_or_config(schema, config, intern!(py, "total"), intern!(py, "typed_dict_total"))?.unwrap_or(true);
58-
let populate_by_name = schema_or_config_same(schema, config, intern!(py, "populate_by_name"))?.unwrap_or(false);
58+
let validate_by_name = config.get_as(intern!(py, "validate_by_name"))?.unwrap_or(false);
5959

6060
let extra_behavior = ExtraBehavior::from_schema_or_config(py, schema, config, ExtraBehavior::Ignore)?;
6161

@@ -110,7 +110,7 @@ impl BuildValidator for TypedDictValidator {
110110

111111
let lookup_key = match field_info.get_item(intern!(py, "validation_alias"))? {
112112
Some(alias) => {
113-
let alt_alias = if populate_by_name { Some(field_name) } else { None };
113+
let alt_alias = if validate_by_name { Some(field_name) } else { None };
114114
LookupKey::from_py(py, &alias, alt_alias)?
115115
}
116116
None => LookupKey::from_string(py, field_name),

tests/validators/test_arguments.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -883,14 +883,14 @@ def test_alias(py_and_json: PyAndJson, input_value, expected):
883883
],
884884
ids=repr,
885885
)
886-
def test_alias_populate_by_name(py_and_json: PyAndJson, input_value, expected):
886+
def test_alias_validate_by_name(py_and_json: PyAndJson, input_value, expected):
887887
v = py_and_json(
888888
{
889889
'type': 'arguments',
890890
'arguments_schema': [
891891
{'name': 'a', 'mode': 'positional_or_keyword', 'schema': {'type': 'int'}, 'alias': 'Foo'}
892892
],
893-
'populate_by_name': True,
893+
'validate_by_name': True,
894894
}
895895
)
896896
if isinstance(expected, Err):

tests/validators/test_model_fields.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -506,9 +506,9 @@ def test_alias_allow_pop(py_and_json: PyAndJson):
506506
v = py_and_json(
507507
{
508508
'type': 'model-fields',
509-
'populate_by_name': True,
510509
'fields': {'field_a': {'validation_alias': 'FieldA', 'type': 'model-field', 'schema': {'type': 'int'}}},
511-
}
510+
},
511+
config=CoreConfig(validate_by_name=True),
512512
)
513513
assert v.validate_test({'FieldA': '123'}) == ({'field_a': 123}, None, {'field_a'})
514514
assert v.validate_test({'field_a': '123'}) == ({'field_a': 123}, None, {'field_a'})
@@ -697,8 +697,8 @@ def test_paths_allow_by_name(py_and_json: PyAndJson, input_value):
697697
'schema': {'type': 'int'},
698698
}
699699
},
700-
'populate_by_name': True,
701-
}
700+
},
701+
config=CoreConfig(validate_by_name=True),
702702
)
703703
assert v.validate_test(input_value) == ({'field_a': 42}, None, {'field_a'})
704704

@@ -985,8 +985,8 @@ def test_from_attributes_by_name():
985985
core_schema.model_fields_schema(
986986
fields={'a': core_schema.model_field(schema=core_schema.int_schema(), validation_alias='a_alias')},
987987
from_attributes=True,
988-
populate_by_name=True,
989-
)
988+
),
989+
config=CoreConfig(validate_by_name=True),
990990
)
991991
assert v.validate_python(Cls(a_alias=1)) == ({'a': 1}, None, {'a'})
992992
assert v.validate_python(Cls(a=1)) == ({'a': 1}, None, {'a'})
@@ -1383,9 +1383,9 @@ def test_alias_extra_by_name(py_and_json: PyAndJson):
13831383
'type': 'model-fields',
13841384
'extra_behavior': 'allow',
13851385
'from_attributes': True,
1386-
'populate_by_name': True,
13871386
'fields': {'field_a': {'validation_alias': 'FieldA', 'type': 'model-field', 'schema': {'type': 'int'}}},
1388-
}
1387+
},
1388+
config=CoreConfig(validate_by_name=True),
13891389
)
13901390
assert v.validate_test({'FieldA': 1}) == ({'field_a': 1}, {}, {'field_a'})
13911391
assert v.validate_test({'field_a': 1}) == ({'field_a': 1}, {}, {'field_a'})

tests/validators/test_typed_dict.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -393,11 +393,11 @@ def test_alias_allow_pop(py_and_json: PyAndJson):
393393
v = py_and_json(
394394
{
395395
'type': 'typed-dict',
396-
'populate_by_name': True,
397396
'fields': {
398397
'field_a': {'validation_alias': 'FieldA', 'type': 'typed-dict-field', 'schema': {'type': 'int'}}
399398
},
400-
}
399+
},
400+
config=CoreConfig(validate_by_name=True),
401401
)
402402
assert v.validate_test({'FieldA': '123'}) == {'field_a': 123}
403403
assert v.validate_test({'field_a': '123'}) == {'field_a': 123}
@@ -590,8 +590,8 @@ def test_paths_allow_by_name(py_and_json: PyAndJson, input_value):
590590
'schema': {'type': 'int'},
591591
}
592592
},
593-
'populate_by_name': True,
594-
}
593+
},
594+
config=CoreConfig(validate_by_name=True),
595595
)
596596
assert v.validate_test(input_value) == {'field_a': 42}
597597

@@ -795,11 +795,11 @@ def test_alias_extra_by_name(py_and_json: PyAndJson):
795795
{
796796
'type': 'typed-dict',
797797
'extra_behavior': 'allow',
798-
'populate_by_name': True,
799798
'fields': {
800799
'field_a': {'validation_alias': 'FieldA', 'type': 'typed-dict-field', 'schema': {'type': 'int'}}
801800
},
802-
}
801+
},
802+
config=CoreConfig(validate_by_name=True),
803803
)
804804
assert v.validate_test({'FieldA': 1}) == {'field_a': 1}
805805
assert v.validate_test({'field_a': 1}) == {'field_a': 1}

0 commit comments

Comments
 (0)