Skip to main content

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 695 type, typing.TypeAlias, and simple MyT = int | str forms)

  • Function signatures -> export interface via @gen.interface("Name")

  • Forward refs like child: "Node" in dataclasses stay as Node in 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 to any.

  • Missing function return annotation defaults to void. If you annotate -> None you’ll get null (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 file
    • return_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 interface
  • gen.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 via pyproject.toml in this directory.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

bisex-0.2.0.tar.gz (15.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

bisex-0.2.0-py3-none-any.whl (10.8 kB view details)

Uploaded Python 3

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

Hashes for bisex-0.2.0.tar.gz
Algorithm Hash digest
SHA256 1303d26cfa7306a34888455b62b68928b827aa8fef2e4c9385e57592feedb617
MD5 1a782a237e1eee4349d29cfd5fc53cc9
BLAKE2b-256 a6c3e888565a7e3331e1a84746bba9fc33d5014fc222ec828436fccee4cd9e22

See more details on using hashes here.

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

Hashes for bisex-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 560b9e23ee600791d86f893ae6f0725394467fdd8fa8d3ab50d62cdd80eb6357
MD5 94cf02497e0660594ce5abd3b0dfb97e
BLAKE2b-256 c6b8fbe8f1d64c39da9c20ebf8fc85534e48a07833fbf3355fdfa9008279e4e9

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page