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():

  1. inspect.Parameter.empty""

  2. Alias-registry lookup → e.g. "types.Length"

  3. Registered dispatch handlers, in registration order

  4. Fallback: str(annotation).replace("typing.", "")

Core functions

annotation_to_str(annotation: Any, ctx: StubContext) str[source]

Convert any annotation object to a valid .pyi string.

Resolution order:

  1. inspect.Parameter.empty or inspect.Signature.empty""

  2. 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.

  3. Registered dispatch handlers, tried in order.

  4. 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.ForwardRef objects, NoneType, and inspect.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_param

Formats a full parameter including name and default.

stubpy.context.StubContext.lookup_alias

Alias 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) str[source]

Format a single inspect.Parameter as a stub-ready string.

The hints dict (from get_hints_for_method()) takes priority over param.annotation because it contains fully resolved annotations — forward references evaluated, from __future__ import annotations strings expanded.

Parameters:
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, or property. None returns an empty dict.

Returns:

dict{param_name: annotation} with forward references resolved, or an empty dict if resolution fails or fn is None.

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.empty when the parameter has no default.

Returns:

strrepr(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

isinstance(a, str)

String forward references, e.g. "Element"

isinstance(a, ForwardRef)

typing.ForwardRef objects

a is type(None)

NoneType → emits "None"

isinstance(a, UnionType)

PEP 604 str | int unions (Python 3.10+)

isinstance(a, type)

Plain classes — uses __name__

__origin__ is not None

All subscripted typing generics (Union, Optional, Callable, Literal, …)

_name is not None

Bare unsubscripted aliases (List, Dict, …)