diff --git a/changes/1397-AlexECX.md b/changes/1397-AlexECX.md new file mode 100644 index 0000000..1377f8a --- /dev/null +++ b/changes/1397-AlexECX.md @@ -0,0 +1 @@ +Always use a field's real name with includes/excludes in `model._iter()`, regardless of `by_alias`. \ No newline at end of file diff --git a/pydantic/main.py b/pydantic/main.py index cd33fd4..04e9d26 100644 --- a/pydantic/main.py +++ b/pydantic/main.py @@ -690,27 +690,29 @@ class BaseModel(Representation, metaclass=ModelMetaclass): value_exclude = ValueItems(self, exclude) if exclude else None value_include = ValueItems(self, include) if include else None - for k, v in self.__dict__.items(): + for field_key, v in self.__dict__.items(): if ( - (allowed_keys is not None and k not in allowed_keys) + (allowed_keys is not None and field_key not in allowed_keys) or (exclude_none and v is None) - or (exclude_defaults and self.__field_defaults__.get(k, _missing) == v) + or (exclude_defaults and self.__field_defaults__.get(field_key, _missing) == v) ): continue - if by_alias and k in self.__fields__: - k = self.__fields__[k].alias + if by_alias and field_key in self.__fields__: + dict_key = self.__fields__[field_key].alias + else: + dict_key = field_key if to_dict or value_include or value_exclude: v = self._get_value( v, to_dict=to_dict, by_alias=by_alias, - include=value_include and value_include.for_element(k), - exclude=value_exclude and value_exclude.for_element(k), + include=value_include and value_include.for_element(field_key), + exclude=value_exclude and value_exclude.for_element(field_key), exclude_unset=exclude_unset, exclude_defaults=exclude_defaults, exclude_none=exclude_none, ) - yield k, v + yield dict_key, v def _calculate_keys( self, diff --git a/tests/test_edge_cases.py b/tests/test_edge_cases.py index 38b6391..d7078d3 100644 --- a/tests/test_edge_cases.py +++ b/tests/test_edge_cases.py @@ -436,7 +436,35 @@ def test_advanced_exclude(): assert m.dict(exclude={'e': ..., 'f': {'d'}}) == {'f': {'c': 'foo'}} -def test_advanced_value_inclide(): +def test_advanced_exclude_by_alias(): + class SubSubModel(BaseModel): + a: str + aliased_b: str = Field(..., alias='b_alias') + + class SubModel(BaseModel): + aliased_c: str = Field(..., alias='c_alias') + aliased_d: List[SubSubModel] = Field(..., alias='d_alias') + + class Model(BaseModel): + aliased_e: str = Field(..., alias='e_alias') + aliased_f: SubModel = Field(..., alias='f_alias') + + m = Model( + e_alias='e', + f_alias=SubModel(c_alias='foo', d_alias=[SubSubModel(a='a', b_alias='b'), SubSubModel(a='c', b_alias='e')]), + ) + + excludes = {'aliased_f': {'aliased_c': ..., 'aliased_d': {-1: {'a'}}}} + assert m.dict(exclude=excludes, by_alias=True) == { + 'e_alias': 'e', + 'f_alias': {'d_alias': [{'a': 'a', 'b_alias': 'b'}, {'b_alias': 'e'}]}, + } + + excludes = {'aliased_e': ..., 'aliased_f': {'aliased_d'}} + assert m.dict(exclude=excludes, by_alias=True) == {'f_alias': {'c_alias': 'foo'}} + + +def test_advanced_value_include(): class SubSubModel(BaseModel): a: str b: str