diff --git a/HISTORY.rst b/HISTORY.rst index b94ba3c..ac3565f 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -3,10 +3,11 @@ History ------- - v0.32 (unreleased) .................. * add model name to ``ValidationError`` error message, #676 by @dmontagu +* **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) .................... diff --git a/pydantic/main.py b/pydantic/main.py index d59736d..749df9f 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_], type(self)) 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,16 +645,16 @@ 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: return self.to_string() - def __dir__(self) -> 'ListStr': - ret = list(object.__dir__(self)) - ret.extend(self.__values__.keys()) - return ret + @property + def __values__(self) -> 'DictStrAny': + warnings.warn('`__values__` attribute is deprecated, use `__dict__` instead', DeprecationWarning) + return self.__dict__ def create_model( diff --git a/tests/test_edge_cases.py b/tests/test_edge_cases.py index eaf2500..5716060 100644 --- a/tests/test_edge_cases.py +++ b/tests/test_edge_cases.py @@ -995,3 +995,25 @@ 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__ + + +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)