Skip to content

Commit a780908

Browse files
committed
starting on alias API unification
* replace populate_by_name with validate_by_name * enforce validate_by_alias in conjunction with validate_by_name (via get_lookup_key) * deprecate populate_by_name spec on model, dc, and td schemas in favor of access through the config setting
1 parent a0e60bd commit a780908

File tree

10 files changed

+287
-76
lines changed

10 files changed

+287
-76
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/lookup_key.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -577,3 +577,22 @@ fn py_get_attrs<'py>(obj: &Bound<'py, PyAny>, attr_name: &Py<PyString>) -> PyRes
577577
}
578578
}
579579
}
580+
581+
pub fn get_lookup_key(
582+
py: Python,
583+
validation_alias: Option<Bound<'_, PyAny>>,
584+
validate_by_name: bool,
585+
validate_by_alias: bool,
586+
field_name: &str,
587+
) -> PyResult<LookupKey> {
588+
let lookup_key = match (validation_alias, validate_by_name, validate_by_alias) {
589+
(Some(va), true, true) => LookupKey::from_py(py, &va, Some(field_name))?,
590+
(Some(_va), true, false) => LookupKey::from_string(py, field_name),
591+
(Some(va), false, true) => LookupKey::from_py(py, &va, None)?,
592+
(Some(_va), false, false) => {
593+
return py_schema_err!("`validate_by_name` and `validate_by_alias` cannot both be set to `False`.")
594+
}
595+
(None, _, _) => LookupKey::from_string(py, field_name),
596+
};
597+
Ok(lookup_key)
598+
}

src/validators/arguments.rs

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use crate::build_tools::py_schema_err;
1111
use crate::build_tools::{schema_or_config_same, ExtraBehavior};
1212
use crate::errors::{ErrorTypeDefaults, ValError, ValLineError, ValResult};
1313
use crate::input::{Arguments, BorrowInput, Input, KeywordArgs, PositionalArgs, ValidationMatch};
14-
use crate::lookup_key::LookupKey;
14+
use crate::lookup_key::{get_lookup_key, LookupKey};
1515
use crate::tools::SchemaDict;
1616

1717
use super::validation_state::ValidationState;
@@ -68,15 +68,17 @@ 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);
72-
7371
let arguments_schema: Bound<'_, PyList> = schema.get_as_req(intern!(py, "arguments_schema"))?;
7472
let mut parameters: Vec<Parameter> = Vec::with_capacity(arguments_schema.len());
7573

7674
let mut positional_params_count = 0;
7775
let mut had_default_arg = false;
7876
let mut had_keyword_only = false;
7977

