stubpy.annotations¶
stubpy.annotations¶
Annotation-to-string conversion using a registered dispatch table.
The central function annotation_to_str() converts any live Python
annotation object to a stub-safe string. Each annotation kind is handled
by a small private function registered via @_register(predicate). Adding
support for a new annotation type is a single decorated function — no
editing of an if/elif chain.
Resolution order inside annotation_to_str():
inspect.Parameter.empty→""Alias-registry lookup → e.g.
"types.Length"Registered dispatch handlers, in registration order
Fallback:
str(annotation).replace("typing.", "")
Core functions
- annotation_to_str(annotation: Any, ctx: StubContext) str[source]¶
Convert any annotation object to a valid
.pyistring.Resolution order:
inspect.Parameter.emptyorinspect.Signature.empty→""Alias-registry lookup via
lookup_alias()— if the annotation matches a registered alias it is returned as-is (e.g."types.Length") and the alias module is marked as used.Registered dispatch handlers, tried in order.
Fallback:
str(annotation).replace("typing.", "").
This function is recursive — generic argument lists are processed by calling it on each
__args__element.- Parameters:
annotation (Any) – Any live Python annotation object. Accepted values include plain types (
int,str), PEP 604 unions (str | None), subscripted typing generics (Optional[int]), string forward references ("Element"),typing.ForwardRefobjects,NoneType, andinspect.Parameter.empty.ctx (StubContext) – The current
StubContext. Used for alias lookup and to track which type-module imports are needed.
- Returns:
str – A stub-safe string representation, or
""for empty sentinels.
See also
format_paramFormats a full parameter including name and default.
stubpy.context.StubContext.lookup_aliasAlias resolution logic.
Examples
>>> from stubpy.context import StubContext >>> from stubpy.annotations import annotation_to_str >>> from typing import Optional, List >>> ctx = StubContext() >>> annotation_to_str(int, ctx) 'int' >>> annotation_to_str(str | None, ctx) 'Optional[str]' >>> annotation_to_str(List[int], ctx) 'List[int]' >>> annotation_to_str("Element", ctx) 'Element' >>> annotation_to_str(type(None), ctx) 'None'
- format_param(param: Parameter, hints: dict[str, Any], ctx: StubContext, raw_ann_override: str | None = None, docstring_type: str | None = None) str[source]¶
Format a single
inspect.Parameteras a stub-ready string.Resolution order for the annotation string:
raw_ann_override — when provided and it references a registered alias module prefix (e.g.
"types."), the raw AST string is used directly. This preserves alias names that Python’styping.Unionflattens at runtime.hints dict — resolved type hints from
get_hints_for_method().param.annotation — the raw
inspect.Parameterannotation.docstring_type — when provided and no annotation was found from sources 1–3, emitted as an inline
# type: Xcomment rather than as a live annotation (making the inferred origin visible).
- Parameters:
param (inspect.Parameter) – The parameter to format.
hints (dict) – Resolved type hints keyed by parameter name.
ctx (StubContext) – Passed through to
annotation_to_str().raw_ann_override (str or None, optional) – Raw annotation string from the AST pre-pass.
docstring_type (str or None, optional) – Type string inferred from the docstring. Only used when no other annotation source is available; emitted as
# type: Xcomment.
- Returns:
str – A formatted string such as
"x: int","size: float = 1.0","*args: str", or"**kwargs".
Examples
>>> import inspect >>> from stubpy.context import StubContext >>> from stubpy.annotations import format_param >>> ctx = StubContext() >>> p = inspect.Parameter("x", inspect.Parameter.POSITIONAL_OR_KEYWORD, ... annotation=int, default=0) >>> format_param(p, {}, ctx) 'x: int = 0' >>> p_star = inspect.Parameter("items", inspect.Parameter.VAR_POSITIONAL, ... annotation=str) >>> format_param(p_star, {"items": str}, ctx) '*items: str'
- get_hints_for_method(fn: Any) dict[str, Any][source]¶
Safely resolve type hints for fn, unwrapping descriptors first.
- Parameters:
fn (Any) – A callable,
classmethod,staticmethod, orproperty.Nonereturns an empty dict.- Returns:
dict –
{param_name: annotation}with forward references resolved, or an empty dict if resolution fails or fn isNone.
Examples
>>> class A: ... def foo(self, x: int) -> str: ... >>> get_hints_for_method(A.foo) {'x': <class 'int'>, 'return': <class 'str'>}
- default_to_str(default: Any) str[source]¶
Render a parameter default value as a stub-safe string.
- Parameters:
default (Any) – The default value, or
inspect.Parameter.emptywhen the parameter has no default.- Returns:
str –
repr(default)for real defaults, or""for the empty sentinel.
Examples
>>> import inspect >>> default_to_str(inspect.Parameter.empty) '' >>> default_to_str("black") "'black'" >>> default_to_str(1.0) '1.0' >>> default_to_str(None) 'None'
Extending the dispatch table
New annotation kinds can be supported by registering a handler with the
internal _register decorator:
from stubpy.annotations import _register
from stubpy.context import StubContext
@_register(lambda a: isinstance(a, MyAnnotationType))
def _handle_my_annotation(annotation, ctx: StubContext) -> str:
return f"MyAlias[{annotation.inner}]"
Built-in handlers (in dispatch order):
Predicate |
Handles |
|---|---|
|
String forward references, e.g. |
|
|
|
|
|
The |
|
PEP 604 |
|
TypeVar-like objects — uses |
|
Plain classes — uses |
|
All subscripted typing generics (Union, Optional, Callable, Literal, …) |
|
Bare unsubscripted aliases ( |
Alias preservation with Optional / union forms
When a registered alias (e.g. types.Color = Union[str, Tuple[...]]) is
used as Optional[types.Color] or types.Color | None, Python
constructs a new Union whose args include NoneType — losing the
Color boundary. Both _handle_pep604_union and the
typing.Union branch of _handle_generic detect this case by
reconstructing the non-None sub-union and checking the alias registry
on it before falling back to per-argument expansion.
When an alias appears inside a container generic such as
Tuple[types.Color, types.Length] or List[types.Color], the
recursive call to annotation_to_str() on each argument handles
preservation naturally because Python does not flatten the args of
tuple/list/Tuple/List subscriptions.
AST raw-annotation override in format_param
format_param() accepts an optional raw_ann_override string —
the annotation as written in source code, captured by the AST pre-pass
before Python evaluates it. When the string contains a registered alias
module prefix (e.g. "types."), it takes priority over the runtime
annotation. This recovers alias names destroyed by typing.Union
flattening (e.g. Union[types.Color, int] remains as-is instead of
expanding to Union[str, Tuple[...], int]).