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()
This commit is contained in:
Samuel Colvin
2021-02-26 14:43:47 +00:00
committed by GitHub
parent 8f0980e982
commit 2ee6811655
11 changed files with 47 additions and 181 deletions
-76
View File
@@ -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.
-1
View File
@@ -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
+1 -2
View File
@@ -6,7 +6,7 @@ from .decorator import validate_arguments
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 @@ __all__ = [
# fields
'Field',
'Required',
'Schema',
# main
'BaseConfig',
'BaseModel',
+3 -6
View File
@@ -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 @@ class UndefinedType:
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
+6 -32
View File
@@ -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 @@ class BaseModel(Representation, metaclass=ModelMetaclass):
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 @@ class BaseModel(Representation, metaclass=ModelMetaclass):
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 @@ class BaseModel(Representation, metaclass=ModelMetaclass):
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
-16
View File
@@ -161,22 +161,6 @@ def test_pop_by_field_name():
]
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
+21
View File
@@ -4,6 +4,7 @@ from typing import Any, List, Optional
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
+9 -30
View File
@@ -19,7 +19,7 @@ from pydantic import (
validate_model,
validator,
)
from pydantic.fields import Field, Schema
from pydantic.fields import Field
try:
import cython
@@ -1107,16 +1107,6 @@ def test_nested_init(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 test_not_optional_subfields():
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')]
+1 -2
View File
@@ -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():
-16
View File
@@ -394,22 +394,6 @@ def test_case_sensitive(monkeypatch):
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:
+6
View File
@@ -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