MyType: A Powerful-yet-constrained Type Wrapper#

class my.typing.MyType.MyType(root: TypeArg[R] = typing.Any, uid: int = 0, *, raw: Any = <class 'NoneType'>, main: type[T] | type[Union] | type[None] | None = None, name: str = '', args: tuple[~my.typing.MyType.MyType, ...]=(), vals: MyType | None = None, keys: MyType | None = None, origin: type | None = None, literal_members: list[Any] = [])#

A wrapper for any type annotation that normalizes the wide variety of interfaces.

Examples

Consider this simplified subset of the hierarchy:

str_t = MyType(str)
bytes_t = MyType(bytes)
int_t = MyType(int)
float_t = MyType(float)

String = str | bytes
string_t = MyType(String)
Scalar = int | float
scalar_t = MyType(Scalar)

Atom = str | int | bytes | float
atom_t = MyType(Atom)

``__and__()``

# Affirms if either is part of the other, fails otherwise
assert str_t & atom_t
assert atom_t & str_t
assert not str_t & int_t

# Works with a raw *plain type* on either side, as long as the other side is wrapped
assert str & atom_t and str_t & Atom
assert atom_t & str
#! assert str & Atom  # two raw annotations know nothing of each other
#! assert Atom & str_t  # a raw union LHS cannot reflect onto the wrapped RHS

# Works with unions and tuples of plain types (when LHS is wrapped)
assert str_t & (str | Scalar)
assert str_t & (str, int)
assert not str_t & (bytes, int)

``__contains__()``

# Determines whether the LHS is a subset of the RHS, but not the other way around.
assert str in atom_t
assert Atom not in str_t

# RHS must be a MyType.
#! assert str in Atom

# Works with tuples
assert str in (str_t, Scalar)

“Main” types (i.e. MyType.main)

The main type of an instance represents the most meaningfully active part of that type in this moment. Some cases:

assert MyType(dict[str, int]).main is dict
assert MyType(Literal['a', 'b']).main is None
assert MyType(Optional[int]).is_split
Some cases:
  • Basic generics use their origins, while the vast majority of Atoms use themselves in full.

  • Literals use Literal.

  • Monotomic “Special Forms” (e.g. Annotated[int, ...], Final[str]) use a type or union representing the wrapped content of the inner form.

  • Polytomic “Special Forms” (e.g. Optional[int] / Union[int, str] -> UnionType, Unpack[str, str] -> Unpack) use a type representing the wrapping content of the outer form.

I Initial Methods#

static MyType.new() → MyType[Any]#
static MyType.new(root: None) → MyType[NoneType]
static MyType.new(root: tuple[type[R], ...]) → MyType[type[R] | ...]
static MyType.new(root: TypeArg[R]) → MyType[R]
static MyType.new(root: type[R] | MyType[R]) → MyType[R]
static MyType.new(root: R) → MyType[R]

Create a new MyType instance by parsing a type OR inferring the full type of a value.

Examples

Types parse; values infer:

>>> from my import MyType
>>> MyType.new(int)
MyType[<class 'int'>](main=<class 'int'>)
>>> MyType.new(5)
MyType[<class 'int'>](main=<class 'int'>)
classmethod MyType.parse(root: Rm, throw: bool = False) → Rm#
classmethod MyType.parse(root: type[R], throw: bool = False) → MyType[R]
classmethod MyType.parse(root: Rt, throw: bool = False) → MyType[Rt]
classmethod MyType.parse(root: Union, throw: bool = False) → MyType
classmethod MyType.parse(root: TypeAliasType, throw: bool = False) → MyType
classmethod MyType.parse(root: None, throw: bool = False) → MyType[NoneType]
classmethod MyType.parse(root: object, throw: bool = False) → MyType

Decompose a given type so that other methods can intelligently handle each part in turn.

By far the most likely usecase is for containers such as dict[str, int] (which becomes the tuple (dict, str, int)) and list[int] (which becomes (list, int, None)), but it’s useful for other generics, unions (e.g. string | int), and special non-type forms (e.g. Annotated and Literal).

Parameters:
  • root – The type annotation to decompose – either a type, a union of types, or None.

  • throw – If True, will re-raise any exceptions encountered during parsing.

Returns:

A MyType instance with the root set to the original type, and the other fields populated according to the structure of that type.

Examples

Decompose parameterized generics into their parts:

