Type alias preservation

When a package uses a dedicated types module (e.g. types.py) to define named type aliases, stubpy preserves those names in the stub rather than expanding them to their underlying union.

Setup

Define your type aliases in a sub-module:

mypackage/types.py
from typing import Literal, Sequence, Tuple, Union

Number         = int | float
Length         = str | float | int
Color          = str | Tuple[float, float, float] | Tuple[float, float, float, float]
StrokeLineCap  = Literal["butt", "round", "square"]
StrokeLineJoin = Literal["miter", "round", "bevel"]
DashArray      = Union[str, Sequence[Number]]

Import the sub-module (not individual names) in your source:

mypackage/shapes.py
from mypackage import types        # ← import the module, not `from types import Length`
from mypackage.element import Element

class Shape(Element):
    def __init__(
        self,
        fill:             types.Color         = "black",
        stroke_width:     types.Length        = 1,
        stroke_linecap:   types.StrokeLineCap = "butt",
    ) -> None: ...

Generated stub

shapes.pyi (generated)
from __future__ import annotations
from typing import Optional
from mypackage import types
from mypackage.element import Element

class Shape(Element):
    def __init__(
        self,
        fill:           types.Color         = 'black',
        stroke_width:   types.Length        = 1,
        stroke_linecap: types.StrokeLineCap = 'butt',
        id:             Optional[str]       = None,
        opacity:        float               = 1.0,
    ) -> None: ...

Compared to the unexpanded alternative:

what it would look like without alias preservation
def __init__(
    self,
    fill:           str | Tuple[float, float, float] | Tuple[float, float, float, float] = 'black',
    stroke_width:   str | float | int = 1,
    stroke_linecap: Literal['butt', 'round', 'square'] = 'butt',
    ...

The alias form is shorter, more readable, and stays in sync with your types module automatically.

Aliases propagate through **kwargs

Type aliases are preserved even when parameters arrive via **kwargs backtracing. In the example above, id and opacity come from Element.__init__ through **kwargs, but any aliased annotations among them are still emitted as their alias names.

Only imported modules are scanned

stubpy only looks for aliases in sub-modules that are imported as a module object into your source file. The key is:

from mypackage import types   # ✓ imports the module — aliases discovered
from mypackage.types import Length  # ✗ imports a name — aliases NOT discovered

This is intentional: importing individual names gives you full control over whether stubpy treats them as aliases or expands them.