General‑purpose Python→TypeScript type generator
Project description
Bisex — Python Types -> TypeScript Types, no nonsense
Tiny, dependency‑free helper that turns your Python types into TypeScript: dataclasses and TypedDicts become TS interfaces, Enums become TS enums, and type aliases cross the bridge intact. You can also “record” Python functions into TypeScript interfaces for a clean API contract.
No runtime imports, no spooky side effects — we read your .py files with the AST. Add a dash of decorators for functions and call it a day.
Why? Because wiring types by hand is boring. Let robots help.
TL;DR (30 seconds)
from pathlib import Path
from bisex import TsGen
gen = TsGen(
py_types=["server/types.py", "domain/models.py"],
out_ts=Path("web/src/lib/types.generated.ts"),
)
@gen.interface("Backend")
def ping(name: str) -> None:
...
gen.generate() # writes web/src/lib/types.generated.ts
What it converts
-
Dataclasses ->
export interface(field annotations only) -
TypedDicts ->
export interface -
Enums ->
export enum(string/number members; computed values fallback to the member name string; negatives and floats are OK) -
Type aliases ->
export type(PEP 695type,typing.TypeAlias, and simpleMyT = int | strforms) -
Function signatures ->
export interfacevia@gen.interface("Name") -
Forward refs like
child: "Node"in dataclasses stay asNodein TS. -
Variadic tuples like
tuple[int, ...]become(number)[]. -
list[T]/Sequence[T]->(T)[];dict[K, V]->Record<K, V>;set[T]->Set<T>. -
Callable[[A, B], R]->(a1: A, a2: B) => R. -
Missing function param annotations (when using
@gen.interface) default toany. -
Missing function return annotation defaults to
void. If you annotate-> Noneyou’ll getnull(as expected).
Minimal example
Python source (types.py):
from dataclasses import dataclass
from enum import Enum
from typing import TypedDict
@dataclass
class User:
name: str
friend: "User" # forward ref preserved as User
tags: list[str]
class Flavor(Enum):
VANILLA = "vanilla"
MAGIC = 1 + 2 # computed -> becomes "MAGIC"
class UInfo(TypedDict):
id: int
label: str
type Id = int | str # PEP 695
Generated TypeScript (excerpt):
export interface User {
name: string;
friend: User;
tags: (string)[];
}
export enum Flavor {
VANILLA = "vanilla",
MAGIC = "MAGIC",
}
export interface UInfo {
id: number;
label: string;
}
export type Id = number | string;
Recording function APIs
from bisex import TsGen
gen = TsGen(py_types=[], out_ts="types.generated.ts")
@gen.interface("Backend")
def hello(name: str) -> None: # returns null in TS because it’s explicitly None
...
@gen.interface("Backend")
def add(a: int, b: int): # no return annotation -> void
...
@gen.interface("Backend")
def maybe(x: str | None) -> str | None:
...
print(gen.produce_ts())
You’ll get:
export interface Backend {
hello: (name: string) => null;
add: (a: number, b: number) => void;
maybe: (x: string | null) => string | null;
}
Async wrapper trick
If your frontend calls back into Python (Eel, RPC, etc.), wrap returns:
gen = TsGen(py_types=["types.py"], out_ts="web/types.generated.ts", return_wrapper="() => Promise<{ret}>")
Now ret in each signature is wrapped as a promise factory. Example:
hello: (name: string) => () => Promise<null>;
API (no fluff)
TsGen(py_types, out_ts, return_wrapper=None)py_types:str | Path | Iterable[str | Path]— .py files to scan for static types (dataclasses, TypedDicts, Enums, aliases)out_ts:Path | str— output .ts filereturn_wrapper:str | None— format string;{ret}is replaced with the return type
gen.interface(name: str)— decorator capturing a function signature into an exported TS interfacegen.produce_ts() -> str— return TypeScript as a string (no writes)gen.generate() -> Path— write to disk and return the output path
What we don’t do (yet)
- Execute your modules. Static shapes come from the AST — no import side effects.
- Infer field optionality from default values. TS output ignores Python defaults.
- Cover every edge case of the typing module. Exotic stuff may fall back to
any.
Troubleshooting
- “Why is my enum member a string of its own name?” — The value wasn’t a string/number literal (e.g. computed). That’s by design.
- “Why is a type
any?” — The shape was too dynamic or gnarly. Add annotations or simplify the type expression.
Name, vibe, license
- Name: because it converts both ways across the Python/TypeScript border (we know, we know).
- Vibe: tiny, focused, no heavy magic. Bring your own build glue.
- License: this folder is part of the WuWa Mod Manager repo. Extract and publish as you see fit. The package source lives under
bisex/and is built viapyproject.tomlin this directory.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file bisex-0.2.0.tar.gz.
File metadata
- Download URL: bisex-0.2.0.tar.gz
- Upload date:
- Size: 15.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1303d26cfa7306a34888455b62b68928b827aa8fef2e4c9385e57592feedb617
|
|
| MD5 |
1a782a237e1eee4349d29cfd5fc53cc9
|
|
| BLAKE2b-256 |
a6c3e888565a7e3331e1a84746bba9fc33d5014fc222ec828436fccee4cd9e22
|
File details
Details for the file bisex-0.2.0-py3-none-any.whl.
File metadata
- Download URL: bisex-0.2.0-py3-none-any.whl
- Upload date:
- Size: 10.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
560b9e23ee600791d86f893ae6f0725394467fdd8fa8d3ab50d62cdd80eb6357
|
|
| MD5 |
94cf02497e0660594ce5abd3b0dfb97e
|
|
| BLAKE2b-256 |
c6b8fbe8f1d64c39da9c20ebf8fc85534e48a07833fbf3355fdfa9008279e4e9
|