>>> from my import MyType
>>> MyType.parse(dict[str, int]).summarize()
(<class 'dict'>, <class 'str'>, <class 'int'>)
>>> MyType.parse(int | str).is_split
True
classmethod MyType.typeof(data: R) → MyType[TypeVar]#

Infer the type annotation of a given data value, recursing into containers.

Parameters:

data – Data value to infer type from.

Returns:

Parsed MyType instance representing the inferred type.

Examples

Infer full container annotations from runtime values:

>>> from my import MyType
>>> str(MyType.typeof({'a': [1]}))
'dict[str, list[int]]'
property MyType.none: MyType[NoneType]#

A MyType instance that only matches None.

property MyType.all: MyType[Any]#

A MyType instance that matches everything.

II Properties#

property MyType.is_split: bool#

Whether this type is a union of multiple types.

property MyType.rtype: type | Union#

A version of the root that has been lightly coerced into being a regular type.

Plain types and unions return themselves, and an Unpack unwraps to its content. Otherwise a split type collapses to the bare UnionType, a parameterized generic yields its first argument’s root (e.g. dict[str, int] -> str, not dict), and anything else falls back to Any.

III Operators#

MyType.__contains__(child: None) → TypeIs[None]#
MyType.__contains__(child: type) → TypeIs[type[T]]
MyType.__contains__(child: MyType) → TypeIs[MyType[T]]
MyType.__contains__(child: tuple[type, ...]) → TypeIs[tuple[type[T], ...]]
MyType.__contains__(child: object) → TypeIs[type[T]]

Determine whether the preceding type is a valid subset of this one.

MyType.__and__(other: None) → TypeIs[None]#
MyType.__and__(other: type) → TypeIs[type[T]]
MyType.__and__(other: MyType) → TypeIs[MyType[T]]
MyType.__and__(other: tuple[type, ...]) → TypeIs[tuple[type[T], ...]]

Determine whether either of the two types contains the other.

MyType.__rand__(other: None) → TypeIs[None]#
MyType.__rand__(other: type) → TypeIs[type[T]]
MyType.__rand__(other: MyType) → TypeIs[MyType[T]]
MyType.__rand__(other: tuple[type, ...]) → TypeIs[tuple[type[T], ...]]
MyType.__rand__(other: object) → TypeIs[type[T]]

Determine whether either of the two types contains the other (reflected form).

IV Application#

MyType.match(other: None) → TypeIs[None]#
MyType.match(other: type) → TypeIs[type[T]]
MyType.match(other: MyType) → TypeIs[MyType[T]]
MyType.match(other: tuple[type, ...]) → TypeIs[tuple[type[T], ...]]
MyType.match(other: object) → TypeIs[type[T]]

Determine whether the given type value is a subset of this type.

Examples

Membership runs from the argument into this type:

>>> from my import MyType
>>> MyType(int | str).match(int)
True
>>> MyType(int).match(int | str)
False
MyType.check(data: object) → TypeIs[T]#

Determine whether a given data value matches this type, recursing into containers.

Parameters:

data – The data value to check. Ideally not an exhaustable iter.

Returns:

True if all aspects of this type are satisfied by this data, including nested types.

Examples

Validate values against the parsed type:

>>> from my import MyType
>>> t = MyType.parse(dict[str, int])
>>> t.check({'a': 1}), t.check({'a': 'b'})
(True, False)
MyType.check_iter(data: Iterable) → Iterator[bool]#

Yield a boolean for each element of data indicating if it matches this type.

Parameters:

data – The iterable of values to check. Ideally not an exhaustable iter.

Yields:

One boolean per element, True if that element matches this type.

MyType.literal_check(val: object) → bool#

Determine whether a value satisfies this Literal or tuple-literal type.

MyType.is_map_item() → bool#

Whether this type is a map item: a bare tuple or a tuple of exactly two types.

MyType.members() → Iterator[MyType]#

Yield all field types for Pydantic models or TypedDicts.

Returns:

Iterator of MyType instances for each field in the type.

MyType.summarize() → tuple[type | None, type | None, type | None]#

Get a simplified summary of this type with just the main types.

Returns:

Tuple of (main_type, keys, value_type) where key and value are extracted from their respective MyType wrappers.

property MyType.idx: str#

The hierarchy ID of the most specific registered IDXS type that matches this one.

Scans the registered index (longest keys first) and returns the ID of the first entry whose type matches (is a subset of) this one, or ‘0’ when none do.