From 2ee6811655ff7aa16865a5458031348a84acc873 Mon Sep 17 00:00:00 2001 From: Samuel Colvin Date: Fri, 26 Feb 2021 14:43:47 +0000 Subject: [PATCH] remove DeprecationWarnings from v1 release & fix coverage (#2415) * remove DeprecationWarnings from v1 release * coverage on unpickling Undefined private attributes * coverage on undefined in copy, allow Undefined to be pickled unchanged * fix coverage of model._iter() --- docs/version_1_release_notes.md | 76 --------------------------------- mkdocs.yml | 1 - pydantic/__init__.py | 3 +- pydantic/fields.py | 9 ++-- pydantic/main.py | 38 +++-------------- tests/test_aliases.py | 16 ------- tests/test_construction.py | 21 +++++++++ tests/test_edge_cases.py | 39 ++++------------- tests/test_main.py | 3 +- tests/test_settings.py | 16 ------- tests/test_utils.py | 6 +++ 11 files changed, 47 insertions(+), 181 deletions(-) delete mode 100644 docs/version_1_release_notes.md diff --git a/docs/version_1_release_notes.md b/docs/version_1_release_notes.md deleted file mode 100644 index d923347a75..0000000000 --- a/docs/version_1_release_notes.md +++ /dev/null @@ -1,76 +0,0 @@ -After 2.5 years of development with contributions from over 80 people and 62 releases, *pydantic* has reached -version 1! - -While the fundamentals of *pydantic* have remained unchanged since the previous release -[v0.32.2](changelog.md#v0322-2019-08-17) (indeed, since *pydantic* began in early 2017); -a number of things have changed which you may wish to be aware of while migrating to Version 1. - -Below is a list of significant changes, for a full list of changes see release notes for -[v1.0b1](changelog.md#v10b1-2019-10-01), [v1.0b2](changelog.md#v10b2-2019-10-07), -and [v1.0](changelog.md#v10-2019-10-23). - -## What's new in pydantic v1 - -### Root validators - -A new decorator [`root_validator`](usage/validators.md#root-validators) has been added to allow validation of entire -models. - -### Custom JSON encoding/decoding - -There are new `Config` settings to allow -[Custom JSON (de)serialisation](usage/exporting_models.md#custom-json-deserialisation). This can allow alternative -JSON implementations to be used with significantly improved performance. - -### Boolean parsing - -The logic for [parsing and validating boolean values](usage/types.md#booleans) has been overhauled to only allow -a defined set of values rather than allowing any value as it used to. - -### URL parsing - -The logic for parsing URLs (and related objects like DSNs) has been completely re-written to provide more useful -error messages, greater simplicity and more flexibility. - -### Performance improvements - -Some less "clever" error handling and cleanup of how errors are wrapped (together with many other small changes) -has improved the performance of *pydantic* by ~25%, see -[samuelcolvin/pydantic#819](https://github.com/samuelcolvin/pydantic/pull/819). - -### ORM mode improvements - -There are improvements to [`GetterDict`](usage/models.md#orm-mode-aka-arbitrary-class-instances) to make ORM mode -easier to use and work with root validators, see -[samuelcolvin/pydantic#822](https://github.com/samuelcolvin/pydantic/pull/822). - -### Settings improvements - -There are a number of changes to how [`BaseSettings`](usage/settings.md) works: - -* `case_insensitive` has been renamed to `case_sensitive` and the default has changed to `case_sensitive = False` -* the default for `env_prefix` has changed to an empty string, i.e. by default there's no prefix for environment - variable lookups -* aliases are no longer used when looking up environment variables, instead there's a new `env` setting for `Field()` or - in `Config.fields`. - -### Improvements to field ordering - -There are some subtle changes to the ordering of fields, see [Model field ordering](usage/models.md#field-ordering) -for more details. - -### Schema renamed to Field - -The function used for providing extra information about fields has been renamed from `Schema` to `Field`. The -new name makes more sense since the method can be used to provide any sort of information and change the behaviour -of the field, as well as add attributes which are used while [generating a model schema](usage/schema.md). - -### Improved repr methods and devtools integration - -The `__repr__` and `__str__` method of models as well as most other public classes in *pydantic* have been altered -to be consistent and informative. There's also new [integration with python-devtools](usage/devtools.md). - -### Field constraints checks - -Constraints added to `Field()` which are not enforced now cause an error when a model is created, see -[Unenforced Field constraints](usage/schema.md#unenforced-field-constraints) for more details and work-arounds. diff --git a/mkdocs.yml b/mkdocs.yml index a0550e3637..c2f4e4014a 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -28,7 +28,6 @@ extra_javascript: nav: - Overview: index.md - install.md -- 'Version 1 release notes': version_1_release_notes.md - Usage: - usage/models.md - 'Field Types': usage/types.md diff --git a/pydantic/__init__.py b/pydantic/__init__.py index 4fdba0824a..2e7aab406b 100644 --- a/pydantic/__init__.py +++ b/pydantic/__init__.py @@ -6,7 +6,7 @@ from .env_settings import BaseSettings from .error_wrappers import ValidationError from .errors import * -from .fields import Field, PrivateAttr, Required, Schema +from .fields import Field, PrivateAttr, Required from .main import * from .networks import * from .parse import Protocol @@ -34,7 +34,6 @@ # fields 'Field', 'Required', - 'Schema', # main 'BaseConfig', 'BaseModel', diff --git a/pydantic/fields.py b/pydantic/fields.py index 4db34a2535..7d0c2a3ead 100644 --- a/pydantic/fields.py +++ b/pydantic/fields.py @@ -1,4 +1,3 @@ -import warnings from collections import defaultdict, deque from collections.abc import Iterable as CollectionsIterable from typing import ( @@ -59,6 +58,9 @@ def __repr__(self) -> str: def __copy__(self: T) -> T: return self + def __reduce__(self) -> str: + return 'Undefined' + def __deepcopy__(self: T, _: Any) -> T: return self @@ -233,11 +235,6 @@ def Field( return field_info -def Schema(default: Any, **kwargs: Any) -> Any: - warnings.warn('`Schema` is deprecated, use `Field` instead', DeprecationWarning) - return Field(default, **kwargs) - - # used to be an enum but changed to int's for small performance improvement as less access overhead SHAPE_SINGLETON = 1 SHAPE_LIST = 2 diff --git a/pydantic/main.py b/pydantic/main.py index f1cb71f2d9..6c818d38e2 100644 --- a/pydantic/main.py +++ b/pydantic/main.py @@ -195,21 +195,6 @@ def prepare_config(config: Type[BaseConfig], cls_name: str) -> None: except ValueError: raise ValueError(f'"{cls_name}": {config.extra} is not a valid value for "extra"') - if hasattr(config, 'allow_population_by_alias'): - warnings.warn( - f'{cls_name}: "allow_population_by_alias" is deprecated and replaced by "allow_population_by_field_name"', - DeprecationWarning, - ) - config.allow_population_by_field_name = config.allow_population_by_alias # type: ignore - - if hasattr(config, 'case_insensitive') and any('BaseSettings.Config' in c.__qualname__ for c in config.__mro__): - warnings.warn( - f'{cls_name}: "case_insensitive" is deprecated on BaseSettings config and replaced by ' - f'"case_sensitive" (default False)', - DeprecationWarning, - ) - config.case_sensitive = not config.case_insensitive # type: ignore - def validate_custom_root_type(fields: Dict[str, ModelField]) -> None: if len(fields) > 1: @@ -466,18 +451,18 @@ def __setattr__(self, name, value): # noqa: C901 (ignore complexity) self.__fields_set__.add(name) def __getstate__(self) -> 'DictAny': + private_attrs = ((k, getattr(self, k, Undefined)) for k in self.__private_attributes__) return { '__dict__': self.__dict__, '__fields_set__': self.__fields_set__, - '__private_attribute_values__': {k: getattr(self, k, Undefined) for k in self.__private_attributes__}, + '__private_attribute_values__': {k: v for k, v in private_attrs if v is not Undefined}, } def __setstate__(self, state: 'DictAny') -> None: object_setattr(self, '__dict__', state['__dict__']) object_setattr(self, '__fields_set__', state['__fields_set__']) for name, value in state.get('__private_attribute_values__', {}).items(): - if value is not Undefined: - object_setattr(self, name, value) + object_setattr(self, name, value) def _init_private_attributes(self) -> None: for name, private_attr in self.__private_attributes__.items(): @@ -859,14 +844,17 @@ def _iter( for field_key, v in self.__dict__.items(): if (allowed_keys is not None and field_key not in allowed_keys) or (exclude_none and v is None): continue + if exclude_defaults: model_field = self.__fields__.get(field_key) if not getattr(model_field, 'required', True) and getattr(model_field, 'default', _missing) == v: continue + 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, @@ -922,20 +910,6 @@ def __eq__(self, other: Any) -> bool: def __repr_args__(self) -> 'ReprArgs': return self.__dict__.items() # type: ignore - @property - def fields(self) -> Dict[str, ModelField]: - warnings.warn('`fields` attribute is deprecated, use `__fields__` instead', DeprecationWarning) - return self.__fields__ - - def to_string(self, pretty: bool = False) -> str: - warnings.warn('`model.to_string()` method is deprecated, use `str(model)` instead', DeprecationWarning) - return str(self) - - @property - def __values__(self) -> 'DictStrAny': - warnings.warn('`__values__` attribute is deprecated, use `__dict__` instead', DeprecationWarning) - return self.__dict__ - _is_base_model_class_defined = True diff --git a/tests/test_aliases.py b/tests/test_aliases.py index 5e0fce29e0..24caa9ed62 100644 --- a/tests/test_aliases.py +++ b/tests/test_aliases.py @@ -161,22 +161,6 @@ class Config: ] -def test_population_by_alias(): - with pytest.warns(DeprecationWarning, match='"allow_population_by_alias" is deprecated and replaced by'): - - class Model(BaseModel): - a: str - - class Config: - allow_population_by_alias = True - fields = {'a': {'alias': '_a'}} - - assert Model.__config__.allow_population_by_field_name is True - assert Model(a='different').a == 'different' - assert Model(a='different').dict() == {'a': 'different'} - assert Model(a='different').dict(by_alias=True) == {'_a': 'different'} - - def test_alias_child_precedence(): class Parent(BaseModel): x: int diff --git a/tests/test_construction.py b/tests/test_construction.py index 7d3c7c1dd7..8a8130c489 100644 --- a/tests/test_construction.py +++ b/tests/test_construction.py @@ -4,6 +4,7 @@ import pytest from pydantic import BaseModel, Field, PrivateAttr +from pydantic.fields import Undefined class Model(BaseModel): @@ -253,6 +254,26 @@ def test_recursive_pickle(): assert m.__foo__ == m2.__foo__ +def test_pickle_undefined(): + m = ModelTwo(a=24, d=Model(a='123.45')) + m2 = pickle.loads(pickle.dumps(m)) + assert m2.__foo__ == {'private'} + + m.__foo__ = Undefined + m3 = pickle.loads(pickle.dumps(m)) + assert not hasattr(m3, '__foo__') + + +def test_copy_undefined(): + m = ModelTwo(a=24, d=Model(a='123.45')) + m2 = m.copy() + assert m2.__foo__ == {'private'} + + m.__foo__ = Undefined + m3 = m.copy() + assert not hasattr(m3, '__foo__') + + def test_immutable_copy_with_allow_mutation(): class Model(BaseModel): a: int diff --git a/tests/test_edge_cases.py b/tests/test_edge_cases.py index 09e1bac76d..26e08140e8 100644 --- a/tests/test_edge_cases.py +++ b/tests/test_edge_cases.py @@ -19,7 +19,7 @@ validate_model, validator, ) -from pydantic.fields import Field, Schema +from pydantic.fields import Field try: import cython @@ -1107,16 +1107,6 @@ class TopModel(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 @@ -1219,25 +1209,6 @@ def check_a(cls, v): assert Model(a=12).a == 12 -def test_scheme_deprecated(): - - with pytest.warns(DeprecationWarning, match='`Schema` is deprecated, use `Field` instead'): - - class Model(BaseModel): - foo: int = Schema(4) - - -def test_fields_deprecated(): - class Model(BaseModel): - v: str = 'x' - - with pytest.warns(DeprecationWarning, match='`fields` attribute is deprecated, use `__fields__` instead'): - assert Model().fields.keys() == {'v'} - - assert Model().__fields__.keys() == {'v'} - assert Model.__fields__.keys() == {'v'} - - def test_optional_field_constraints(): class MyModel(BaseModel): my_int: Optional[int] = Field(..., ge=3) @@ -1793,3 +1764,11 @@ class User(BaseModel): module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) assert module.User(id=12).dict() == {'id': 12, 'name': 'Jane Doe'} + + +def test_iter_coverage(): + class MyModel(BaseModel): + x: int = 1 + y: str = 'a' + + assert list(MyModel()._iter(by_alias=True)) == [('x', 1), ('y', 'a')] diff --git a/tests/test_main.py b/tests/test_main.py index 08c926c40b..e193772b1f 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -62,8 +62,7 @@ def test_ultra_simple_repr(): assert dict(m) == {'a': 10.2, 'b': 10} assert m.dict() == {'a': 10.2, 'b': 10} assert m.json() == '{"a": 10.2, "b": 10}' - with pytest.raises(DeprecationWarning, match=r'`model.to_string\(\)` method is deprecated'): - assert m.to_string() == 'a=10.2 b=10' + assert str(m) == 'a=10.2 b=10' def test_default_factory_field(): diff --git a/tests/test_settings.py b/tests/test_settings.py index 7513c4c532..8ccc297384 100644 --- a/tests/test_settings.py +++ b/tests/test_settings.py @@ -394,22 +394,6 @@ class Config: assert exc_info.value.errors() == [{'loc': ('foo',), 'msg': 'field required', 'type': 'value_error.missing'}] -def test_case_insensitive(monkeypatch): - class Settings1(BaseSettings): - foo: str - - with pytest.warns(DeprecationWarning, match='Settings2: "case_insensitive" is deprecated on BaseSettings'): - - class Settings2(BaseSettings): - foo: str - - class Config: - case_insensitive = False - - assert Settings1.__config__.case_sensitive is False - assert Settings2.__config__.case_sensitive is True - - def test_nested_dataclass(env): @dataclasses.dataclass class MyDataclass: diff --git a/tests/test_utils.py b/tests/test_utils.py index 52e4b81af8..c489354ffc 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,5 +1,6 @@ import collections.abc import os +import pickle import re import string import sys @@ -499,3 +500,8 @@ def test_all_identical(): assert ( all_identical([a, [b], b], [a, [b], b]) is False ), 'New list objects are different objects and should therefor not be identical.' + + +def test_undefined_pickle(): + undefined2 = pickle.loads(pickle.dumps(Undefined)) + assert undefined2 is Undefined