ts_type generates typescript's type from python code
Project description
ts_type
Overview
ts_type is a Python library that generates TypeScript type definitions from Python type hints. This library helps bridge the gap between backend (Python) and frontend (TypeScript), ensuring type safety and improving development efficiency.
Motivation
In modern Python development, it has become common to use typing to annotate type information. On the frontend side, TypeScript dominates the ecosystem due to its strong typing capabilities. Many projects use Python as the backend and TypeScript as the frontend, making it highly beneficial to share type information between the two.
At Nailbook, we use Python for the backend and TypeScript for the frontend, making ts_type a perfect fit for our needs. By generating TypeScript types from all API definitions, we not only prevent type mismatches in API calls but also achieve exceptionally high development efficiency.
What is ts_type?
ts_type is a utility for generating TypeScript type definitions from Python objects.
This library leverages Python's dynamic nature to collect and construct type definitions.
You can specify any objects you want to export as TypeScript types.
For example, if you have a web API that uses Python’s typing module for static type annotations,
you can retrieve those type definitions and generate a union of API types effortlessly:
Why Use ts_type?
- Automatic Type Synchronization
Ensure that your frontend TypeScript types always match your backend Python types. - Eliminate Type Mismatches
Reduce runtime errors caused by incorrect API usage. - Improve Developer Productivity
No need to manually write or update TypeScript types for API responses.
How To Use
Installation
You can install ts_type via pip:
pip install ts-type
Generating TypeScript Definitions from Python Models
To use ts_type, follow these steps:
1. Define Python Models
Create a file named example.py and define your Python models:
from dataclasses import dataclass
from typing import Optional, List, Dict
import ts_type as ts
@ts.gen_type
@dataclass
class MyCustomType:
a: int
b: Optional[str]
c: List[str]
d: Dict[str, int]
2. Create a code generation script
Next, create a script named codegen.py to generate TypeScript types:
# Ensure that the defined class is registered
import example
import ts_type as ts
ts.generator.generate('output_dir')
3. Run the script
Execute the script to generate TypeScript types:
python codegen.py
This will generate output_dir/example.gen.ts with the following content:
export type MyCustomType = example__MyCustomType;
type example__MyCustomType = {
"a": number;
"b": string | null;
"c": string[];
"d": {[key: string]: number};
};
Support Custom Types
You can customize the builder to support any objects, including third-party libraries. For example, if you are using the cleaned library (which, let's be honest, might only be me using it 😇) for request validation, you can define a custom builder as follows:
class Builder(ts.NodeBuilder):
def handle_unknown_type(self, t: Any) -> ts.TypeNode:
if isinstance(t, type):
if issubclass(t, cl.Cleaned):
return self.define_cleaned(t)
if issubclass(t, cl.Undefined):
return ts.Undefined()
return super().handle_unknown_type(t)
def define_cleaned(self,
t: type[cl.Cleaned],
exclude: set[str] = set()) -> ts.TypeNode:
ret: ts.TypeNode = self.define_ref_node(t, lambda: ts.Object(
attrs={
k: self.type_to_node(self.field_to_type(f))
for k, f in t._meta.fields.items()
},
omissible={k for k, f in t._meta.fields.items() if f.has_default}))
if (exclude := exclude & set(t._meta.fields)):
ret = ts.Omit(ret, ts.Union(
[self.literal_to_node(k) for k in sorted(exclude)]))
return ret
def field_to_type(self, t: cl.Field) -> type:
if isinstance(t, cl.OptionalField):
vt = self.field_to_type(t.field)
return Union[None, cl.Undefined, vt]
elif isinstance(t, cl.ListField):
vt = self.field_to_type(t.value)
return list[vt]
elif isinstance(t, cl.SetField):
vt = self.field_to_type(t.value)
return set[vt]
elif isinstance(t, cl.DictField):
kt = self.field_to_type(t.key)
vt = self.field_to_type(t.value)
return dict[kt, vt]
elif isinstance(t, cl.NestedField):
return t._server()
elif isinstance(t, cl.EnumField):
return t._server()
else:
return ts.resolve_typevar(
t.__class__,
cl.Field.__parameters__[0])
# Don't forget to pass the builder to the generator
ts.generator.generate(output_dir, builder_cls=Builder)
Generating TypeScript Definitions from API Models
Below is an example demonstrating how ts_type can generate TypeScript type definitions for API request/response models:
1. Define API models
Create a Python script with the following content as api_sample.py:
from dataclasses import dataclass
import ts_type as ts
@dataclass
class RequestForApiA:
a: int
b: str
@dataclass
class ResponseForApiA:
result: bool
def api_a(request: RequestForApiA) -> ResponseForApiA:
...
@dataclass
class RequestForApiB:
@dataclass
class Item:
id: int
name: str
items: list[Item]
def api_b(request: RequestForApiB) -> int:
...
apis = {
'/api/a': api_a,
'/api/b': api_b,
}
2. Generate TypeScript types
Run the following script:
import ts_type as ts
from api_sample import apis
class Apis(ts.NodeCompatibleClass[ts.NodeBuilder]):
@classmethod
def convert_to_node(cls, builder: ts.NodeBuilder) -> ts.TypeNode:
return ts.Union(of=[
ts.Object(attrs=dict(
# It must be `literal_to_node` because
# `type_to_node` treats str as ForwardRef.
url=builder.literal_to_node(url),
# `type_to_node` supports many builtin types such as
# dataclasses, Enums, Lists, Unions.
request=builder.type_to_node(api.__annotations__['request']),
response=builder.type_to_node(api.__annotations__['return']),
))
for url, api in apis.items()
])
ts.generator.add(Apis, 'apis', 'Apis')
ts.generator.generate('output_dir')
3. Expected Output
Running the script will generate the following TypeScript definitions:
export type Apis = {
"url": "/api/a";
"request": __main____RequestForApiA;
"response": __main____ResponseForApiA;
} | {
"url": "/api/b";
"request": __main____RequestForApiB;
"response": number;
};
type __main____RequestForApiA = {
"a": number;
"b": string;
};
type __main____ResponseForApiA = {
"result": boolean;
};
type __main____RequestForApiB = {
"items": __main____RequestForApiB__Item[];
};
type __main____RequestForApiB__Item = {
"id": number;
"name": string;
};
You can use it to extract request and response types from url.
type RequestForApiA = Extract<Apis, {url: "/api/a"}>['request'];
type ResponseForApiA = Extract<Apis, {url: "/api/a"}>['response'];
type RequestForApiB = Extract<Apis, {url: "/api/b"}>['request'];
type ResponseForApiB = Extract<Apis, {url: "/api/b"}>['response'];
More Examples
save following code as example.py
from dataclasses import dataclass
from datetime import datetime
from enum import Enum
from typing import TypeVar, Generic, Optional
import ts_type as ts
T = TypeVar('T')
@ts.gen_type
@dataclass
class Article:
class Type(Enum):
news = 'news'
essay = 'essay'
@dataclass
class Author:
id: int
name: str
id: int
type: Type
title: str
author: Author
published_at: datetime
@ts.gen_type
@dataclass
class Pagination(Generic[T]):
items: list[T]
next_cursor: Optional[str]
@ts.gen_type
@dataclass
class Response:
status: int
articles: Pagination[Article]
run:
import example
example.ts.generator.generate('output_dir')
output: output_dir/example.gen.ts
export type Article = example__Article;
export type Pagination<T> = example__Pagination<T>;
export type Response = example__Response;
type example__Article = {
"id": number;
"type": example__Article__Type;
"title": string;
"author": example__Article__Author;
"published_at": string;
};
type example__Article__Type = "news" | "essay";
type example__Article__Author = {
"id": number;
"name": string;
};
type example__Pagination<T> = {
"items": T[];
"next_cursor": string | null;
};
type example__Response = {
"status": number;
"articles": example__Pagination<example__Article>;
};
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 ts_type-0.3.4.tar.gz.
File metadata
- Download URL: ts_type-0.3.4.tar.gz
- Upload date:
- Size: 18.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5f2e2a4ba4e5f7f63eb7cc5a0208b868dc066da99c34da891bc3cbfe0f27b19d
|
|
| MD5 |
c894bf569bfae60de8f2b8f42503dfb5
|
|
| BLAKE2b-256 |
a99a37a2cc86f00818ba15dc2cace832a8bdde4b22fa48c23b2480a10eae8ca6
|
Provenance
The following attestation bundles were made for ts_type-0.3.4.tar.gz:
Publisher:
publish.yml on saryou/ts_type
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ts_type-0.3.4.tar.gz -
Subject digest:
5f2e2a4ba4e5f7f63eb7cc5a0208b868dc066da99c34da891bc3cbfe0f27b19d - Sigstore transparency entry: 214712344
- Sigstore integration time:
-
Permalink:
saryou/ts_type@649d170f9bbf9ac4f2af037c4a17baa6f83302c2 -
Branch / Tag:
refs/tags/0.3.4 - Owner: https://github.com/saryou
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@649d170f9bbf9ac4f2af037c4a17baa6f83302c2 -
Trigger Event:
release
-
Statement type:
File details
Details for the file ts_type-0.3.4-py3-none-any.whl.
File metadata
- Download URL: ts_type-0.3.4-py3-none-any.whl
- Upload date:
- Size: 14.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
dc16d431d5f8e3e09b923b38f95037c07d95dbed3116964771e65f4cddca7038
|
|
| MD5 |
634964c87ed747799665358fc919255f
|
|
| BLAKE2b-256 |
65be61270497b881389a61797ea9a288d4560de80d97cfe9679499e2796e5dbb
|
Provenance
The following attestation bundles were made for ts_type-0.3.4-py3-none-any.whl:
Publisher:
publish.yml on saryou/ts_type
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ts_type-0.3.4-py3-none-any.whl -
Subject digest:
dc16d431d5f8e3e09b923b38f95037c07d95dbed3116964771e65f4cddca7038 - Sigstore transparency entry: 214712346
- Sigstore integration time:
-
Permalink:
saryou/ts_type@649d170f9bbf9ac4f2af037c4a17baa6f83302c2 -
Branch / Tag:
refs/tags/0.3.4 - Owner: https://github.com/saryou
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@649d170f9bbf9ac4f2af037c4a17baa6f83302c2 -
Trigger Event:
release
-
Statement type: