Macrotype
Consider this:
VAL = 42
def get() -> type(VAL):
return VAL
This is perfectly valid Python, but type checkers don’t accept it. Instead, you are supposed to write out all type references statically or use very limited global aliases.
macrotype makes this work exactly as you expect with all static type
checkers, so your types can be as dynamic as the rest of your Python code.
How?
macrotype is a CLI tool intended to be run before static type checking:
macrotype your_module
macrotype imports your modules under normal Python and then generates
corresponding .pyi files with all types pinned statically so the type
checker can understand them.
In our example, macrotype would generate this:
VAL: int
def get() -> int: ...
macrotype is the bridge between static type checkers and your dynamic
code. macrotype will import your modules as Python and then re-export the
runtime types back out into a form that static type checkers can consume.
What else?
In addition to the CLI tool, there are also helpers for generating dynamic
types. See macrotype.meta_types. These are intended for you to import to
enable dynamic programming patterns which would be unthinkable without
macrotype.
Type checking
Most users will want to run their static type checker through the
macrotype.check entrypoint. This wrapper generates stub files and then
invokes your checker with PYTHONPATH configured so the generated stubs are
found. The console script is installed as macrotype-check and accepts the
checker command followed by the paths to stub. Any additional arguments after
-- are passed through to the checker:
macrotype-check mypy src/ -- --strict
Stubs are written to __macrotype__ by default; use -o to choose a
different directory. When run with mypy, macrotype-check prepends the
stub directory to MYPYPATH so the overlay stubs are picked up
automatically. Other tools receive the stub directory on PYTHONPATH.
If you run mypy without macrotype-check, set MYPYPATH or pass
--custom-typeshed-dir to point at the stub directory so it behaves the same
way.
Watch mode
Use --watch (or -w) to regenerate stubs whenever the source files
change. The command is re-run in a fresh Python process each time:
macrotype --watch your_module
The same flag is available for macrotype-check to rerun the wrapped type
checker as files change.
Dogfooding
The macrotype project uses the CLI on itself. Running:
python -m macrotype macrotype
regenerates the stub files for the package in place. A CI job ensures that the
checked in .pyi files are always in sync with this command.
Documentation
Full documentation is available on Read the Docs.
To build the documentation locally, install the project with the doc optional dependency:
pip install .[doc]
sphinx-build docs docs/_build
Generic type handling
macrotype can parse generic classes that lack built‑in handlers. In this
case macrotype.types_ast.parse_type() produces a TypeNode containing a
GenericNode capturing the original class and its type arguments. Libraries
can customize the result by providing an on_generic callback:
from collections import deque
from typing import Deque
from macrotype.types_ast import GenericNode, ListNode, parse_type
def handle(node: GenericNode):
if node.origin is deque:
return ListNode(node.args[0])
return node
parse_type(Deque[int], on_generic=handle)
Dynamic annotations
all_annotations collects __annotations__ from a class and its bases.
With a comprehension you can derive new annotations using standard Python:
from typing import Final
from macrotype.meta_types import all_annotations
class Cls:
a: int
b: str | None
class FinalCls:
__annotations__ = {k: Final[v] for k, v in all_annotations(Cls).items()}
class OptionalCls:
__annotations__ = {k: v | None for k, v in all_annotations(Cls).items()}
class OmittedCls:
__annotations__ = {k: v for k, v in all_annotations(Cls).items() if k != "b"}
class ReplacedCls:
__annotations__ = {**all_annotations(Cls), "a": str}
Module Documentation
Utilities for extracting type information and generating stub files.
- exception macrotype.InvalidTypeError(message: str, *, hint: str | None = None, file: str | None = None, line: int | None = None)
Exception raised for invalid typing constructs.
- class macrotype.PyiClass(name: 'str', bases: 'list[str]' = <factory>, type_params: 'list[str]' = <factory>, body: 'list[PyiElement]' = <factory>, typeddict_total: 'bool | None' = None, decorators: 'list[str]' = <factory>, *, used_types: 'set[type]' = <factory>, line: 'int | None' = None)
-
- render(indent: int = 0) list[str]
Return the lines for this element indented by
indentlevels.
- class macrotype.PyiElement
Abstract representation of an element in a
.pyifile.- render(indent: int = 0) list[str]
Return the lines for this element indented by
indentlevels.
- class macrotype.PyiFunction(name: 'str', args: 'list[tuple[str, str | None]]', return_type: 'str' = '', decorators: 'list[str]' = <factory>, type_params: 'list[str]' = <factory>, is_async: 'bool' = False, *, used_types: 'set[type]' = <factory>, line: 'int | None' = None)
- classmethod from_function(fn: Callable, decorators: list[str] | None = None, exclude_params: set[str] | None = None, *, skip_final: bool = False, globalns: dict[str, Any] | None = None, localns: dict[str, Any] | None = None) PyiFunction
Create a
PyiFunctionfromfn.
- render(indent: int = 0) list[str]
Return the lines for this element indented by
indentlevels.
- class macrotype.PyiModule(imports: 'list[str]' = <factory>, body: 'list[PyiElement]' = <factory>, headers: 'list[str]' = <factory>, comments: 'dict[int, str]' = <factory>)
- class macrotype.PyiVariable(name: 'str', type_str: 'str', *, used_types: 'set[type]' = <factory>, line: 'int | None' = None)
- classmethod from_assignment(name: str, value: Any) PyiVariable
Create a
PyiVariablefrom an assignment value.
- render(indent: int = 0) list[str]
Return the lines for this element indented by
indentlevels.
- class macrotype.TypeRenderInfo(text: str, used: set[type])
Formatted representation of a type along with the used types.
- macrotype.clear_registry() None
Remove all registered overloads and clear
typing’s registry.
- macrotype.emit_as(name: str)
Decorator that overrides the emitted name for a function or class.
- macrotype.find_typevars(type_obj: Any) set[str]
Return a set of type variable names referenced by
type_obj.
- macrotype.format_type(type_obj: Any, *, globalns: dict[str, Any] | None = None, _skip_parse: bool = False) TypeRenderInfo
Return a
TypeRenderInfoinstance fortype_obj.
- macrotype.get_caller_module(level: int = 2) str
Return the name of the module
levelcalls up the stack.
- macrotype.get_overloads(func: Callable) list[Callable]
Return overloads registered for func including builtin ones.
- macrotype.load_module_from_path(path: Path, *, type_checking: bool = False, module_name: str | None = None) ModuleType
Load a module from
path.When
type_checkingisTruethe module is executed withTYPE_CHECKINGblocks enabled and their contents executed.module_namecontrols the name used insys.modulesand defaults topath.stem.
- macrotype.make_literal_map(name: str, mapping: dict[str | int, str | int])
Dynamically build a class exposing
mappingviaLiteraloverloads.
- macrotype.overload(func: Callable) Callable
Replacement
overloaddecorator that also registers withtyping.
- macrotype.overload_for(*args, **kwargs)
Decorator that records literal overload information for args and kwargs.
- macrotype.patch_typing()
Context manager that patches
typing.overloadandget_overloads.
- macrotype.set_module(obj: Any, module: str) None
Set
obj.__module__to module and adjust overloads.