Writing .dty Stubs
A .dty stub describes a Python module’s API in Typhon’s strict dialect (T?, Result[T, E], sealed unions, interfaces). The compiler:
- Checks your code against the stub at build time.
- Emits a
.pyicompanion so mypy / pyright / Pyrefly /tyalso understand it.
File location
Place .dty files inside your configured src/ directory (commonly src/stubs/<module>.dty). tyc build and tyc check --stubs pick up every .dty under src/; files outside that directory are not scanned.
Example
class Redis: host: str port: int
impl Redis: def get(self, key: str) -> str? def set(self, key: str, value: str) -> bool def delete(self, *keys: str) -> int def keys(self, pattern: str) -> list[str]
def from_url(url: str) -> Redisclass Redis: host: str port: int
def get(self, key: str) -> str | None: ... def set(self, key: str, value: str) -> bool: ... def delete(self, *keys: str) -> int: ... def keys(self, pattern: str) -> list[str]: ...
def from_url(url: str) -> Redis: ...Notes:
- The same syntax as
.ty, but methods have no body (signatures only). T?isT | Nonein the emitted.pyi.- Class fields work — the stub asserts their types.
- Free functions sit at module level.
What emits as .pyi
Standard PEP 561 stub. Method bodies become ....
Stubbing a third-party module
If you’re stubbing a module you don’t own (e.g. redis-py):
- Write
src/stubs/redis.dty. - Run
tyc check --stubsto confirm the stub matches the runtime module’s surface. - Update the stub when the library upgrades and
tyc check --stubscomplains.
Stubbing a Typhon-authored library for Python consumers
If you’re writing a .ty library that Python users will consume via the emitted .py:
- The
.pycarries Typhon’s annotations as standard Python annotations (T | None, etc.). - You usually don’t need a separate
.dty— the emitted.pyis typed already.
You’d write a .dty only if you want to expose a stricter API surface in Typhon than the implementation reveals (e.g. hiding an internal helper).
Compiler-bundled stubs (Layer 0)
You don’t always have to write the stub yourself. tyc ships curated, embedded .dty stubs for popular libraries whose packaging defeats venv introspection — httpx and requests to start. tyc check, tyc build, and the LSP seed these into the project shape map before venv introspection runs, so the library is shaped out of the box:
- Its construction is type-checked.
- Its
unintrospectable-dependencywarning is suppressed. - No
.venvand notyc syncare required.
Authored stubs win
Bundled stubs are gap-fill only. The precedence is: an authored project .dty/.ty for the same module beats the bundle, and the bundle in turn beats venv introspection. So if you write src/stubs/httpx.dty, your stub is the surface the checker uses — the bundle steps aside. Write your own for long-lived dependencies you call a lot and want pinned to a precise, strict surface.
partial leniency
Bundled class shapes are marked partial: members the stub omits stay lenient, so a real attribute the curated stub didn’t enumerate won’t raise a false attribute_not_found. Request methods take **kwargs: object, and client constructors enumerate the common kwargs as optional fields. The aim is to shape the head of the dependency distribution without becoming a maintenance burden; the long tail stays best-effort.
Common patterns
Optional return that’s also nullable
impl Database: def find_user(self, id: int) -> User? # may be NoneGeneric methods
class Cache[K, V]: def get(self, key: K) -> V? def put(self, key: K, value: V) -> NoneClass hierarchies
class Model: id: int
class User(Model): name: strAsync functions
impl HttpClient: async def get(self, url: str) -> Response async def post(self, url: str, body: bytes) -> ResponseWhat’s not stubbed
.dty is for typed surfaces. If the module is genuinely dynamic (returns Any everywhere, uses **kwargs extensively), you may need to:
- Use
unsafe:blocks at the call site. - Wrap the dynamism in a small typed adapter.
You don’t have to stub everything — only what your project consumes.
Where next
- .pyi Emission — what the emitted stub looks like.
- Stub Drift —
tyc check --stubsandtyc stubtest. - Wrapping an Untyped Library (recipe) — patterns.