generics

Utilities for generic type introspection and resolution.

This module wraps the Python typing machinery (and some Pydantic internals) to make it easy to:

  • inspect the parameters declared on a generic class or alias,

  • examine how those parameters are specialised on subclasses and aliases,

  • follow argument bindings through entire inheritance hierarchies, and

  • expose reusable descriptors that resolve type arguments on demand.

These helper methods raise GenericsError when the caller provides inputs that make resolution impossible (for example, asking for a parameter that was never declared, or querying a non-generic class).

Repeated lookups are cached so the helpers remain inexpensive even when used repeatedly.

Examples

1. Inspect concrete types of generic type parameters

Parameters declared by a generic class or alias can be inspected using get_parameter_infos():

>>> class Parent[T]:
...     pass
>>> class Child(Parent[list[int]]):
...     pass
>>> from app.util.helpers import generics
>>> generics.get_concrete_parent_argument(Child, Parent, "T")
list[int]

If you rely on forward references, note that this library might not be able to resolve them:

>>> class SomeClass(Parent["AnotherClass"]): ...
>>> generics.get_concrete_parent_argument(SomeClass, Parent, "T", bound=False)
Traceback (most recent call last):
  ...
GenericsError: Could not resolve ForwardRef('AnotherClass') to a concrete type. Consider using `register_forwardref_type`.

In order to automatically resolve forward references, you may register types with the library:

>>> def another_scope():
...     class AnotherClass: ...
...
...     generics.register_type(AnotherClass)

Now the library can resolve the forward reference:

>>> another_scope()
>>> generics.get_concrete_parent_argument(SomeClass, Parent, "T")
<class '...AnotherClass'>

2. Class descriptors for generic type parameter introspection

Classes can expose reusable introspection class methods by using GenericIntrospectionMethod:

>>> from app.util.helpers import generics
>>> class Repository[T]:
...     item_type        = generics.GenericIntrospectionMethod[T]()
...     item_type_origin = generics.GenericIntrospectionMethod[T](origin=True)
>>> class StrRepository(Repository[str]):
...     pass
>>> StrRepository.item_type()
<class 'str'>
>>> class IntListRepository(Repository[list[int]]):
...     pass
>>> IntListRepository.item_type()
list[int]
>>> IntListRepository.item_type_origin()
<class 'list'>

Instances can also invoke the descriptor directly:

>>> IntListRepository().item_type()
list[int]

The descriptor enforces type argument bounds if present:

>>> class Animal: ...
>>> class Dog(Animal): ...
>>> class AnimalRepository[T: Animal](Repository[T]):
...     animal_type = generics.GenericIntrospectionMethod[T]()
>>> AnimalRepository.animal_type()
<class 'app.util.helpers.generics.Animal'>
>>> class IntRepository(AnimalRepository[int]): ...
>>> IntRepository.animal_type()
Traceback (most recent call last):
  ...
GenericsError: IntRepository.T type argument <class 'int'> is not a subclass of <class '__main__.Animal'>

This can be turned off by passing bound=False to the descriptor instantiation or the method call:

>>> IntRepository.animal_type(bound=False)
<class 'int'>

You can also pass a source alias to cover cases where cls has lost its generic specialisation:

>>> alias = Repository[int]
>>> Repository.item_type(source=alias)
<class 'int'>

See test/util/helpers/test_generic_introspection_method.py for more involved scenarios.

Functions

bound_to_str(bound)

Return a human-friendly representation of bound.

evaluate_forwardref(ref, *[, owner, namespace])

get_argument(cls, param)

Return the raw type argument bound to param for cls.

get_argument_info(cls, param, *[, ...])

Return the ArgumentInfo for param or fall back to the raw TypeVar.

get_argument_info_or_none(cls, param, *[, ...])

Return the ArgumentInfo for param if the binding exists on cls.

get_argument_infos(cls, *[, fail, args])

Return a cached mapping of parameter names to ArgumentInfo for cls.

get_bases_between(cls, parent)

Return the inheritance chain between cls and parent, inclusive.

get_bases_between_or_none(cls, parent[, result])

Return the inheritance chain between cls and parent, inclusive.

get_concrete_argument(cls, param)

Return the concrete origin type bound to param for cls.

get_concrete_parent_argument(cls, parent, ...)

Return the concrete argument resolved for param from parent within cls.

get_concrete_parent_argument_origin(cls, ...)

Return the origin type for the concrete argument resolved from parent.

get_generic_base(cls)

Return the generic base class for cls.

get_generic_base_or_none(cls)

Return the generic base class for cls if one exists.

get_origin(cls, *[, passthrough])

Return the typing origin for cls or cls itself when passthrough is True.

get_origin_or_none(cls)

Return the typing origin for cls or None when not generic.

get_original_bases(cls)

Return the tuple of original bases for cls, resolving aliases.

get_parameter_info(cls, name_position_or_typevar)

Resolve name_or_typevar to a ParameterInfo.

get_parameter_info_or_none(cls, ...[, ...])

Return a specific ParameterInfo by name, position, or TypeVar if present.

get_parameter_infos(cls, *[, fail])

Return a mapping of parameter names to ParameterInfo for cls.

get_parent_argument(cls, parent, param, **kwargs)

Return the value or bound resolved for param on parent within cls.

get_parent_argument_info(cls, parent, param, *)

Return the resolved ArgumentInfo for param from parent.

get_parent_argument_info_or_none(cls, ...[, ...])

Return the final ArgumentInfo for param inherited from parent if any.

get_parent_argument_infos(cls, parent, param, *)

Return the cached sequence of argument bindings from cls to parent.

get_parent_argument_or_none(cls, parent, ...)

Return the value or bound resolved for param on parent within cls if any.

has_parameter(cls, param)

Return True if cls defines a generic parameter matching param.

is_generic(cls)

Return whether cls is a generic class.

is_pydantic_model(cls)

Return whether cls resolves to a Pydantic BaseModel subclass.

iter_argument_infos(cls, *[, fail, args])

Yield ArgumentInfo values for each generic parameter on cls.

iter_parameter_infos(cls, *[, fail])

Yield ParameterInfo entries for each generic parameter of cls.

iter_parent_argument_infos(cls, parent, param, *)

Yield argument bindings for param across the inheritance chain to parent.

register_type(cls, *[, name])

Add a named class to the generics types namespace.

unregister_type(target)

Remove a named class from the generics types namespace.

Classes

ArgumentInfo

Metadata describing the argument bound to a ParameterInfo.

GenericIntrospectionMethod

Descriptor that resolves generic parent arguments on demand.

GetConcreteParentArgumentKwargs

Typed keyword arguments accepted by get_concrete_parent_argument.

GetParentArgumentKwargs

Typed keyword arguments accepted by get_parent_argument.

ParameterInfo

Metadata describing a generic type parameter for a class or alias.

Exceptions

GenericsError

Signals incorrect generic usage outside the helpers' control.