Repository: pydantic/pydantic

Issue:
Correctly typed default values for specialized types e.g. PositiveInt, Json, ...
Neither in the documentation nor any code on the repo (e.g. in tests) could I find any example of how to work with more exotic pydantic types such as `PositiveInt` or `Json` that have default values:

Take for instance :

```python
class Foo(BaseModel):
    bar: PositiveInt = 1  # mypy complains about a type mismatch between PositiveInt and int
```

fair enough, let's try:

```python
class Foo(BaseModel):
    bar: PositiveInt = PositiveInt(1)  # typechecks, hooray
```

How about `Json` then ?

```python
class Foo(BaseModel):
    bar: Json = {"hello": "world"}  # same as above, Json is not a type alias of Dict
```

```python
class Foo(BaseModel):
    bar: Json = '{"hello": "world"}'  # again, Json is not a type alias of str
```

however:

```python
class Foo(BaseModel):
    bar: Json = Json({"hello": "world"})  # doesn't work as Json takes no arguments
```

How does one add a default value for a `Json` field (or any other more exotic field for that matter)? 



How to keep mypy happy using `pydantic.DirectoryPath` with a default value
### Checks

* [x] I added a descriptive title to this issue
* [x] I have searched (google, github) for similar issues and couldn't find anything
* [x] I have read and followed [the docs](https://pydantic-docs.helpmanual.io/) and couldn't find an answer
* [x] After submitting this, I commit to one of:
    * Look through open issues and helped at least one other person
    * Hit the "watch" button on this repo to receive notifications and I commit to help at least 2 people that ask questions in the future
    * Implement a Pull Request for a confirmed bug

<!-- Sorry to sound so draconian, but every second saved replying to issues is time spend improving pydantic :-) -->

# Question

Output of `python -c "import pydantic.utils; print(pydantic.utils.version_info())"`:
```
             pydantic version: 1.7.2
            pydantic compiled: True
                 install path: /Users/michaeloliver/echo/app/.venv/lib/python3.9/site-packages/pydantic
               python version: 3.9.0 (default, Oct 27 2020, 14:15:17)  [Clang 12.0.0 (clang-1200.0.32.21)]
                     platform: macOS-10.16-x86_64-i386-64bit
     optional deps. installed: ['typing-extensions']
```
<!-- or if you're using pydantic prior to v1.3, manually include: OS, python version and pydantic version -->

