Skip to content

Commit

Permalink
starting on alias API unification
Browse files Browse the repository at this point in the history
  • Loading branch information
sydney-runkle committed Feb 19, 2025
1 parent a0e60bd commit 8598f3e
Show file tree
Hide file tree
Showing 8 changed files with 40 additions and 46 deletions.
34 changes: 14 additions & 20 deletions python/pydantic_core/core_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,6 @@ class CoreConfig(TypedDict, total=False):
`field_names` to construct error `loc`s. Default is `True`.
revalidate_instances: Whether instances of models and dataclasses should re-validate. Default is 'never'.
validate_default: Whether to validate default values during validation. Default is `False`.
populate_by_name: Whether an aliased field may be populated by its name as given by the model attribute,
as well as the alias. (Replaces 'allow_population_by_field_name' in Pydantic v1.) Default is `False`.
str_max_length: The maximum length for string fields.
str_min_length: The minimum length for string fields.
str_strip_whitespace: Whether to strip whitespace from string fields.
Expand All @@ -74,6 +72,9 @@ class CoreConfig(TypedDict, total=False):
regex_engine: The regex engine to use for regex pattern validation. Default is 'rust-regex'. See `StringSchema`.
cache_strings: Whether to cache strings. Default is `True`, `True` or `'all'` is required to cache strings
during general validation since validators don't know if they're in a key or a value.
validate_by_alias: Whether to validate by alias. Default is `True`.
validate_by_name: Whether to validate by attribute name. Default is `False`. Replacement for `populate_by_name`.
serialize_by_alias: Whether to serialize by alias. Default is `False`, expected to change to `True` in V3.
"""

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


IncExCall: TypeAlias = 'set[int | str] | dict[int | str, IncExCall] | None'
Expand Down Expand Up @@ -2888,7 +2891,6 @@ class TypedDictSchema(TypedDict, total=False):
# all these values can be set via config, equivalent fields have `typed_dict_` prefix
extra_behavior: ExtraBehavior
total: bool # default: True
populate_by_name: bool # replaces `allow_population_by_field_name` in pydantic v1
ref: str
metadata: dict[str, Any]
serialization: SerSchema
Expand All @@ -2904,7 +2906,6 @@ def typed_dict_schema(
extras_schema: CoreSchema | None = None,
extra_behavior: ExtraBehavior | None = None,
total: bool | None = None,
populate_by_name: bool | None = None,
ref: str | None = None,
metadata: dict[str, Any] | None = None,
serialization: SerSchema | None = None,
Expand Down Expand Up @@ -2938,7 +2939,6 @@ class MyTypedDict(TypedDict):
metadata: Any other information you want to include with the schema, not used by pydantic-core
extra_behavior: The extra behavior to use for the typed dict
total: Whether the typed dict is total, otherwise uses `typed_dict_total` from config
populate_by_name: Whether the typed dict should populate by name
serialization: Custom serialization schema
"""
return _dict_not_none(
Expand All @@ -2950,7 +2950,6 @@ class MyTypedDict(TypedDict):
extras_schema=extras_schema,
extra_behavior=extra_behavior,
total=total,
populate_by_name=populate_by_name,
ref=ref,
metadata=metadata,
serialization=serialization,
Expand Down Expand Up @@ -3012,9 +3011,7 @@ class ModelFieldsSchema(TypedDict, total=False):
computed_fields: list[ComputedField]
strict: bool
extras_schema: CoreSchema
# all these values can be set via config, equivalent fields have `typed_dict_` prefix
extra_behavior: ExtraBehavior
populate_by_name: bool # replaces `allow_population_by_field_name` in pydantic v1
from_attributes: bool
ref: str
metadata: dict[str, Any]
Expand All @@ -3029,7 +3026,6 @@ def model_fields_schema(
strict: bool | None = None,
extras_schema: CoreSchema | None = None,
extra_behavior: ExtraBehavior | None = None,
populate_by_name: bool | None = None,
from_attributes: bool | None = None,
ref: str | None = None,
metadata: dict[str, Any] | None = None,
Expand Down Expand Up @@ -3058,7 +3054,6 @@ def model_fields_schema(
ref: optional unique identifier of the schema, used to reference the schema in other places
metadata: Any other information you want to include with the schema, not used by pydantic-core
extra_behavior: The extra behavior to use for the typed dict
populate_by_name: Whether the typed dict should populate by name
from_attributes: Whether the typed dict should be populated from attributes
serialization: Custom serialization schema
"""
Expand All @@ -3070,7 +3065,6 @@ def model_fields_schema(
strict=strict,
extras_schema=extras_schema,
extra_behavior=extra_behavior,
populate_by_name=populate_by_name,
from_attributes=from_attributes,
ref=ref,
metadata=metadata,
Expand Down Expand Up @@ -3254,7 +3248,6 @@ class DataclassArgsSchema(TypedDict, total=False):
dataclass_name: Required[str]
fields: Required[list[DataclassField]]
computed_fields: list[ComputedField]
populate_by_name: bool # default: False
collect_init_only: bool # default: False
ref: str
metadata: dict[str, Any]
Expand All @@ -3267,7 +3260,6 @@ def dataclass_args_schema(
fields: list[DataclassField],
*,
computed_fields: list[ComputedField] | None = None,
populate_by_name: bool | None = None,
collect_init_only: bool | None = None,
ref: str | None = None,
metadata: dict[str, Any] | None = None,
Expand Down Expand Up @@ -3295,7 +3287,6 @@ def dataclass_args_schema(
dataclass_name: The name of the dataclass being validated
fields: The fields to use for the dataclass
computed_fields: Computed fields to use when serializing the dataclass
populate_by_name: Whether to populate by name
collect_init_only: Whether to collect init only fields into a dict to pass to `__post_init__`
ref: optional unique identifier of the schema, used to reference the schema in other places
metadata: Any other information you want to include with the schema, not used by pydantic-core
Expand All @@ -3307,7 +3298,6 @@ def dataclass_args_schema(
dataclass_name=dataclass_name,
fields=fields,
computed_fields=computed_fields,
populate_by_name=populate_by_name,
collect_init_only=collect_init_only,
ref=ref,
metadata=metadata,
Expand Down Expand Up @@ -3436,7 +3426,8 @@ def arguments_parameter(
class ArgumentsSchema(TypedDict, total=False):
type: Required[Literal['arguments']]
arguments_schema: Required[list[ArgumentsParameter]]
populate_by_name: bool
validate_by_name: bool
validate_by_alias: bool
var_args_schema: CoreSchema
var_kwargs_mode: VarKwargsMode
var_kwargs_schema: CoreSchema
Expand All @@ -3448,7 +3439,8 @@ class ArgumentsSchema(TypedDict, total=False):
def arguments_schema(
arguments: list[ArgumentsParameter],
*,
populate_by_name: bool | None = None,
validate_by_name: bool | None = None,
validate_by_alias: bool | None = None,
var_args_schema: CoreSchema | None = None,
var_kwargs_mode: VarKwargsMode | None = None,
var_kwargs_schema: CoreSchema | None = None,
Expand All @@ -3475,7 +3467,8 @@ def arguments_schema(
Args:
arguments: The arguments to use for the arguments schema
populate_by_name: Whether to populate by name
validate_by_name: Whether to populate by argument names, defaults to False.
validate_by_alias: Whether to populate by argument aliases, defaults to True.
var_args_schema: The variable args schema to use for the arguments schema
var_kwargs_mode: The validation mode to use for variadic keyword arguments. If `'uniform'`, every value of the
keyword arguments will be validated against the `var_kwargs_schema` schema. If `'unpacked-typed-dict'`,
Expand All @@ -3488,7 +3481,8 @@ def arguments_schema(
return _dict_not_none(
type='arguments',
arguments_schema=arguments,
populate_by_name=populate_by_name,
validate_by_name=validate_by_name,
validate_by_alias=validate_by_alias,
var_args_schema=var_args_schema,
var_kwargs_mode=var_kwargs_mode,
var_kwargs_schema=var_kwargs_schema,
Expand Down
6 changes: 3 additions & 3 deletions src/validators/arguments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use ahash::AHashSet;
use pyo3::IntoPyObjectExt;

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

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

let arguments_schema: Bound<'_, PyList> = schema.get_as_req(intern!(py, "arguments_schema"))?;
let mut parameters: Vec<Parameter> = Vec::with_capacity(arguments_schema.len());
Expand Down Expand Up @@ -102,7 +102,7 @@ impl BuildValidator for ArgumentsValidator {
if mode == "keyword_only" || mode == "positional_or_keyword" {
kw_lookup_key = match arg.get_item(intern!(py, "alias"))? {
Some(alias) => {
let alt_alias = if populate_by_name { Some(name.as_str()) } else { None };
let alt_alias = if validate_by_name { Some(name.as_str()) } else { None };
Some(LookupKey::from_py(py, &alias, alt_alias)?)
}
None => Some(LookupKey::from_string(py, &name)),
Expand Down
4 changes: 2 additions & 2 deletions src/validators/dataclass.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ impl BuildValidator for DataclassArgsValidator {
) -> PyResult<CombinedValidator> {
let py = schema.py();

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

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

Expand All @@ -77,7 +77,7 @@ impl BuildValidator for DataclassArgsValidator {

let lookup_key = match field.get_item(intern!(py, "validation_alias"))? {
Some(alias) => {
let alt_alias = if populate_by_name { Some(name.as_str()) } else { None };
let alt_alias = if validate_by_name { Some(name.as_str()) } else { None };
LookupKey::from_py(py, &alias, alt_alias)?
}
None => LookupKey::from_string(py, &name),
Expand Down
4 changes: 2 additions & 2 deletions src/validators/model_fields.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ impl BuildValidator for ModelFieldsValidator {
let strict = is_strict(schema, config)?;

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

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

Expand Down Expand Up @@ -81,7 +81,7 @@ impl BuildValidator for ModelFieldsValidator {

let lookup_key = match field_info.get_item(intern!(py, "validation_alias"))? {
Some(alias) => {
let alt_alias = if populate_by_name { Some(field_name) } else { None };
let alt_alias = if validate_by_name { Some(field_name) } else { None };
LookupKey::from_py(py, &alias, alt_alias)?
}
None => LookupKey::from_string(py, field_name),
Expand Down
6 changes: 3 additions & 3 deletions src/validators/typed_dict.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use pyo3::prelude::*;
use pyo3::types::{PyDict, PyString};

use crate::build_tools::py_schema_err;
use crate::build_tools::{is_strict, schema_or_config, schema_or_config_same, ExtraBehavior};
use crate::build_tools::{is_strict, schema_or_config, ExtraBehavior};
use crate::errors::LocItem;
use crate::errors::{ErrorTypeDefaults, ValError, ValLineError, ValResult};
use crate::input::BorrowInput;
Expand Down Expand Up @@ -55,7 +55,7 @@ impl BuildValidator for TypedDictValidator {

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

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

Expand Down Expand Up @@ -110,7 +110,7 @@ impl BuildValidator for TypedDictValidator {

let lookup_key = match field_info.get_item(intern!(py, "validation_alias"))? {
Some(alias) => {
let alt_alias = if populate_by_name { Some(field_name) } else { None };
let alt_alias = if validate_by_name { Some(field_name) } else { None };
LookupKey::from_py(py, &alias, alt_alias)?
}
None => LookupKey::from_string(py, field_name),
Expand Down
4 changes: 2 additions & 2 deletions tests/validators/test_arguments.py
Original file line number Diff line number Diff line change
Expand Up @@ -883,14 +883,14 @@ def test_alias(py_and_json: PyAndJson, input_value, expected):
],
ids=repr,
)
def test_alias_populate_by_name(py_and_json: PyAndJson, input_value, expected):
def test_alias_validate_by_name(py_and_json: PyAndJson, input_value, expected):
v = py_and_json(
{
'type': 'arguments',
'arguments_schema': [
{'name': 'a', 'mode': 'positional_or_keyword', 'schema': {'type': 'int'}, 'alias': 'Foo'}
],
'populate_by_name': True,
'validate_by_name': True,
}
)
if isinstance(expected, Err):
Expand Down
16 changes: 8 additions & 8 deletions tests/validators/test_model_fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -506,9 +506,9 @@ def test_alias_allow_pop(py_and_json: PyAndJson):
v = py_and_json(
{
'type': 'model-fields',
'populate_by_name': True,
'fields': {'field_a': {'validation_alias': 'FieldA', 'type': 'model-field', 'schema': {'type': 'int'}}},
}
},
config=CoreConfig(validate_by_name=True),
)
assert v.validate_test({'FieldA': '123'}) == ({'field_a': 123}, None, {'field_a'})
assert v.validate_test({'field_a': '123'}) == ({'field_a': 123}, None, {'field_a'})
Expand Down Expand Up @@ -697,8 +697,8 @@ def test_paths_allow_by_name(py_and_json: PyAndJson, input_value):
'schema': {'type': 'int'},
}
},
'populate_by_name': True,
}
},
config=CoreConfig(validate_by_name=True),
)
assert v.validate_test(input_value) == ({'field_a': 42}, None, {'field_a'})

Expand Down Expand Up @@ -985,8 +985,8 @@ def test_from_attributes_by_name():
core_schema.model_fields_schema(
fields={'a': core_schema.model_field(schema=core_schema.int_schema(), validation_alias='a_alias')},
from_attributes=True,
populate_by_name=True,
)
),
config=CoreConfig(validate_by_name=True),
)
assert v.validate_python(Cls(a_alias=1)) == ({'a': 1}, None, {'a'})
assert v.validate_python(Cls(a=1)) == ({'a': 1}, None, {'a'})
Expand Down Expand Up @@ -1383,9 +1383,9 @@ def test_alias_extra_by_name(py_and_json: PyAndJson):
'type': 'model-fields',
'extra_behavior': 'allow',
'from_attributes': True,
'populate_by_name': True,
'fields': {'field_a': {'validation_alias': 'FieldA', 'type': 'model-field', 'schema': {'type': 'int'}}},
}
},
config=CoreConfig(validate_by_name=True),
)
assert v.validate_test({'FieldA': 1}) == ({'field_a': 1}, {}, {'field_a'})
assert v.validate_test({'field_a': 1}) == ({'field_a': 1}, {}, {'field_a'})
Expand Down
12 changes: 6 additions & 6 deletions tests/validators/test_typed_dict.py
Original file line number Diff line number Diff line change
Expand Up @@ -393,11 +393,11 @@ def test_alias_allow_pop(py_and_json: PyAndJson):
v = py_and_json(
{
'type': 'typed-dict',
'populate_by_name': True,
'fields': {
'field_a': {'validation_alias': 'FieldA', 'type': 'typed-dict-field', 'schema': {'type': 'int'}}
},
}
},
config=CoreConfig(validate_by_name=True),
)
assert v.validate_test({'FieldA': '123'}) == {'field_a': 123}
assert v.validate_test({'field_a': '123'}) == {'field_a': 123}
Expand Down Expand Up @@ -590,8 +590,8 @@ def test_paths_allow_by_name(py_and_json: PyAndJson, input_value):
'schema': {'type': 'int'},
}
},
'populate_by_name': True,
}
},
config=CoreConfig(validate_by_name=True),
)
assert v.validate_test(input_value) == {'field_a': 42}

Expand Down Expand Up @@ -795,11 +795,11 @@ def test_alias_extra_by_name(py_and_json: PyAndJson):
{
'type': 'typed-dict',
'extra_behavior': 'allow',
'populate_by_name': True,
'fields': {
'field_a': {'validation_alias': 'FieldA', 'type': 'typed-dict-field', 'schema': {'type': 'int'}}
},
}
},
config=CoreConfig(validate_by_name=True),
)
assert v.validate_test({'FieldA': 1}) == {'field_a': 1}
assert v.validate_test({'field_a': 1}) == {'field_a': 1}
Expand Down

0 comments on commit 8598f3e

Please sign in to comment.