Join GitHub today
GitHub is home to over 40 million developers working together to host and review code, manage projects, and build software together.Sign up
Added generic approach to strict type checking for constrained types #799
This PR adds configurable strictness validation to ConstrainedStr, ConstrainedInt and ConstrainedFloat.
This behaviour is desired for our use case as we generally never want coercion for anything,
Related issue number
This issue seems related but was not regarded when creating this PR: #780
- Use the arbitrary validator to build strict validators for ConstrainedInt, ConstrainedFloat, ConstrainedStr - Make StrictStr a derived class of ConstrainedStr - Add tests for new strict cases for ConstrainedInt and ConstrainedFloat
@@ Coverage Diff @@ ## master #799 +/- ## ===================================== Coverage 100% 100% ===================================== Files 17 17 Lines 2972 2984 +12 Branches 578 580 +2 ===================================== + Hits 2972 2984 +12
- Make ConstrainedInt and ConstrainedFloat use those validators instead of abusing arbitrary type validator for strictness - Prevent double validaton of same conditions by only yielding either the strict or non-strict type validator for for those classes
I deleted my previous posts because the solution I came up with in the end seems trivial.
I guess responses were created due to emails generated.. So here is a response to a question that disappeared :)
import pydantic, typing IdentStr = typing.NewType('IdentStr', pydantic.ConstrainedStr) IdentStr.min_length Traceback (most recent call last): File "<input>", line 1, in <module> AttributeError: 'function' object has no attribute 'min_length'
Since in real life, we control the code for IdentStr, I am playing around with:
if PYDANTIC_VERSION >= "1.0.0": from pydantic import StrictStr else: # min version is 0.32.2 in setup.py from pydantic import ConstrainedStr from pydantic.errors import StrError class StrictStr(ConstrainedStr): @classmethod def strict_str_validator(cls, v: str) -> str: if not isinstance(v, str): raise StrError() return v @classmethod def __get_validators__(cls): yield cls.strict_str_validator yield from super().__get_validators__() class IdentStr(StrictStr): min_length = 1
I am sorta lost as to why this isn't working as expected for me in v1.2:
from typing import Type, TypeVar import pydantic class ConnectionName(pydantic.StrictStr): min_length = 1 class EndpointStr(pydantic.StrictStr): min_length = 1 DEFAULT_ENDPOINT = EndpointStr("default") T = TypeVar("T", bound="UpRequestEvent") class UpRequestEvent(pydantic.BaseModel): connection_name: ConnectionName # connection name connection_endpoint: EndpointStr # connection name's endpoint (for server type connections) @classmethod def build_from( cls: Type[T], connection_name: ConnectionName, connection_endpoint: EndpointStr = DEFAULT_ENDPOINT, **kwargs ) -> T: return cls( connection_name=connection_name, connection_endpoint=connection_endpoint, ) class Config: use_enum_values = True # This works UpRequestEvent.build_from( connection_name="test", connection_endpoint="default", ) # This fails UpRequestEvent.build_from( connection_name=ConnectionName("test"), connection_endpoint=EndpointStr("default"), )
My special types ConnectionName and EndpointStr are a subclass of
Am I not able to subclass these two types?
@skewty I think I found the issue.
If you change the signature of
def validate(cls, value: Union[str]) -> Union[str]: ...
def strict_str_validator(v: Any) -> Union[str]:
your code works.
I think what is happening is something weird with how cython handles
@skewty It would be great if you could submit a new (bug) issue about this. It would also be great if you could make a PR with these changes and adding an appropriate test, but we can get to it eventually if you create an issue.
Strangely, looking at the differences in the generated cython code, you can see cython is actually injecting this error:
+060: return v __Pyx_XDECREF(__pyx_r); if (!(likely(PyString_CheckExact(__pyx_v_v))||((__pyx_v_v) == Py_None)||(PyErr_Format(PyExc_TypeError, "Expected %.16s, got %.200s", "str", Py_TYPE(__pyx_v_v)->tp_name), 0))) __PYX_ERR(0, 60, __pyx_L1_error) __Pyx_INCREF(__pyx_v_v); __pyx_r = ((PyObject*)__pyx_v_v);
It also makes another minor change in the code earlier on, but I can't tell if that is affecting anything meaningful at first glance (looks like it is just looking up the