<!-- Please read the [docs](https://pydantic-docs.helpmanual.io/) and search through issues to
confirm your question hasn't already been answered. -->

<!-- Where possible please include a self-contained code snippet describing your question: -->

I would like to use `DirectoryPath` with a default value if no environment override exists (using `BaseSettings`)

```py
from pathlib import Path

from pydantic import BaseSettings, DirectoryPath

BASE_DIR = Path(__file__).parent


class Config(BaseSettings):
    CONTENT_DIR: DirectoryPath = BASE_DIR / "content"

```

`mypy` complains:
```
src/hublib/config.py:8: error: Incompatible types in assignment (expression has type "Path", variable has type "DirectoryPath")
```

What would be the best approach ? 


Discussion:
Options:
* Use `type: ignore`
* Use `x: int = Field(.., ge=0)`
* Might be a possibility to modify the mypy plugin to accept this.

Regarding `Json` I'm not sure what we can do since the internal data could be anything. Might be possible to alter the mypy plugin, or find another escape hatch.

Maybe we could do something like `if TYPE_CHECKING: Json = Any; else: ...`? PR welcome if that works. 
What do you think about introducing fields specifically for parsing json objects and arrays?. Imho these solves four issues:

1. Removes the ambiguity that a parsed `Json` can either be a `list`, `dict`, `str`, `int`, `float`, `bool` or `None` at runtime and thus removes additional runtime checks (which is one of the main purposes of parsing, see [this article](https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/) for a very well written opinion on the subject of parsing vs validating input)
2. Makes it obvious which type to expect at runtime 
3. Lets the user provide default values that typecheck 
4. Prevents the common mistake of assuming `json == json object` or `json == json object | json array` 

An implementation could look like this:

```python
class JsonObject(dict):
    """Field for parsing json objects into a python dictionary."""

    @classmethod
    def __get_validators__(cls):
        yield cls.validate

    @classmethod
    def validate(cls, v):
        error = ValueError(f'The provided value {v} is not a valid json object')
        try:
            data = json.loads(v)
        except json.JSONDecodeError:
            raise error
        else:
            if not isinstance(data, dict):
                raise error
            return cls(data)


class JsonArray(list):
    """Field for parsing json arrays into a python list."""

    @classmethod
    def __get_validators__(cls):
        yield cls.validate

    @classmethod
    def validate(cls, v):
        error = ValueError(f'The provided value {v} is not a valid json array')
        try:
            data = json.loads(v)
        except json.JSONDecodeError:
            raise error
        else:
            if not isinstance(data, list):
                raise error
            return cls(data)
```

and usage would be 

```python
class Data(BaseModel):
    obj: JsonObject = JsonObject({})
    array: JsonArray = JsonArray([])
    other: Union[JsonArray, JsonObject] = JsonObject({})
```

notice that the above typechecks correctly and that at runtime one could use e.g. `isinstance(..., JsonArray)` for determining the type of `other`. This is imho a clearer expression of intent than using `isinstance(..., list)` 
Not necessary, you can already use `Json[List[int]]` or `Json[List[Any]]`.


Resolving patch:
diff --git a/pydantic/_hypothesis_plugin.py b/pydantic/_hypothesis_plugin.py
--- a/pydantic/_hypothesis_plugin.py
+++ b/pydantic/_hypothesis_plugin.py
@@ -76,7 +76,7 @@ def is_valid_email(s: str) -> bool:
 
 # PyObject - dotted names, in this case taken from the math module.
 st.register_type_strategy(
-    pydantic.PyObject,
+    pydantic.PyObject,  # type: ignore[arg-type]
     st.sampled_from(
         [cast(pydantic.PyObject, f'math.{name}') for name in sorted(vars(math)) if not name.startswith('_')]
     ),
@@ -163,7 +163,7 @@ def add_luhn_digit(card_number: str) -> str:
 # so here we only have to register subclasses for other constrained types which
 # don't go via those mechanisms.  Then there are the registration hooks below.
 st.register_type_strategy(pydantic.StrictBool, st.booleans())
-st.register_type_strategy(pydantic.StrictStr, st.text())  # type: ignore[arg-type]
+st.register_type_strategy(pydantic.StrictStr, st.text())
 
 
 # Constrained-type resolver functions
diff --git a/pydantic/types.py b/pydantic/types.py
--- a/pydantic/types.py
+++ b/pydantic/types.py
@@ -136,119 +136,224 @@ def _registered(typ: Union[Type[T], 'ConstrainedNumberMeta']) -> Union[Type[T],
     return typ
 
 
-class ConstrainedBytes(bytes):
-    strip_whitespace = False
-    to_lower = False
-    min_length: OptionalInt = None
-    max_length: OptionalInt = None
+class ConstrainedNumberMeta(type):
+    def __new__(cls, name: str, bases: Any, dct: Dict[str, Any]) -> 'ConstrainedInt':  # type: ignore
+        new_cls = cast('ConstrainedInt', type.__new__(cls, name, bases, dct))
+
+        if new_cls.gt is not None and new_cls.ge is not None:
+            raise errors.ConfigError('bounds gt and ge cannot be specified at the same time')
+        if new_cls.lt is not None and new_cls.le is not None:
+            raise errors.ConfigError('bounds lt and le cannot be specified at the same time')
+
+        return _registered(new_cls)  # type: ignore
+
+
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ BOOLEAN TYPES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+if TYPE_CHECKING:
+    StrictBool = bool
+else:
+
+    class StrictBool(int):
+        """
+        StrictBool to allow for bools which are not type-coerced.
+        """
+
+        @classmethod
+        def __modify_schema__(cls, field_schema: Dict[str, Any]) -> None:
+            field_schema.update(type='boolean')
+
+        @classmethod
+        def __get_validators__(cls) -> 'CallableGenerator':
+            yield cls.validate
+
+        @classmethod
+        def validate(cls, value: Any) -> bool:
+            """
+            Ensure that we only allow bools.
+            """
+            if isinstance(value, bool):
+                return value
+
+            raise errors.StrictBoolError()
+
+
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ INTEGER TYPES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+
+class ConstrainedInt(int, metaclass=ConstrainedNumberMeta):
     strict: bool = False
+    gt: OptionalInt = None
+    ge: OptionalInt = None
+    lt: OptionalInt = None
+    le: OptionalInt = None
+    multiple_of: OptionalInt = None
 
     @classmethod
     def __modify_schema__(cls, field_schema: Dict[str, Any]) -> None:
-        update_not_none(field_schema, minLength=cls.min_length, maxLength=cls.max_length)
+        update_not_none(
+            field_schema,
+            exclusiveMinimum=cls.gt,
+            exclusiveMaximum=cls.lt,
+            minimum=cls.ge,
+            maximum=cls.le,
+            multipleOf=cls.multiple_of,
+        )
 
     @classmethod
     def __get_validators__(cls) -> 'CallableGenerator':
-        yield strict_bytes_validator if cls.strict else bytes_validator
-        yield constr_strip_whitespace
-        yield constr_lower
-        yield constr_length_validator
+        yield strict_int_validator if cls.strict else int_validator
+        yield number_size_validator
+        yield number_multiple_validator
 
 
-class StrictBytes(ConstrainedBytes):
-    strict = True
+def conint(
+    *, strict: bool = False, gt: int = None, ge: int = None, lt: int = None, le: int = None, multiple_of: int = None
+) -> Type[int]:
+    # use kwargs then define conf in a dict to aid with IDE type hinting
+    namespace = dict(strict=strict, gt=gt, ge=ge, lt=lt, le=le, multiple_of=multiple_of)
+    return type('ConstrainedIntValue', (ConstrainedInt,), namespace)
 
 
-def conbytes(
-    *, strip_whitespace: bool = False, to_lower: bool = False, min_length: int = None, max_length: int = None
-) -> Type[bytes]:
-    # use kwargs then define conf in a dict to aid with IDE type hinting
-    namespace = dict(strip_whitespace=strip_whitespace, to_lower=to_lower, min_length=min_length, max_length=max_length)
-    return _registered(type('ConstrainedBytesValue', (ConstrainedBytes,), namespace))
+if TYPE_CHECKING:
+    PositiveInt = int
+    NegativeInt = int
+    NonPositiveInt = int
+    NonNegativeInt = int
+    StrictInt = int
+else:
 
+    class PositiveInt(ConstrainedInt):
+        gt = 0
 
-# This types superclass should be List[T], but cython chokes on that...
-class ConstrainedList(list):  # type: ignore
-    # Needed for pydantic to detect that this is a list
-    __origin__ = list
-    __args__: Tuple[Type[T], ...]  # type: ignore
+    class NegativeInt(ConstrainedInt):
+        lt = 0
 
-    min_items: Optional[int] = None
-    max_items: Optional[int] = None
-    item_type: Type[T]  # type: ignore
+    class NonPositiveInt(ConstrainedInt):
+        le = 0
+
+    class NonNegativeInt(ConstrainedInt):
+        ge = 0
+
+    class StrictInt(ConstrainedInt):
+        strict = True
 
-    @classmethod
-    def __get_validators__(cls) -> 'CallableGenerator':
-        yield cls.list_length_validator
+
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ FLOAT TYPES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+
+class ConstrainedFloat(float, metaclass=ConstrainedNumberMeta):
+    strict: bool = False
+    gt: OptionalIntFloat = None
+    ge: OptionalIntFloat = None
+    lt: OptionalIntFloat = None
+    le: OptionalIntFloat = None
+    multiple_of: OptionalIntFloat = None
 
     @classmethod
     def __modify_schema__(cls, field_schema: Dict[str, Any]) -> None:
-        update_not_none(field_schema, minItems=cls.min_items, maxItems=cls.max_items)
+        update_not_none(
+            field_schema,
+            exclusiveMinimum=cls.gt,
+            exclusiveMaximum=cls.lt,
+            minimum=cls.ge,
+            maximum=cls.le,
+            multipleOf=cls.multiple_of,
+        )
+        # Modify constraints to account for differences between IEEE floats and JSON
+        if field_schema.get('exclusiveMinimum') == -math.inf:
+            del field_schema['exclusiveMinimum']
+        if field_schema.get('minimum') == -math.inf:
+            del field_schema['minimum']
+        if field_schema.get('exclusiveMaximum') == math.inf:
+            del field_schema['exclusiveMaximum']
+        if field_schema.get('maximum') == math.inf:
+            del field_schema['maximum']
 
     @classmethod
-    def list_length_validator(cls, v: 'Optional[List[T]]') -> 'Optional[List[T]]':
-        if v is None:
-            return None
+    def __get_validators__(cls) -> 'CallableGenerator':
+        yield strict_float_validator if cls.strict else float_validator
+        yield number_size_validator
+        yield number_multiple_validator
 
-        v = list_validator(v)
-        v_len = len(v)
 
-        if cls.min_items is not None and v_len < cls.min_items:
-            raise errors.ListMinLengthError(limit_value=cls.min_items)
+def confloat(
+    *,
+    strict: bool = False,
+    gt: float = None,
+    ge: float = None,
+    lt: float = None,
+    le: float = None,
+    multiple_of: float = None,
+) -> Type[float]:
+    # use kwargs then define conf in a dict to aid with IDE type hinting
+    namespace = dict(strict=strict, gt=gt, ge=ge, lt=lt, le=le, multiple_of=multiple_of)
+    return type('ConstrainedFloatValue', (ConstrainedFloat,), namespace)
 
-        if cls.max_items is not None and v_len > cls.max_items:
-            raise errors.ListMaxLengthError(limit_value=cls.max_items)
 
-        return v
+if TYPE_CHECKING:
+    PositiveFloat = float
+    NegativeFloat = float
+    NonPositiveFloat = float
+    NonNegativeFloat = float
+    StrictFloat = float
+else:
 
+    class PositiveFloat(ConstrainedFloat):
+        gt = 0
 
-def conlist(item_type: Type[T], *, min_items: int = None, max_items: int = None) -> Type[List[T]]:
-    # __args__ is needed to conform to typing generics api
-    namespace = {'min_items': min_items, 'max_items': max_items, 'item_type': item_type, '__args__': (item_type,)}
-    # We use new_class to be able to deal with Generic types
-    return new_class('ConstrainedListValue', (ConstrainedList,), {}, lambda ns: ns.update(namespace))
+    class NegativeFloat(ConstrainedFloat):
+        lt = 0
 
+    class NonPositiveFloat(ConstrainedFloat):
+        le = 0
 
-# This types superclass should be Set[T], but cython chokes on that...
-class ConstrainedSet(set):  # type: ignore
-    # Needed for pydantic to detect that this is a set
-    __origin__ = set
-    __args__: Set[Type[T]]  # type: ignore
+    class NonNegativeFloat(ConstrainedFloat):
+        ge = 0
 
-    min_items: Optional[int] = None
-    max_items: Optional[int] = None
-    item_type: Type[T]  # type: ignore
+    class StrictFloat(ConstrainedFloat):
+        strict = True
 
-    @classmethod
-    def __get_validators__(cls) -> 'CallableGenerator':
-        yield cls.set_length_validator
+
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ BYTES TYPES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+
+class ConstrainedBytes(bytes):
+    strip_whitespace = False
+    to_lower = False
+    min_length: OptionalInt = None
+    max_length: OptionalInt = None
+    strict: bool = False
 
     @classmethod
     def __modify_schema__(cls, field_schema: Dict[str, Any]) -> None:
-        update_not_none(field_schema, minItems=cls.min_items, maxItems=cls.max_items)
+        update_not_none(field_schema, minLength=cls.min_length, maxLength=cls.max_length)
 
     @classmethod
-    def set_length_validator(cls, v: 'Optional[Set[T]]') -> 'Optional[Set[T]]':
-        if v is None:
-            return None
+    def __get_validators__(cls) -> 'CallableGenerator':
+        yield strict_bytes_validator if cls.strict else bytes_validator
+        yield constr_strip_whitespace
+        yield constr_lower
+        yield constr_length_validator
 
-        v = set_validator(v)
-        v_len = len(v)
 
-        if cls.min_items is not None and v_len < cls.min_items:
-            raise errors.SetMinLengthError(limit_value=cls.min_items)
+def conbytes(
+    *, strip_whitespace: bool = False, to_lower: bool = False, min_length: int = None, max_length: int = None
+) -> Type[bytes]:
+    # use kwargs then define conf in a dict to aid with IDE type hinting
+    namespace = dict(strip_whitespace=strip_whitespace, to_lower=to_lower, min_length=min_length, max_length=max_length)
+    return _registered(type('ConstrainedBytesValue', (ConstrainedBytes,), namespace))
 
-        if cls.max_items is not None and v_len > cls.max_items:
-            raise errors.SetMaxLengthError(limit_value=cls.max_items)
 
-        return v
+if TYPE_CHECKING:
+    StrictBytes = bytes
+else:
 
+    class StrictBytes(ConstrainedBytes):
+        strict = True
 
-def conset(item_type: Type[T], *, min_items: int = None, max_items: int = None) -> Type[Set[T]]:
-    # __args__ is needed to conform to typing generics api
-    namespace = {'min_items': min_items, 'max_items': max_items, 'item_type': item_type, '__args__': [item_type]}
-    # We use new_class to be able to deal with Generic types
-    return new_class('ConstrainedSetValue', (ConstrainedSet,), {}, lambda ns: ns.update(namespace))
+
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ STRING TYPES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 
 class ConstrainedStr(str):
@@ -263,7 +368,10 @@ class ConstrainedStr(str):
     @classmethod
     def __modify_schema__(cls, field_schema: Dict[str, Any]) -> None:
         update_not_none(
-            field_schema, minLength=cls.min_length, maxLength=cls.max_length, pattern=cls.regex and cls.regex.pattern
+            field_schema,
+            minLength=cls.min_length,
+            maxLength=cls.max_length,
+            pattern=cls.regex and cls.regex.pattern,
         )
 
     @classmethod
@@ -309,200 +417,135 @@ def constr(
     return _registered(type('ConstrainedStrValue', (ConstrainedStr,), namespace))
 
 
-class StrictStr(ConstrainedStr):
-    strict = True
-
-
 if TYPE_CHECKING:
-    StrictBool = bool
+    StrictStr = str
 else:
 
-    class StrictBool(int):
-        """
-        StrictBool to allow for bools which are not type-coerced.
-        """
-
-        @classmethod
-        def __modify_schema__(cls, field_schema: Dict[str, Any]) -> None:
-            field_schema.update(type='boolean')
-
-        @classmethod
-        def __get_validators__(cls) -> 'CallableGenerator':
-            yield cls.validate
+    class StrictStr(ConstrainedStr):
+        strict = True
 
-        @classmethod
-        def validate(cls, value: Any) -> bool:
-            """
-            Ensure that we only allow bools.
-            """
-            if isinstance(value, bool):
-                return value
 
-            raise errors.StrictBoolError()
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ SET TYPES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
+# This types superclass should be Set[T], but cython chokes on that...
+class ConstrainedSet(set):  # type: ignore
+    # Needed for pydantic to detect that this is a set
+    __origin__ = set
+    __args__: Set[Type[T]]  # type: ignore
 
-class PyObject:
-    validate_always = True
+    min_items: Optional[int] = None
+    max_items: Optional[int] = None
+    item_type: Type[T]  # type: ignore
 
     @classmethod
     def __get_validators__(cls) -> 'CallableGenerator':
-        yield cls.validate
+        yield cls.set_length_validator
 
     @classmethod
-    def validate(cls, value: Any) -> Any:
-        if isinstance(value, Callable):  # type: ignore
-            return value
+    def __modify_schema__(cls, field_schema: Dict[str, Any]) -> None:
+        update_not_none(field_schema, minItems=cls.min_items, maxItems=cls.max_items)
 
-        try:
-            value = str_validator(value)
-        except errors.StrError:
-            raise errors.PyObjectError(error_message='value is neither a valid import path not a valid callable')
+    @classmethod
+    def set_length_validator(cls, v: 'Optional[Set[T]]') -> 'Optional[Set[T]]':
+        if v is None:
+            return None
 
-        try:
-            return import_string(value)
-        except ImportError as e:
-            raise errors.PyObjectError(error_message=str(e))
+        v = set_validator(v)
+        v_len = len(v)
 
-    if TYPE_CHECKING:
+        if cls.min_items is not None and v_len < cls.min_items:
+            raise errors.SetMinLengthError(limit_value=cls.min_items)
 
-        def __call__(self, *args: Any, **kwargs: Any) -> Any:
-            ...
+        if cls.max_items is not None and v_len > cls.max_items:
+            raise errors.SetMaxLengthError(limit_value=cls.max_items)
 
+        return v
 
-class ConstrainedNumberMeta(type):
-    def __new__(cls, name: str, bases: Any, dct: Dict[str, Any]) -> 'ConstrainedInt':  # type: ignore
-        new_cls = cast('ConstrainedInt', type.__new__(cls, name, bases, dct))
 
-        if new_cls.gt is not None and new_cls.ge is not None:
-            raise errors.ConfigError('bounds gt and ge cannot be specified at the same time')
-        if new_cls.lt is not None and new_cls.le is not None:
-            raise errors.ConfigError('bounds lt and le cannot be specified at the same time')
+def conset(item_type: Type[T], *, min_items: int = None, max_items: int = None) -> Type[Set[T]]:
+    # __args__ is needed to conform to typing generics api
+    namespace = {'min_items': min_items, 'max_items': max_items, 'item_type': item_type, '__args__': [item_type]}
+    # We use new_class to be able to deal with Generic types
+    return new_class('ConstrainedSetValue', (ConstrainedSet,), {}, lambda ns: ns.update(namespace))
 
-        return _registered(new_cls)  # type: ignore
 
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ LIST TYPES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-class ConstrainedInt(int, metaclass=ConstrainedNumberMeta):
-    strict: bool = False
-    gt: OptionalInt = None
-    ge: OptionalInt = None
-    lt: OptionalInt = None
-    le: OptionalInt = None
-    multiple_of: OptionalInt = None
+# This types superclass should be List[T], but cython chokes on that...
+class ConstrainedList(list):  # type: ignore
+    # Needed for pydantic to detect that this is a list
+    __origin__ = list
+    __args__: Tuple[Type[T], ...]  # type: ignore
 
-    @classmethod
-    def __modify_schema__(cls, field_schema: Dict[str, Any]) -> None:
-        update_not_none(
-            field_schema,
-            exclusiveMinimum=cls.gt,
-            exclusiveMaximum=cls.lt,
-            minimum=cls.ge,
-            maximum=cls.le,
-            multipleOf=cls.multiple_of,
-        )
+    min_items: Optional[int] = None
+    max_items: Optional[int] = None
+    item_type: Type[T]  # type: ignore
 
     @classmethod
     def __get_validators__(cls) -> 'CallableGenerator':
+        yield cls.list_length_validator
 
-        yield strict_int_validator if cls.strict else int_validator
-        yield number_size_validator
-        yield number_multiple_validator
-
-
-def conint(
-    *, strict: bool = False, gt: int = None, ge: int = None, lt: int = None, le: int = None, multiple_of: int = None
-) -> Type[int]:
-    # use kwargs then define conf in a dict to aid with IDE type hinting
-    namespace = dict(strict=strict, gt=gt, ge=ge, lt=lt, le=le, multiple_of=multiple_of)
-    return type('ConstrainedIntValue', (ConstrainedInt,), namespace)
-
-
-class PositiveInt(ConstrainedInt):
-    gt = 0
-
-
-class NegativeInt(ConstrainedInt):
-    lt = 0
-
-
-class NonPositiveInt(ConstrainedInt):
-    le = 0
-
-
-class NonNegativeInt(ConstrainedInt):
-    ge = 0
+    @classmethod
+    def __modify_schema__(cls, field_schema: Dict[str, Any]) -> None:
+        update_not_none(field_schema, minItems=cls.min_items, maxItems=cls.max_items)
 
+    @classmethod
+    def list_length_validator(cls, v: 'Optional[List[T]]') -> 'Optional[List[T]]':
+        if v is None:
+            return None
 
-class StrictInt(ConstrainedInt):
-    strict = True
+        v = list_validator(v)
+        v_len = len(v)
 
+        if cls.min_items is not None and v_len < cls.min_items:
+            raise errors.ListMinLengthError(limit_value=cls.min_items)
 
-class ConstrainedFloat(float, metaclass=ConstrainedNumberMeta):
-    strict: bool = False
-    gt: OptionalIntFloat = None
-    ge: OptionalIntFloat = None
-    lt: OptionalIntFloat = None
-    le: OptionalIntFloat = None
-    multiple_of: OptionalIntFloat = None
+        if cls.max_items is not None and v_len > cls.max_items:
+            raise errors.ListMaxLengthError(limit_value=cls.max_items)
 
-    @classmethod
-    def __modify_schema__(cls, field_schema: Dict[str, Any]) -> None:
-        update_not_none(
-            field_schema,
-            exclusiveMinimum=cls.gt,
-            exclusiveMaximum=cls.lt,
-            minimum=cls.ge,
-            maximum=cls.le,
-            multipleOf=cls.multiple_of,
-        )
-        # Modify constraints to account for differences between IEEE floats and JSON
-        if field_schema.get('exclusiveMinimum') == -math.inf:
-            del field_schema['exclusiveMinimum']
-        if field_schema.get('minimum') == -math.inf:
-            del field_schema['minimum']
-        if field_schema.get('exclusiveMaximum') == math.inf:
-            del field_schema['exclusiveMaximum']
-        if field_schema.get('maximum') == math.inf:
-            del field_schema['maximum']
+        return v
 
-    @classmethod
-    def __get_validators__(cls) -> 'CallableGenerator':
-        yield strict_float_validator if cls.strict else float_validator
-        yield number_size_validator
-        yield number_multiple_validator
 
+def conlist(item_type: Type[T], *, min_items: int = None, max_items: int = None) -> Type[List[T]]:
+    # __args__ is needed to conform to typing generics api
+    namespace = {'min_items': min_items, 'max_items': max_items, 'item_type': item_type, '__args__': (item_type,)}
+    # We use new_class to be able to deal with Generic types
+    return new_class('ConstrainedListValue', (ConstrainedList,), {}, lambda ns: ns.update(namespace))
 
-def confloat(
-    *,
-    strict: bool = False,
-    gt: float = None,
-    ge: float = None,
-    lt: float = None,
-    le: float = None,
-    multiple_of: float = None,
-) -> Type[float]:
-    # use kwargs then define conf in a dict to aid with IDE type hinting
-    namespace = dict(strict=strict, gt=gt, ge=ge, lt=lt, le=le, multiple_of=multiple_of)
-    return type('ConstrainedFloatValue', (ConstrainedFloat,), namespace)
 
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PYOBJECT TYPE ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-class PositiveFloat(ConstrainedFloat):
-    gt = 0
 
+if TYPE_CHECKING:
+    # TODO: add `str` and support it thanks to the plugin
+    # PyObject = Union[str, Callable[..., Any]]
+    PyObject = Callable[..., Any]
+else:
 
-class NegativeFloat(ConstrainedFloat):
-    lt = 0
+    class PyObject:
+        validate_always = True
 
+        @classmethod
+        def __get_validators__(cls) -> 'CallableGenerator':
+            yield cls.validate
 
-class NonPositiveFloat(ConstrainedFloat):
-    le = 0
+        @classmethod
+        def validate(cls, value: Any) -> Any:
+            if isinstance(value, Callable):
+                return value
 
+            try:
+                value = str_validator(value)
+            except errors.StrError:
+                raise errors.PyObjectError(error_message='value is neither a valid import path not a valid callable')
 
-class NonNegativeFloat(ConstrainedFloat):
-    ge = 0
+            try:
+                return import_string(value)
+            except ImportError as e:
+                raise errors.PyObjectError(error_message=str(e))
 
 
-class StrictFloat(ConstrainedFloat):
-    strict = True
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ DECIMAL TYPES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 
 class ConstrainedDecimal(Decimal, metaclass=ConstrainedNumberMeta):
@@ -586,62 +629,77 @@ def condecimal(
     return type('ConstrainedDecimalValue', (ConstrainedDecimal,), namespace)
 
 
-class UUID1(UUID):
-    _required_version = 1
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ UUID TYPES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-    @classmethod
-    def __modify_schema__(cls, field_schema: Dict[str, Any]) -> None:
-        field_schema.update(type='string', format=f'uuid{cls._required_version}')
+if TYPE_CHECKING:
+    UUID1 = Union[UUID, str]
+    UUID3 = Union[UUID, str]
+    UUID4 = Union[UUID, str]
+    UUID5 = Union[UUID, str]
+else:
 
+    class UUID1(UUID):
+        _required_version = 1
 
-class UUID3(UUID1):
-    _required_version = 3
+        @classmethod
+        def __modify_schema__(cls, field_schema: Dict[str, Any]) -> None:
+            field_schema.update(type='string', format=f'uuid{cls._required_version}')
 
+    class UUID3(UUID1):
+        _required_version = 3
 
-class UUID4(UUID1):
-    _required_version = 4
+    class UUID4(UUID1):
+        _required_version = 4
 
+    class UUID5(UUID1):
+        _required_version = 5
 
-class UUID5(UUID1):
-    _required_version = 5
 
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PATH TYPES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-class FilePath(Path):
-    @classmethod
-    def __modify_schema__(cls, field_schema: Dict[str, Any]) -> None:
-        field_schema.update(format='file-path')
+if TYPE_CHECKING:
+    FilePath = Union[Path, str]
+    DirectoryPath = Union[Path, str]
+else:
 
-    @classmethod
-    def __get_validators__(cls) -> 'CallableGenerator':
-        yield path_validator
-        yield path_exists_validator
-        yield cls.validate
+    class FilePath(Path):
+        @classmethod
+        def __modify_schema__(cls, field_schema: Dict[str, Any]) -> None:
+            field_schema.update(format='file-path')
 
-    @classmethod
-    def validate(cls, value: Path) -> Path:
-        if not value.is_file():
-            raise errors.PathNotAFileError(path=value)
+        @classmethod
+        def __get_validators__(cls) -> 'CallableGenerator':
+            yield path_validator
+            yield path_exists_validator
+            yield cls.validate
 
-        return value
+        @classmethod
+        def validate(cls, value: Path) -> Path:
+            if not value.is_file():
+                raise errors.PathNotAFileError(path=value)
 
+            return value
 
-class DirectoryPath(Path):
-    @classmethod
-    def __modify_schema__(cls, field_schema: Dict[str, Any]) -> None:
-        field_schema.update(format='directory-path')
+    class DirectoryPath(Path):
+        @classmethod
+        def __modify_schema__(cls, field_schema: Dict[str, Any]) -> None:
+            field_schema.update(format='directory-path')
 
-    @classmethod
-    def __get_validators__(cls) -> 'CallableGenerator':
-        yield path_validator
-        yield path_exists_validator
-        yield cls.validate
+        @classmethod
+        def __get_validators__(cls) -> 'CallableGenerator':
+            yield path_validator
+            yield path_exists_validator
+            yield cls.validate
 
-    @classmethod
-    def validate(cls, value: Path) -> Path:
-        if not value.is_dir():
-            raise errors.PathNotADirectoryError(path=value)
+        @classmethod
+        def validate(cls, value: Path) -> Path:
+            if not value.is_dir():
+                raise errors.PathNotADirectoryError(path=value)
+
+            return value
 
-        return value
+
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ JSON TYPE ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 
 class JsonWrapper:
@@ -653,10 +711,17 @@ def __getitem__(self, t: Type[Any]) -> Type[JsonWrapper]:
         return _registered(type('JsonWrapperValue', (JsonWrapper,), {'inner_type': t}))
 
 
-class Json(metaclass=JsonMeta):
-    @classmethod
-    def __modify_schema__(cls, field_schema: Dict[str, Any]) -> None:
-        field_schema.update(type='string', format='json-string')
+if TYPE_CHECKING:
+    Json = str
+else:
+
+    class Json(metaclass=JsonMeta):
+        @classmethod
+        def __modify_schema__(cls, field_schema: Dict[str, Any]) -> None:
+            field_schema.update(type='string', format='json-string')
+
+
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ SECRET TYPES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 
 class SecretStr:
@@ -759,6 +824,9 @@ def get_secret_value(self) -> bytes:
         return self._secret_value
 
 
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PAYMENT CARD TYPES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+
 class PaymentCardBrand(str, Enum):
     # If you add another card type, please also add it to the
     # Hypothesis strategy in `pydantic._hypothesis_plugin`.
@@ -861,6 +929,8 @@ def _get_brand(card_number: str) -> PaymentCardBrand:
         return brand
 
 
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ BYTE SIZE TYPE ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
 BYTE_SIZES = {
     'b': 1,
     'kb': 10 ** 3,


Test patch:
diff --git a/tests/mypy/modules/success.py b/tests/mypy/modules/success.py
--- a/tests/mypy/modules/success.py
+++ b/tests/mypy/modules/success.py
@@ -6,9 +6,35 @@
 import json
 import sys
 from datetime import date, datetime
+from pathlib import Path
 from typing import Any, Dict, Generic, List, Optional, TypeVar
-
-from pydantic import BaseModel, NoneStr, PyObject, StrictBool, root_validator, validate_arguments, validator
+from uuid import UUID
+
+from pydantic import (
+    UUID1,
+    BaseModel,
+    DirectoryPath,
+    FilePath,
+    Json,
+    NegativeFloat,
+    NegativeInt,
+    NoneStr,
+    NonNegativeFloat,
+    NonNegativeInt,
+    NonPositiveFloat,
+    NonPositiveInt,
+    PositiveFloat,
+    PositiveInt,
+    PyObject,
+    StrictBool,
+    StrictBytes,
+    StrictFloat,
+    StrictInt,
+    StrictStr,
+    root_validator,
+    validate_arguments,
+    validator,
+)
 from pydantic.fields import Field, PrivateAttr
 from pydantic.generics import GenericModel
 from pydantic.typing import ForwardRef
@@ -160,3 +186,37 @@ class MyConf(BaseModel):
 
 class MyPrivateAttr(BaseModel):
     _private_field: str = PrivateAttr()
+
+
+class PydanticTypes(BaseModel):
+    # Boolean
+    my_strict_bool: StrictBool = True
+    # Integer
+    my_positive_int: PositiveInt = 1
+    my_negative_int: NegativeInt = -1
+    my_non_positive_int: NonPositiveInt = -1
+    my_non_negative_int: NonNegativeInt = 1
+    my_strict_int: StrictInt = 1
+    # Float
+    my_positive_float: PositiveFloat = 1.1
+    my_negative_float: NegativeFloat = -1.1
+    my_non_positive_float: NonPositiveFloat = -1.1
+    my_non_negative_float: NonNegativeFloat = 1.1
+    my_strict_float: StrictFloat = 1.1
+    # Bytes
+    my_strict_bytes: StrictBytes = b'pika'
+    # String
+    my_strict_str: StrictStr = 'pika'
+    # PyObject
+    # TODO: my_pyobject_str: PyObject = 'datetime.date'
+    my_pyobject_callable: PyObject = date
+    # UUID
+    my_uuid1: UUID1 = UUID('a8098c1a-f86e-11da-bd1a-00112444be1e')
+    my_uuid1_str: UUID1 = 'a8098c1a-f86e-11da-bd1a-00112444be1e'
+    # Path
+    my_file_path: FilePath = Path('root') / 'myfile.txt'
+    my_file_path_str: FilePath = 'root/myfile.txt'
+    my_dir_path: DirectoryPath = Path('root') / 'mydir'
+    my_dir_path_str: DirectoryPath = 'root/mydir'
+    # Json
+    my_json: Json = '{"hello": "world"}'