stubpy.emitter

stubpy.emitter

Stub text generation — converts live class objects into .pyi source.

Two formatting modes are chosen automatically:

  • Inline for methods with ≤ 2 non-self/cls parameters.

  • Multi-line for methods with > 2 parameters, each on its own indented line with a trailing comma for clean diffs.

generate_class_stub(cls: type, ctx: StubContext) str[source]

Generate the full .pyi block for cls.

Emits in order:

  1. class Name(Base, ...): line.

  2. Class-level annotations from cls.__dict__["__annotations__"].

  3. A blank line after annotations when present.

  4. One stub per public method defined directly on cls.

  5. ... when the class has neither annotations nor methods.

Parameters:
Returns:

str – Complete class stub as a multi-line string without a trailing newline.

Examples

>>> from stubpy.context import StubContext
>>> class Point:
...     x: float
...     y: float
...     def __init__(self, x: float, y: float) -> None: ...
>>> stub = generate_class_stub(Point, StubContext())
>>> "class Point:" in stub
True
>>> "x: float" in stub
True
generate_method_stub(cls: type, method_name: str, ctx: StubContext, indent: str = '    ') str[source]

Generate the .pyi stub line(s) for a single method on cls.

Dispatches on the descriptor type in cls.__dict__:

  • property@property with optional @name.setter.

  • classmethod@classmethod with cls first.

  • staticmethod@staticmethod with no implicit first parameter.

  • Regular method — self as first parameter.

Methods with ≤ 2 non-self params are formatted inline; larger signatures are split across lines with a trailing comma on each parameter.

Parameters:
  • cls (type) – The class that owns the method.

  • method_name (str) – Name of the method as it appears in cls.__dict__.

  • ctx (StubContext) – The current StubContext.

  • indent (str, optional) – Indentation string prepended to each line. Default is four spaces.

Returns:

str – One or more stub lines, or "" if method_name is not in cls.__dict__.

Examples

>>> from stubpy.context import StubContext
>>> class A:
...     def move(self, x: float, y: float) -> None: ...
>>> stub = generate_method_stub(A, "move", StubContext())
>>> stub
'    def move(self, x: float, y: float) -> None: ...'
methods_defined_on(cls: type) list[str][source]

Return names of callable members defined directly on cls.

Only inspects cls.__dict__ — inherited members are excluded. Dunder names not in _PUBLIC_DUNDERS are silently skipped. Insertion order is preserved.

Parameters:

cls (type) – The class to inspect.

Returns:

list of str – Method names (including classmethods, staticmethods, properties) defined on cls that should appear in a stub.

Examples

>>> class Parent:
...     def parent_method(self) -> None: ...
>>> class Child(Parent):
...     def child_method(self) -> None: ...
>>> methods_defined_on(Child)
['child_method']
insert_kw_separator(params_with_hints: list[tuple[Parameter, dict[str, Any]]]) list[tuple[Parameter, dict[str, Any]]][source]

Insert a bare * sentinel before the first keyword-only parameter.

Python requires a bare * (or a *args) before any keyword-only parameters in a function signature. When no *args is present but keyword-only parameters exist, this function inserts a sentinel inspect.Parameter named _KW_SEP_NAME that generate_method_stub() emits as a literal *.

If a VAR_POSITIONAL (*args) parameter is already present no sentinel is needed and the list is returned unchanged.

Parameters:

params_with_hints (list of ParamWithHints) – The parameter list from resolve_params().

Returns:

list of ParamWithHints – The same list with the sentinel inserted at the correct position, or the original list unchanged if no insertion is needed.

Examples

>>> import inspect
>>> kw = inspect.Parameter("b", inspect.Parameter.KEYWORD_ONLY)
>>> pos = inspect.Parameter("a", inspect.Parameter.POSITIONAL_OR_KEYWORD)
>>> result = insert_kw_separator([(pos, {}), (kw, {})])
>>> result[1][0].name   # sentinel is between a and b
'__kw_sep__'

Formatting rules

Inline (≤ 2 non-self/cls parameters):

def area(self) -> float: ...
def scale(self, sx: float, sy: Optional[float] = None) -> Element: ...

Multi-line (> 2 non-self/cls parameters), each param on its own line with a trailing comma:

def __init__(
    self,
    width: float,
    height: float,
    depth: float = 1.0,
) -> None: ...

Trailing commas make diffs cleaner — adding or removing a parameter changes exactly one line.

Public dunders

Only the methods listed in the internal _PUBLIC_DUNDERS set are included in stubs. Internal Python machinery names (__dict__, __weakref__, __class__, etc.) are omitted.