78+
let validate_by_name = schema_or_config_same(schema, config, intern!(py, "validate_by_name"))?.unwrap_or(false);
79+
let validate_by_alias =
80+
schema_or_config_same(schema, config, intern!(py, "validate_by_alias"))?.unwrap_or(true);
81+
8082
for (arg_index, arg) in arguments_schema.iter().enumerate() {
8183
let arg = arg.downcast::<PyDict>()?;
8284

@@ -100,13 +102,14 @@ impl BuildValidator for ArgumentsValidator {
100102
let mut kw_lookup_key = None;
101103
let mut kwarg_key = None;
102104
if mode == "keyword_only" || mode == "positional_or_keyword" {
103-
kw_lookup_key = match arg.get_item(intern!(py, "alias"))? {
104-
Some(alias) => {
105-
let alt_alias = if populate_by_name { Some(name.as_str()) } else { None };
106-
Some(LookupKey::from_py(py, &alias, alt_alias)?)
107-
}
108-
None => Some(LookupKey::from_string(py, &name)),
109-
};
105+
let validation_alias = arg.get_item(intern!(py, "alias"))?;
106+
kw_lookup_key = Some(get_lookup_key(
107+
py,
108+
validation_alias,
109+
validate_by_name,
110+
validate_by_alias,
111+
name.as_str(),
112+
)?);
110113
kwarg_key = Some(py_name.unbind());
111114
}
112115

src/validators/dataclass.rs

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use crate::errors::{ErrorType, ErrorTypeDefaults, ValError, ValLineError, ValRes
1212
use crate::input::{
1313
input_as_python_instance, Arguments, BorrowInput, Input, InputType, KeywordArgs, PositionalArgs, ValidationMatch,
1414
};
15-
use crate::lookup_key::LookupKey;
15+
use crate::lookup_key::{get_lookup_key, LookupKey};
1616
use crate::tools::SchemaDict;
1717
use crate::validators::function::convert_err;
1818

@@ -54,8 +54,8 @@ 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);
58-
57+
let validate_by_name = config.get_as(intern!(py, "validate_by_name"))?.unwrap_or(false);
58+
let validate_by_alias = config.get_as(intern!(py, "validate_by_alias"))?.unwrap_or(true);
5959
let extra_behavior = ExtraBehavior::from_schema_or_config(py, schema, config, ExtraBehavior::Ignore)?;
6060

6161
let extras_validator = match (schema.get_item(intern!(py, "extras_schema"))?, &extra_behavior) {
@@ -75,13 +75,8 @@ impl BuildValidator for DataclassArgsValidator {
7575
let py_name: Bound<'_, PyString> = field.get_as_req(intern!(py, "name"))?;
7676
let name: String = py_name.extract()?;
7777

78-
let lookup_key = match field.get_item(intern!(py, "validation_alias"))? {
79-
Some(alias) => {
80-
let alt_alias = if populate_by_name { Some(name.as_str()) } else { None };
81-
LookupKey::from_py(py, &alias, alt_alias)?
82-
}
83-
None => LookupKey::from_string(py, &name),
84-
};
78+
let validation_alias = field.get_item(intern!(py, "validation_alias"))?;
79+
let lookup_key = get_lookup_key(py, validation_alias, validate_by_name, validate_by_alias, name.as_str())?;
8580

8681
let schema = field.get_as_req(intern!(py, "schema"))?;
8782

src/validators/model_fields.rs

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use crate::errors::LocItem;
1212
use crate::errors::{ErrorType, ErrorTypeDefaults, ValError, ValLineError, ValResult};
1313
use crate::input::ConsumeIterator;
1414
use crate::input::{BorrowInput, Input, ValidatedDict, ValidationMatch};
15-
use crate::lookup_key::LookupKey;
15+
use crate::lookup_key::{get_lookup_key, LookupKey};
1616
use crate::tools::SchemaDict;
1717

1818
use super::{build_validator, BuildValidator, CombinedValidator, DefinitionsBuilder, ValidationState, Validator};
@@ -51,7 +51,9 @@ 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+
55+
let validate_by_name = config.get_as(intern!(py, "validate_by_name"))?.unwrap_or(false);
56+
let validate_by_alias = config.get_as(intern!(py, "validate_by_alias"))?.unwrap_or(true);
5557

5658
let extra_behavior = ExtraBehavior::from_schema_or_config(py, schema, config, ExtraBehavior::Ignore)?;
5759

@@ -79,13 +81,8 @@ impl BuildValidator for ModelFieldsValidator {
7981
Err(err) => return py_schema_err!("Field \"{}\":\n {}", field_name, err),
8082
};
8183

82-
let lookup_key = match field_info.get_item(intern!(py, "validation_alias"))? {
83-
Some(alias) => {
84-
let alt_alias = if populate_by_name { Some(field_name) } else { None };
85-
LookupKey::from_py(py, &alias, alt_alias)?
86-
}
87-
None => LookupKey::from_string(py, field_name),
88-
};
84+
let validation_alias = field_info.get_item(intern!(py, "validation_alias"))?;
85+
let lookup_key = get_lookup_key(py, validation_alias, validate_by_name, validate_by_alias, field_name)?;
8986

9087
fields.push(Field {
9188
name: field_name.to_string(),

src/validators/typed_dict.rs

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@ 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;
1010
use crate::input::ConsumeIterator;
1111
use crate::input::ValidationMatch;
1212
use crate::input::{Input, ValidatedDict};
13-
use crate::lookup_key::LookupKey;
13+
use crate::lookup_key::{get_lookup_key, LookupKey};
1414
use crate::tools::SchemaDict;
1515
use ahash::AHashSet;
1616
use jiter::PartialMode;
@@ -55,7 +55,9 @@ 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+
59+
let validate_by_name = config.get_as(intern!(py, "validate_by_name"))?.unwrap_or(false);
60+
let validate_by_alias = config.get_as(intern!(py, "validate_by_alias"))?.unwrap_or(true);
5961

6062
let extra_behavior = ExtraBehavior::from_schema_or_config(py, schema, config, ExtraBehavior::Ignore)?;
6163

@@ -108,13 +110,8 @@ impl BuildValidator for TypedDictValidator {
108110
}
109111
}
110112

111-
let lookup_key = match field_info.get_item(intern!(py, "validation_alias"))? {
112-
Some(alias) => {
113-
let alt_alias = if populate_by_name { Some(field_name) } else { None };
114-
LookupKey::from_py(py, &alias, alt_alias)?
115-
}
116-
None => LookupKey::from_string(py, field_name),
117-
};
113+
let validation_alias = field_info.get_item(intern!(py, "validation_alias"))?;
114+
let lookup_key = get_lookup_key(py, validation_alias, validate_by_name, validate_by_alias, field_name)?;
118115

119116
fields.push(TypedDictField {
120117
name: field_name.to_string(),

0 commit comments

Comments
 (0)