Refactor PrivateAttr to type-check like Field (#2057)

* Refactor PrivateAttr to type-check like Field

* Change TypeError to ValueError for consistency

* Add PrivateAttr example to mypy tests
This commit is contained in:
Alex Hedges
2020-10-28 06:17:45 -04:00
committed by GitHub
parent 95435de452
commit 8aad3a2f58
5 changed files with 36 additions and 17 deletions
+1
View File
@@ -0,0 +1 @@
Fix mypy assignment error when using `PrivateAttr`
+26 -12
View File
@@ -799,21 +799,10 @@ class ModelField(Representation):
return args
class PrivateAttr(Representation):
"""
Indicates that attribute is only used internally and never mixed with regular fields.
Types or values of private attrs are not checked by pydantic and it's up to you to keep them relevant.
Private attrs are stored in model __slots__.
"""
class ModelPrivateAttr(Representation):
__slots__ = ('default', 'default_factory')
def __init__(self, default: Any = Undefined, *, default_factory: Optional[NoArgAnyCallable] = None) -> None:
if default is not Undefined and default_factory is not None:
raise TypeError('default and default_factory args can not be used together')
self.default = default
self.default_factory = default_factory
@@ -825,3 +814,28 @@ class PrivateAttr(Representation):
other.default,
other.default_factory,
)
def PrivateAttr(
default: Any = Undefined,
*,
default_factory: Optional[NoArgAnyCallable] = None,
) -> Any:
"""
Indicates that attribute is only used internally and never mixed with regular fields.
Types or values of private attrs are not checked by pydantic and it's up to you to keep them relevant.
Private attrs are stored in model __slots__.
:param default: the attributes default value
:param default_factory: callable that will be called when a default value is needed for this attribute
If both `default` and `default_factory` are set, an error is raised.
"""
if default is not Undefined and default_factory is not None:
raise ValueError('cannot specify both default and default_factory')
return ModelPrivateAttr(
default,
default_factory=default_factory,
)
+3 -3
View File
@@ -29,7 +29,7 @@ from typing import (
from .class_validators import ValidatorGroup, extract_root_validators, extract_validators, inherit_validators
from .error_wrappers import ErrorWrapper, ValidationError
from .errors import ConfigError, DictError, ExtraError, MissingError
from .fields import SHAPE_MAPPING, ModelField, PrivateAttr, Undefined
from .fields import SHAPE_MAPPING, ModelField, ModelPrivateAttr, PrivateAttr, Undefined
from .json import custom_pydantic_encoder, pydantic_encoder
from .parse import Protocol, load_file, load_str_bytes
from .schema import default_ref_template, model_schema
@@ -215,7 +215,7 @@ class ModelMetaclass(ABCMeta):
validators: 'ValidatorListDict' = {}
pre_root_validators, post_root_validators = [], []
private_attributes: Dict[str, PrivateAttr] = {}
private_attributes: Dict[str, ModelPrivateAttr] = {}
slots: Set[str] = namespace.get('__slots__', ())
slots = {slots} if isinstance(slots, str) else set(slots)
@@ -271,7 +271,7 @@ class ModelMetaclass(ABCMeta):
for var_name, value in namespace.items():
can_be_changed = var_name not in class_vars and not isinstance(value, untouched_types)
if isinstance(value, PrivateAttr):
if isinstance(value, ModelPrivateAttr):
if not is_valid_private_name(var_name):
raise NameError(
f'Private attributes "{var_name}" must not be a valid field name; '
+5 -1
View File
@@ -9,7 +9,7 @@ from datetime import date, datetime
from typing import Any, Dict, Generic, List, Optional, TypeVar
from pydantic import BaseModel, NoneStr, PyObject, StrictBool, root_validator, validate_arguments, validator
from pydantic.fields import Field
from pydantic.fields import Field, PrivateAttr
from pydantic.generics import GenericModel
from pydantic.typing import ForwardRef
@@ -145,3 +145,7 @@ class MyConf(BaseModel):
conf = MyConf()
var1: date = conf.str_pyobject(2020, 12, 20)
var2: date = conf.callable_pyobject(2111, 1, 1)
class MyPrivateAttr(BaseModel):
_private_field: str = PrivateAttr()
+1 -1
View File
@@ -158,5 +158,5 @@ def test_slots_are_ignored():
def test_default_and_default_factory_used_error():
with pytest.raises(TypeError, match='default and default_factory args can not be used together'):
with pytest.raises(ValueError, match='cannot specify both default and default_factory'):
PrivateAttr(default=123, default_factory=lambda: 321)