From abd773b5a5da257ec95da61935486d8d74a95fa6 Mon Sep 17 00:00:00 2001 From: MrMrRobat Date: Sat, 3 Aug 2019 17:19:29 +0300 Subject: [PATCH 1/7] Rename BaseModel.__values__ to BaseModel.__dict__, remove BaseModel.__getattr__ --- pydantic/main.py | 39 ++++++++++++++++----------------------- 1 file changed, 16 insertions(+), 23 deletions(-) diff --git a/pydantic/main.py b/pydantic/main.py index 624ba2a..5de635d 100644 --- a/pydantic/main.py +++ b/pydantic/main.py @@ -266,24 +266,17 @@ class BaseModel(metaclass=MetaModel): _custom_root_type: bool = False Config = BaseConfig - __slots__ = ('__values__', '__fields_set__') + __slots__ = ('__dict__', '__fields_set__') def __init__(__pydantic_self__, **data: Any) -> None: # Uses something other than `self` the first arg to allow "self" as a settable attribute if TYPE_CHECKING: # pragma: no cover - __pydantic_self__.__values__: Dict[str, Any] = {} + __pydantic_self__.__dict__: Dict[str, Any] = {} __pydantic_self__.__fields_set__: 'SetStr' = set() values, fields_set, _ = validate_model(__pydantic_self__, data) - object.__setattr__(__pydantic_self__, '__values__', values) + object.__setattr__(__pydantic_self__, '__dict__', values) object.__setattr__(__pydantic_self__, '__fields_set__', fields_set) - @no_type_check - def __getattr__(self, name): - try: - return self.__values__[name] - except KeyError: - raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'") - @no_type_check def __setattr__(self, name, value): if self.__config__.extra is not Extra.allow and name not in self.__fields__: @@ -295,17 +288,17 @@ class BaseModel(metaclass=MetaModel): if error_: raise ValidationError([error_]) else: - self.__values__[name] = value_ + self.__dict__[name] = value_ self.__fields_set__.add(name) else: - self.__values__[name] = value + self.__dict__[name] = value self.__fields_set__.add(name) def __getstate__(self) -> 'DictAny': - return {'__values__': self.__values__, '__fields_set__': self.__fields_set__} + return {'__dict__': self.__dict__, '__fields_set__': self.__fields_set__} def __setstate__(self, state: 'DictAny') -> None: - object.__setattr__(self, '__values__', state['__values__']) + object.__setattr__(self, '__dict__', state['__dict__']) object.__setattr__(self, '__fields_set__', state['__fields_set__']) def dict( @@ -413,18 +406,18 @@ class BaseModel(metaclass=MetaModel): obj = cls._decompose_class(obj) m = cls.__new__(cls) values, fields_set, _ = validate_model(m, obj) - object.__setattr__(m, '__values__', values) + object.__setattr__(m, '__dict__', values) object.__setattr__(m, '__fields_set__', fields_set) return m @classmethod def construct(cls: Type['Model'], values: 'DictAny', fields_set: 'SetStr') -> 'Model': """ - Creates a new model and set __values__ without any validation, thus values should already be trusted. + Creates a new model and set __dict__ without any validation, thus values should already be trusted. Chances are you don't want to use this method directly. """ m = cls.__new__(cls) - object.__setattr__(m, '__values__', values) + object.__setattr__(m, '__dict__', values) object.__setattr__(m, '__fields_set__', fields_set) return m @@ -448,11 +441,11 @@ class BaseModel(metaclass=MetaModel): """ if include is None and exclude is None and update is None: # skip constructing values if no arguments are passed - v = self.__values__ + v = self.__dict__ else: allowed_keys = self._calculate_keys(include=include, exclude=exclude, skip_defaults=False, update=update) if allowed_keys is None: - v = {**self.__values__, **(update or {})} + v = {**self.__dict__, **(update or {})} else: v = { **dict( @@ -595,7 +588,7 @@ class BaseModel(metaclass=MetaModel): value_exclude = ValueItems(self, exclude) if exclude else None value_include = ValueItems(self, include) if include else None - for k, v in self.__values__.items(): + for k, v in self.__dict__.items(): if allowed_keys is None or k in allowed_keys: yield k, self._get_value( v, @@ -619,7 +612,7 @@ class BaseModel(metaclass=MetaModel): if skip_defaults: keys = self.__fields_set__.copy() else: - keys = set(self.__values__.keys()) + keys = set(self.__dict__.keys()) if include is not None: if isinstance(include, dict): @@ -652,7 +645,7 @@ class BaseModel(metaclass=MetaModel): return '{}{}{}'.format( self.__class__.__name__, divider, - divider.join('{}={}'.format(k, truncate(v)) for k, v in self.__values__.items()), + divider.join('{}={}'.format(k, truncate(v)) for k, v in self.__dict__.items()), ) def __str__(self) -> str: @@ -660,7 +653,7 @@ class BaseModel(metaclass=MetaModel): def __dir__(self) -> 'ListStr': ret = list(object.__dir__(self)) - ret.extend(self.__values__.keys()) + ret.extend(self.__dict__.keys()) return ret From bbf0b4bfbe347fa82fe3f6ca38a4028cdd2230cc Mon Sep 17 00:00:00 2001 From: MrMrRobat Date: Sat, 3 Aug 2019 18:05:17 +0300 Subject: [PATCH 2/7] Add __values__ property with deprecation warning and test for it --- pydantic/main.py | 5 +++++ tests/test_edge_cases.py | 10 ++++++++++ 2 files changed, 15 insertions(+) diff --git a/pydantic/main.py b/pydantic/main.py index 5de635d..0b44b0e 100644 --- a/pydantic/main.py +++ b/pydantic/main.py @@ -656,6 +656,11 @@ class BaseModel(metaclass=MetaModel): ret.extend(self.__dict__.keys()) return ret + @property + def __values__(self) -> 'DictStrAny': + warnings.warn('`__values__` attribute is deprecated, use `__dict__` instead', DeprecationWarning) + return self.__dict__ + def create_model( model_name: str, diff --git a/tests/test_edge_cases.py b/tests/test_edge_cases.py index eaf2500..e2b313f 100644 --- a/tests/test_edge_cases.py +++ b/tests/test_edge_cases.py @@ -995,3 +995,13 @@ def test_nested_init(model): assert m.self == 'Top Model' assert m.nest.self == 'Nested Model' assert m.nest.modified_number == 1 + + +def test_values_attr_deprecation(): + class Model(BaseModel): + foo: int + bar: str + + m = Model(foo=4, bar='baz') + with pytest.warns(DeprecationWarning, match='`__values__` attribute is deprecated, use `__dict__` instead'): + assert m.__values__ == m.__dict__ From 1d766057364eff240b05aabac19acff078bc16cf Mon Sep 17 00:00:00 2001 From: MrMrRobat Date: Sat, 3 Aug 2019 18:42:41 +0300 Subject: [PATCH 3/7] Update history --- HISTORY.rst | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/HISTORY.rst b/HISTORY.rst index a48109c..4adb551 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -3,6 +3,13 @@ History ------- +v0.32 (unreleased) +.................. +* **breaking change**: remove ``__getattr__`` on ``BaseModel``, #712 by @MrMrRobat +* rename ``__values__`` to ``__dict__`` on ``BaseModel``, deprecation warning on use of old name, + attributes access speed increased up to 14 times, #712 by @MrMrRobat + + v0.31.1 (2019-07-31) .................... * fix json generation for ``EnumError``, #697 by @dmontagu @@ -137,7 +144,7 @@ v0.20.0a1 (2019-02-13) * **breaking change** (maybe): more sophisticated argument parsing for validators, any subset of ``values``, ``config`` and ``field`` is now permitted, eg. ``(cls, value, field)``, however the variadic key word argument ("``**kwargs``") **must** be called ``kwargs``, #388 by @samuelcolvin -* **breaking change**: Adds ``skip_defaults`` argument to ``BaseModel.dict()`` to allow skipping of fields that +* **breaking change**: Adds ``skip_defaults`` argument to ``l.dict()`` to allow skipping of fields that were not explicitly set, signature of ``Model.construct()`` changed, #389 by @dgasmith * add ``py.typed`` marker file for PEP-561 support, #391 by @je-l * Fix ``extra`` behaviour for multiple inheritance/mix-ins, #394 by @YaraslauZhylko From 33ef9ce1f28c76579be51ced711f1ed83625b4bb Mon Sep 17 00:00:00 2001 From: Arseny Boykov <36469655+MrMrRobat@users.noreply.github.com> Date: Sat, 3 Aug 2019 22:48:28 +0300 Subject: [PATCH 4/7] Update HISTORY.rst --- HISTORY.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HISTORY.rst b/HISTORY.rst index 4adb551..632c263 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -144,7 +144,7 @@ v0.20.0a1 (2019-02-13) * **breaking change** (maybe): more sophisticated argument parsing for validators, any subset of ``values``, ``config`` and ``field`` is now permitted, eg. ``(cls, value, field)``, however the variadic key word argument ("``**kwargs``") **must** be called ``kwargs``, #388 by @samuelcolvin -* **breaking change**: Adds ``skip_defaults`` argument to ``l.dict()`` to allow skipping of fields that +* **breaking change**: Adds ``skip_defaults`` argument to ``BaseModel.dict()`` to allow skipping of fields that were not explicitly set, signature of ``Model.construct()`` changed, #389 by @dgasmith * add ``py.typed`` marker file for PEP-561 support, #391 by @je-l * Fix ``extra`` behaviour for multiple inheritance/mix-ins, #394 by @YaraslauZhylko From 7f87dda20e4cd83a481e971d6dc7537e613ead0e Mon Sep 17 00:00:00 2001 From: MrMrRobat Date: Sun, 4 Aug 2019 01:34:44 +0300 Subject: [PATCH 5/7] Remove redundant __dir__ method --- pydantic/main.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/pydantic/main.py b/pydantic/main.py index 0b44b0e..d50d3dc 100644 --- a/pydantic/main.py +++ b/pydantic/main.py @@ -651,11 +651,6 @@ class BaseModel(metaclass=MetaModel): def __str__(self) -> str: return self.to_string() - def __dir__(self) -> 'ListStr': - ret = list(object.__dir__(self)) - ret.extend(self.__dict__.keys()) - return ret - @property def __values__(self) -> 'DictStrAny': warnings.warn('`__values__` attribute is deprecated, use `__dict__` instead', DeprecationWarning) From 86be8b2012412e424e870a470dd4b25f8546fd10 Mon Sep 17 00:00:00 2001 From: MrMrRobat Date: Mon, 5 Aug 2019 12:49:12 +0300 Subject: [PATCH 6/7] Union changes messages --- HISTORY.rst | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 632c263..2a1f104 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -5,9 +5,8 @@ History v0.32 (unreleased) .................. -* **breaking change**: remove ``__getattr__`` on ``BaseModel``, #712 by @MrMrRobat -* rename ``__values__`` to ``__dict__`` on ``BaseModel``, deprecation warning on use of old name, - attributes access speed increased up to 14 times, #712 by @MrMrRobat +* **breaking change**: remove ``__getattr__`` and rename ``__values__`` to ``__dict__`` on ``BaseModel``, + deprecation warning on use ``__values__`` attr, attributes access speed increased up to 14 times, #712 by @MrMrRobat v0.31.1 (2019-07-31) From bbdc8e80385a08a6b0a3bbf8a2ded92d466936e2 Mon Sep 17 00:00:00 2001 From: Samuel Colvin Date: Mon, 5 Aug 2019 11:35:49 +0100 Subject: [PATCH 7/7] add test_init_inspection --- tests/test_edge_cases.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/test_edge_cases.py b/tests/test_edge_cases.py index e2b313f..5716060 100644 --- a/tests/test_edge_cases.py +++ b/tests/test_edge_cases.py @@ -1005,3 +1005,15 @@ def test_values_attr_deprecation(): m = Model(foo=4, bar='baz') with pytest.warns(DeprecationWarning, match='`__values__` attribute is deprecated, use `__dict__` instead'): assert m.__values__ == m.__dict__ + + +def test_init_inspection(): + class Foobar(BaseModel): + x: int + + def __init__(self, **data) -> None: + with pytest.raises(AttributeError): + assert self.x + super().__init__(**data) + + Foobar(x=1)