mirror of
https://github.com/kennethreitz/pydantic.git
synced 2026-06-05 23:00:18 +00:00
Add Config.keep_untouched for custom descriptors support (#679)
* Add Config.keep_untouched for custom descriptors support * Update HISTORY.rst * Separate test * Fix tests * Update pydantic/validators.py Co-Authored-By: Samuel Colvin <samcolvin@gmail.com> * Update HISTORY.rst Co-Authored-By: Samuel Colvin <samcolvin@gmail.com> * Rename TYPE_BLACKLIST -> UNTOUCHED_TYPES, fix tests and formatting * Update docs/index.rst Co-Authored-By: Samuel Colvin <samcolvin@gmail.com>
This commit is contained in:
committed by
Samuel Colvin
parent
b702eb8738
commit
fae3588f42
@@ -12,6 +12,7 @@ v0.31 (unreleased)
|
||||
* add advanced exclude support for ``dict``, ``json`` and ``copy``, #648 by @MrMrRobat
|
||||
* fix bug in ``GenericModel`` for models with concrete parameterized fields, #672 by @dmontagu
|
||||
* add documentation for Literal type, #651 by @dmontagu
|
||||
* add ``Config.keep_untouched`` for custom descriptors support, #679 by @MrMrRobat
|
||||
* use ``inspect.cleandoc`` internally to get model description, #657 by @tiangolo
|
||||
* add Color to schema generation, by @euri10
|
||||
|
||||
|
||||
@@ -762,6 +762,8 @@ Options:
|
||||
details.
|
||||
:orm_mode: allows usage of :ref:`ORM mode <orm_mode>`
|
||||
:alias_generator: callable that takes field name and returns alias for it
|
||||
:keep_untouched: tuple of types (e. g. descriptors) that won't change during model creation and won't be
|
||||
included in the model schemas.
|
||||
|
||||
.. warning::
|
||||
|
||||
|
||||
+4
-2
@@ -97,6 +97,7 @@ class BaseConfig:
|
||||
json_encoders: Dict[AnyType, AnyCallable] = {}
|
||||
orm_mode: bool = False
|
||||
alias_generator: Optional[Callable[[str], str]] = None
|
||||
keep_untouched: Tuple[type, ...] = ()
|
||||
|
||||
@classmethod
|
||||
def get_field_schema(cls, name: str) -> Dict[str, str]:
|
||||
@@ -169,7 +170,7 @@ def validate_custom_root_type(fields: Dict[str, Field]) -> None:
|
||||
raise TypeError('custom root type cannot allow mapping')
|
||||
|
||||
|
||||
TYPE_BLACKLIST = FunctionType, property, type, classmethod, staticmethod
|
||||
UNTOUCHED_TYPES = FunctionType, property, type, classmethod, staticmethod
|
||||
|
||||
|
||||
class MetaModel(ABCMeta):
|
||||
@@ -217,10 +218,11 @@ class MetaModel(ABCMeta):
|
||||
config=config,
|
||||
)
|
||||
|
||||
untouched_types = UNTOUCHED_TYPES + config.keep_untouched
|
||||
for var_name, value in namespace.items():
|
||||
if (
|
||||
is_valid_field(var_name)
|
||||
and (annotations.get(var_name) == PyObject or not isinstance(value, TYPE_BLACKLIST))
|
||||
and (annotations.get(var_name) == PyObject or not isinstance(value, untouched_types))
|
||||
and var_name not in class_vars
|
||||
):
|
||||
validate_field_name(bases, var_name)
|
||||
|
||||
@@ -475,7 +475,9 @@ def find_validators( # noqa: C901 (ignore complexity)
|
||||
if config.arbitrary_types_allowed:
|
||||
yield make_arbitrary_type_validator(type_)
|
||||
else:
|
||||
raise RuntimeError(f'no validator found for {type_}')
|
||||
raise RuntimeError(
|
||||
f'no validator found for {type_} see `keep_untouched` or `arbitrary_types_allowed` in Config'
|
||||
)
|
||||
|
||||
|
||||
def _find_supertype(type_: AnyType) -> Optional[AnyType]:
|
||||
|
||||
@@ -743,3 +743,66 @@ def test_parse_root_as_mapping():
|
||||
|
||||
class MyModel(BaseModel):
|
||||
__root__: Mapping[str, str]
|
||||
|
||||
|
||||
def test_untouched_types():
|
||||
from pydantic import BaseModel
|
||||
|
||||
class _ClassPropertyDescriptor:
|
||||
def __init__(self, getter):
|
||||
self.getter = getter
|
||||
|
||||
def __get__(self, instance, owner):
|
||||
return self.getter(owner)
|
||||
|
||||
classproperty = _ClassPropertyDescriptor
|
||||
|
||||
class Model(BaseModel):
|
||||
class Config:
|
||||
keep_untouched = (classproperty,)
|
||||
|
||||
@classproperty
|
||||
def class_name(cls) -> str:
|
||||
return cls.__name__
|
||||
|
||||
assert Model.class_name == 'Model'
|
||||
assert Model().class_name == 'Model'
|
||||
|
||||
|
||||
def test_custom_types_fail_without_keep_untouched():
|
||||
from pydantic import BaseModel
|
||||
|
||||
class _ClassPropertyDescriptor:
|
||||
def __init__(self, getter):
|
||||
self.getter = getter
|
||||
|
||||
def __get__(self, instance, owner):
|
||||
return self.getter(owner)
|
||||
|
||||
classproperty = _ClassPropertyDescriptor
|
||||
|
||||
with pytest.raises(RuntimeError) as e:
|
||||
|
||||
class Model(BaseModel):
|
||||
@classproperty
|
||||
def class_name(cls) -> str:
|
||||
return cls.__name__
|
||||
|
||||
Model.class_name
|
||||
|
||||
assert str(e.value) == (
|
||||
"no validator found for <class 'tests.test_main.test_custom_types_fail_without_keep_untouched.<locals>."
|
||||
"_ClassPropertyDescriptor'> see `keep_untouched` or `arbitrary_types_allowed` in Config"
|
||||
)
|
||||
|
||||
class Model(BaseModel):
|
||||
class Config:
|
||||
arbitrary_types_allowed = True
|
||||
|
||||
@classproperty
|
||||
def class_name(cls) -> str:
|
||||
return cls.__name__
|
||||
|
||||
with pytest.raises(AttributeError) as e:
|
||||
Model.class_name
|
||||
assert str(e.value) == "type object 'Model' has no attribute 'class_name'"
|
||||
|
||||
Reference in New Issue
Block a user