mirror of
https://github.com/kennethreitz/pydantic.git
synced 2026-06-05 23:00:18 +00:00
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:
@@ -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.
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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():
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user