Skip to content

Commit

Permalink
More Inspector features. Introduced inspect_type function to replace …
Browse files Browse the repository at this point in the history
…can_validate in its TypeInspector-returning behaviour.
  • Loading branch information
sg495 committed Feb 21, 2024
1 parent 9b2e8d4 commit 98094b7
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 16 deletions.
1 change: 1 addition & 0 deletions docs/api/typing_validation.inspector.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ TypeInspector
.. autoclass:: typing_validation.inspector.TypeInspector
:show-inheritance:
:members:
:special-members: __repr__

UnsupportedType
---------------
Expand Down
5 changes: 5 additions & 0 deletions docs/api/typing_validation.validation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ can_validate

.. autofunction:: typing_validation.validation.can_validate

inspect_type
------------

.. autofunction:: typing_validation.validation.inspect_type

is_valid
--------

Expand Down
55 changes: 49 additions & 6 deletions typing_validation/inspector.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@
else:
TypeConstructorArgs = typing.Tuple[str, Any]

if sys.version_info[1] >= 11:
from typing import Self
else:
from typing_extensions import Self

_typing_equiv = {
list: typing.List,
tuple: typing.Tuple,
Expand Down Expand Up @@ -113,7 +118,7 @@ class TypeInspector:
"_pending_generic_type_constr",
)

def __new__(cls) -> "TypeInspector":
def __new__(cls) -> Self:
instance = super().__new__(cls)
instance._recorded_constructors = []
instance._unsupported_types = []
Expand All @@ -134,6 +139,34 @@ def unsupported_types(self) -> typing.Tuple[Any, ...]:
r"""The sequence of unsupported types encountered during validation."""
return tuple(self._unsupported_types)

@property
def type_structure(self) -> str:
"""
The structure of the recorded type:
1. The string spans multiple lines, with indentation levels matching
the nesting level of inner types.
2. Any unsupported types encountered are wrapped using the generic type
:obj:`UnsupportedType`.
"""
return "\n".join(self._repr()[0])

@property
def type_annotation(self) -> str:
"""
The type annotation for the recorded type.
Differs from the output of :attr:`type_structure` in the following ways:
1. The annotation is on a single line.
2. Unsupported types are not wrapped.
"""
return "".join(
line.strip()
for line in self._repr(mark_unsupported=False)[0]
)

def _recorded_type(self, idx: int) -> typing.Tuple[Any, int]:
# pylint: disable = too-many-return-statements, too-many-branches
param: Any
Expand Down Expand Up @@ -297,15 +330,23 @@ def __bool__(self) -> bool:
return not self._unsupported_types

def __repr__(self) -> str:
# addr = "0x"+f"{id(self):x}"
header = f"The following type can{'' if self else 'not'} be validated against:"
return header + "\n" + "\n".join(self._repr()[0])
"""
Representation of the inspector, including the :attr:`type_structure`.
:meta public:
"""
return (
"TypeInspector instance for the following type:\n"
+self.type_structure
)

def _repr(
self, idx: int = 0, level: int = 0
self, idx: int = 0, level: int = 0,
*,
mark_unsupported: bool = True
) -> typing.Tuple[typing.List[str], int]:
# pylint: disable = too-many-return-statements, too-many-branches, too-many-statements, too-many-locals
basic_indent = " "
basic_indent = " "
assert len(basic_indent) >= 2
indent = basic_indent * level
next_indent = basic_indent * (level + 1)
Expand All @@ -314,6 +355,8 @@ def _repr(
lines: typing.List[str]
tag, param = self._recorded_constructors[idx]
if tag == "unsupported":
if not mark_unsupported:
return [indent+str(param)], idx
return [
indent + "UnsupportedType[",
indent + " " + str(param),
Expand Down
41 changes: 31 additions & 10 deletions typing_validation/validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -904,29 +904,50 @@ def validate(val: Any, t: Any) -> Literal[True]:


def can_validate(t: Any) -> TypeInspector:
r"""
Checks whether validation is supported for the given type ``t``: if not, :func:`validate` will raise :obj:`UnsupportedTypeError`.
"""
Checks whether validation is supported for the given type ``t``: if not,
:func:`validate` will raise :obj:`UnsupportedTypeError`.
The returned :class:`TypeInspector` instance can be used wherever a boolean is expected, and will indicate whether the type is supported or not:
.. warning::
The return type will be changed to :obj:`bool` in v1.3.0.
To obtain a :class:`TypeInspector` object, please use the newly
introduced :func:`inspect_type` instead.
:param t: the type to be checked for validation support
:type t: :obj:`~typing.Any`
"""
inspector = TypeInspector()
validate(inspector, t)
return inspector


def inspect_type(t: Any) -> TypeInspector:
r"""
Returns a :class:`TypeInspector` instance can be used wherever a boolean is
expected, and will indicate whether the type is supported or not:
>>> from typing import *
>>> from typing_validation import can_validate
>>> res = can_validate(tuple[list[str], Union[int, float, Callable[[int], int]]])
>>> from typing_validation import inspect_type
>>> res = inspect_type(tuple[list[str], Union[int, float, Callable[[int], int]]])
>>> bool(res)
False
However, it also records (with minimal added cost) the full structure of the type as the latter was validated, which it then exposes via its
The instance also records (with minimal added cost) the full structure of
the type as the latter was validated, which it then exposes via its
:attr:`TypeInspector.recorded_type` property:
>>> res = can_validate(tuple[list[Union[str, int]],...])
>>> res = inspect_type(tuple[list[Union[str, int]],...])
>>> bool(res)
True
>>> res.recorded_type
tuple[list[typing.Union[str, int]], ...]
Any unsupported subtype encountered during the validation is left in place, wrapped into an :class:`UnsupportedType`:
Any unsupported subtype encountered during the validation is left in place,
wrapped into an :class:`UnsupportedType`:
>>> can_validate(tuple[list[str], Union[int, float, Callable[[int], int]]])
>>> inspect_type(tuple[list[str], Union[int, float, Callable[[int], int]]])
The following type cannot be validated against:
tuple[
list[
Expand All @@ -945,7 +966,7 @@ def can_validate(t: Any) -> TypeInspector:
:param t: the type to be checked for validation support
:type t: :obj:`~typing.Any`
:raises AssertionError: if things go unexpectedly wrong with ``__args__`` for parametric types
"""
inspector = TypeInspector()
validate(inspector, t)
Expand Down

0 comments on commit 98094b7

Please sign in to comment.