# python-to-typescript-interfaces
### Python to TypeScript Interfaces



- [What is this](#what-is-this)
- [Installation](#installation)
- [Motivation](#motivation)
- [Usage](#usage)
- [Why @dataclass?](#why-dataclass)
- [Why define the types in Python instead of TypeScript?](#why-define-the-types-in-python-instead-of-typescript)
- [Supported Type Mappings](#supported-type-mappings)
- [Supported Enum](#supported-enum)
- [Troubleshooting with Enum and sqlalchemy](#troubleshooting-with-enum-and-sqlalchemy)
- [Support for inheritance](#support-for-inheritance)
- [Support for datetime and date](#support-for-datetime-and-date)
- [Planned Supported Mappings](#planned-supported-mappings)
- [Unsupported/Rejected Mappings](#unsupportedrejected-mappings)
- [Contributing](#contributing)
- [Author](#author)
## What is this?
This library provides utilities that convert Python dataclasses with type
annotations to a TypeScript `interface` and serializes them to a file.
## Installation
```
python --version # requires 3.8+
pip install python-to-typescript-interfaces
```
## Motivation
In web applications where Python is used in the backend and TypeScript is used
in the frontend, it is often the case that the client will make calls to the
backend to request some data with some specific pre-defined "shape". On the
client-side, an `interface` for this data is usually defined and if the Python
backend authors use typechecking, like with [mypy](http://mypy-lang.org/), the
project authors may be typing the JSON response values as well.
This results in a duplication of code. If the shape changes in the backend,
the related interface must also be reflect its changes in the frontend. At
best, this is annoying to maintain. At worst, over time the interfaces may
diverge and cause bugs.
This library aims to have a single source of truth that describes the shape of
the payload between the backend and the frontend.
## Usage
In Python, `python-to-typescript-interfaces` exposes a new class object called `Interface`.
By subclassing this object, you identify to the also-packaged script that you
want it to be serialized to an interface file.
1. First, hook up your dataclasses:
```python
# views.py
from dataclasses import dataclass
from python_to_typescript_interfaces import Interface
@dataclass
class MyComponentProps(Interface):
name: str
show: bool
value: float
@dataclass
class WillNotGetPickedUp: # this doesn't subclass Interface, so it won't be included
name: str
value: float
```
2. In your shell, run the included command and pass in the name of the file or
directory you want to use. By default it will output to a file in your
directory called interface.ts
```
$ python-to-typescript-interfaces views.py
Created interface.ts!
```
You may also use the following arguments:
- `-o, --output [filepath]`: where the file will be saved. default is `interface.ts`.
- `-a, --append`: by default each run will overwrite the output file. this flag
allows only appends. Be warned, duplicate interfaces are not tested.
- `-e, --export`: whether the interface definitions should be prepended with `export`;
- `-dt, --date-type`: defines how date types should be tranformed to, (default: `string`);
3. The resulting file will look like this:
```typescript
// interface.ts
interface MyComponentProps {
name: string;
show: boolean;
value: number;
}
```
## Why @dataclass?
`Dataclass`es were introduced in Python 3.7 and they are great. Some
alternatives that I have seen other codebases using are `NamedTuple` and
`TypedDict`. All of these objects attempt to do the same thing: group together
pieces of data that belong close together like a struct.
However, `dataclass` won out over the other two for the following reasons:
1. dataclasses are built-in to Python. As of writing, `NamedTuple` is also
built-in to the `typing` module, but `TypedDict` is still considered
experimental.
2. dataclasses cannot be declared and defined inline like you can do with
`NamedTuple` and `TypedDict`, e.g., `NamedTuple` can be defined using class
inheritance like `class MyNamedTuple(NamedTuple): ...`, but also like
`MyNamedTuple = NamedTuple('MyNamedTuple', [('name', str), ('id', int)])`.
This is a good thing. Dataclasses require you to use a class style
declaration, which not only looks closer to a TypeScript interface
declaration, but it avoids the complex metaclass machinery that NamedTuples
and TypedDicts use to gain all its features. Since this library uses the
AST and static analysis of the code to determine what data to serialize,
this makes the choice a no-brainer.
3. dataclasses can be made to be immutable (mostly) by setting `frozen=True`.
This library does not require it but in later versions we may provide a
`partial`ed dataclass decorator that guarantees immutability.
4. Because we avoid the metaclass machinery of NamedTuples and TypedDicts, it
opens up the possibility of writing custom classes that allows `mypy` to
typecheck it one way, but gives the AST parser some clues in order to
generate TypeScript types that cannot easily be expressed in Python.
## Why define the types in Python instead of TypeScript?
TypeScript is significantly more mature for typing syntax than Python.
Generally speaking, you can express any type that Python can do in TypeScript,
but _not_ vice versa.
So defining the types in Python guarantee that you can also express the whole
interface in both languages.
## Supported Type Mappings
Please note that usage of `T` `U` and `V` in the table below represent
stand-ins for actual types. They do not represent actually using generic typed
variables.
| Python | Typescript |
| :------------: | :------------------------------------------------: |
| None | null |
| str | string |
| int | number |
| float | number |
| complex | number |
| bool | boolean |
| List | Array\<any\> |
| Tuple | [any] |
| Dict | Record<any, any> |
| List[T] | Array[T] |
| Tuple[T, U] | [T, U] |
| Dict[T, U] | Record<T, U> |
| Optional[T] | T \| null |
| Union[T, U, V] | T \| U \| V |
| Enum | [enum](#supported-enum) |
| datetime | Default : [string](#support-for-datetime-and-date) |
| date | Default : [string](#support-for-datetime-and-date) |
## Supported Enum
According python Restricted Enum subclassing [doc](https://docs.python.org/3/howto/enum.html#restricted-enum-subclassing), A new Enum class must have one base enum class, up to one concrete data type, and as many object-based mixin classes as needed. The order of these base classes is:
```python
class EnumName([mix-in, ...,] [data-type,] base-enum):
pass
```
so the order is important when you add the `Interface` class on your enum class, `Enum` class should always be the last one.
```python
from dataclasses import dataclass
from enum import Enum
from python_to_typescript_interfaces import Interface
@dataclass
class Animal(Interface, Enum):
DOG = "dog"
CAT = "cat"
```
### Troubleshooting with Enum and sqlalchemy
If you want to use Enums with `Interface` class in `sqlalchemy`, you will have an error saying that the type is `unhashable`. To avoid this, and make your Enums working with `sqlalchemy` you need to add a `__hash__` like bellow:
```python
@dataclass
class AnimalSpecies(Interface, Enum):
DOG = "dog"
CAT = "cat"
def __hash__(self):
return hash(self.name)
```
## Support for inheritance
Inheritance is supported only with classes also parsed in the process. For example below, `BaseModel` will not be extended as this class doesn't exist in the file.
```python
@dataclass
class Simple0(Interface):
a: int
b: str
@dataclass
class Simple1(Interface):
c: int
@dataclass
class Simple2(BaseModel, Simple0, Simple1, Interface):
d: str
```
Above python code will be transformed like bellow typescript code :
```typescript
export interface Simple0 {
a: number;
b: string;
}
export interface Simple1 {
c: number;
}
export interface Simple2 extends Simple0, Simple1 {
d: string;
}
```
## Support for datetime and date
Support for `datetime` and `date` types is available through `-dt, --date-type` flag. We have decided to add this support in a configurable way as dates can be transformed in different types depending the projet / implementation. Possible choices are `["string", "number", "Date"]` and the default one is `string` (because it's the return type of a `Date.prototype.toJSON()`).
Examples with below python class:
```python
from dataclasses import dataclass
from datetime import datetime, date
from python_to_typescript_interfaces import Interface
@dataclass
class DatedModel(Interface):
created_at: datetime
updated_at: date
```
It will result in below interface:
```typescript
// Without flag
export interface DatedModel {
created_at: string;
updated_at: string;
}
// With flag: -dt Date
export interface DatedModel {
created_at: Date;
updated_at: Date;
}
// With flag: -dt number
export interface DatedModel {
created_at: number;
updated_at: number;
}
```
## Planned Supported Mappings
- String literals
- Undefined type
- isNaN type
- ReadOnly types
- Excess Properties
## Unsupported/Rejected Mappings
The primary purpose of this library is to help type, first and foremost, _data_
moving back and forth from client to server. Many of these features, whether they be specific to TypeScript or Python, would be overkill to support.
- void
- callables/functions
- generics, TypeVars
- intersection types
- mapped types
- conditional types
- classes
## Contributing
Interested in contributing? You're awesome! It's not much, but here's some notes to get you started [CONTRIBUTING.md](CONTRIBUTING.md).
## Authors
[Christopher Sabater Cordero](https://chrisdoescoding.com)
[Nalyze team](https://nalyze.team/)
Raw data
{
"_id": null,
"home_page": "https://github.com/NalyzeSolutions/python_to_typescript_interfaces",
"name": "python-to-typescript-interfaces",
"maintainer": null,
"docs_url": null,
"requires_python": "<4.0.0,>=3.8.1",
"maintainer_email": null,
"keywords": "python, typescript, interfaces",
"author": "Christopher Cordero",
"author_email": "ccordero@protonmail.com",
"download_url": "https://files.pythonhosted.org/packages/f3/b8/6f03316c7c4451da3cdb88a3403220875ae97d728c354aa1559974c3994c/python_to_typescript_interfaces-0.2.0.tar.gz",
"platform": null,
"description": "# python-to-typescript-interfaces\n\n### Python to TypeScript Interfaces\n\n\n\n\n\n- [What is this](#what-is-this)\n- [Installation](#installation)\n- [Motivation](#motivation)\n- [Usage](#usage)\n- [Why @dataclass?](#why-dataclass)\n- [Why define the types in Python instead of TypeScript?](#why-define-the-types-in-python-instead-of-typescript)\n- [Supported Type Mappings](#supported-type-mappings)\n- [Supported Enum](#supported-enum)\n - [Troubleshooting with Enum and sqlalchemy](#troubleshooting-with-enum-and-sqlalchemy)\n- [Support for inheritance](#support-for-inheritance)\n- [Support for datetime and date](#support-for-datetime-and-date)\n- [Planned Supported Mappings](#planned-supported-mappings)\n- [Unsupported/Rejected Mappings](#unsupportedrejected-mappings)\n- [Contributing](#contributing)\n- [Author](#author)\n\n## What is this?\n\nThis library provides utilities that convert Python dataclasses with type\nannotations to a TypeScript `interface` and serializes them to a file.\n\n## Installation\n\n```\npython --version # requires 3.8+\npip install python-to-typescript-interfaces\n```\n\n## Motivation\n\nIn web applications where Python is used in the backend and TypeScript is used\nin the frontend, it is often the case that the client will make calls to the\nbackend to request some data with some specific pre-defined \"shape\". On the\nclient-side, an `interface` for this data is usually defined and if the Python\nbackend authors use typechecking, like with [mypy](http://mypy-lang.org/), the\nproject authors may be typing the JSON response values as well.\n\nThis results in a duplication of code. If the shape changes in the backend,\nthe related interface must also be reflect its changes in the frontend. At\nbest, this is annoying to maintain. At worst, over time the interfaces may\ndiverge and cause bugs.\n\nThis library aims to have a single source of truth that describes the shape of\nthe payload between the backend and the frontend.\n\n## Usage\n\nIn Python, `python-to-typescript-interfaces` exposes a new class object called `Interface`.\nBy subclassing this object, you identify to the also-packaged script that you\nwant it to be serialized to an interface file.\n\n1. First, hook up your dataclasses:\n\n```python\n# views.py\nfrom dataclasses import dataclass\nfrom python_to_typescript_interfaces import Interface\n\n@dataclass\nclass MyComponentProps(Interface):\n name: str\n show: bool\n value: float\n\n@dataclass\nclass WillNotGetPickedUp: # this doesn't subclass Interface, so it won't be included\n name: str\n value: float\n```\n\n2. In your shell, run the included command and pass in the name of the file or\n directory you want to use. By default it will output to a file in your\n directory called interface.ts\n\n```\n$ python-to-typescript-interfaces views.py\nCreated interface.ts!\n```\n\nYou may also use the following arguments:\n\n- `-o, --output [filepath]`: where the file will be saved. default is `interface.ts`.\n- `-a, --append`: by default each run will overwrite the output file. this flag\n allows only appends. Be warned, duplicate interfaces are not tested.\n- `-e, --export`: whether the interface definitions should be prepended with `export`;\n- `-dt, --date-type`: defines how date types should be tranformed to, (default: `string`);\n\n3. The resulting file will look like this:\n\n```typescript\n// interface.ts\ninterface MyComponentProps {\n name: string;\n show: boolean;\n value: number;\n}\n```\n\n## Why @dataclass?\n\n`Dataclass`es were introduced in Python 3.7 and they are great. Some\nalternatives that I have seen other codebases using are `NamedTuple` and\n`TypedDict`. All of these objects attempt to do the same thing: group together\npieces of data that belong close together like a struct.\n\nHowever, `dataclass` won out over the other two for the following reasons:\n\n1. dataclasses are built-in to Python. As of writing, `NamedTuple` is also\n built-in to the `typing` module, but `TypedDict` is still considered\n experimental.\n2. dataclasses cannot be declared and defined inline like you can do with\n `NamedTuple` and `TypedDict`, e.g., `NamedTuple` can be defined using class\n inheritance like `class MyNamedTuple(NamedTuple): ...`, but also like\n `MyNamedTuple = NamedTuple('MyNamedTuple', [('name', str), ('id', int)])`.\n This is a good thing. Dataclasses require you to use a class style\n declaration, which not only looks closer to a TypeScript interface\n declaration, but it avoids the complex metaclass machinery that NamedTuples\n and TypedDicts use to gain all its features. Since this library uses the\n AST and static analysis of the code to determine what data to serialize,\n this makes the choice a no-brainer.\n3. dataclasses can be made to be immutable (mostly) by setting `frozen=True`.\n This library does not require it but in later versions we may provide a\n `partial`ed dataclass decorator that guarantees immutability.\n4. Because we avoid the metaclass machinery of NamedTuples and TypedDicts, it\n opens up the possibility of writing custom classes that allows `mypy` to\n typecheck it one way, but gives the AST parser some clues in order to\n generate TypeScript types that cannot easily be expressed in Python.\n\n## Why define the types in Python instead of TypeScript?\n\nTypeScript is significantly more mature for typing syntax than Python.\nGenerally speaking, you can express any type that Python can do in TypeScript,\nbut _not_ vice versa.\n\nSo defining the types in Python guarantee that you can also express the whole\ninterface in both languages.\n\n## Supported Type Mappings\n\nPlease note that usage of `T` `U` and `V` in the table below represent\nstand-ins for actual types. They do not represent actually using generic typed\nvariables.\n\n| Python | Typescript |\n| :------------: | :------------------------------------------------: |\n| None | null |\n| str | string |\n| int | number |\n| float | number |\n| complex | number |\n| bool | boolean |\n| List | Array\\<any\\> |\n| Tuple | [any] |\n| Dict | Record<any, any> |\n| List[T] | Array[T] |\n| Tuple[T, U] | [T, U] |\n| Dict[T, U] | Record<T, U> |\n| Optional[T] | T \\| null |\n| Union[T, U, V] | T \\| U \\| V |\n| Enum | [enum](#supported-enum) |\n| datetime | Default : [string](#support-for-datetime-and-date) |\n| date | Default : [string](#support-for-datetime-and-date) |\n\n## Supported Enum\n\nAccording python Restricted Enum subclassing [doc](https://docs.python.org/3/howto/enum.html#restricted-enum-subclassing), A new Enum class must have one base enum class, up to one concrete data type, and as many object-based mixin classes as needed. The order of these base classes is:\n\n```python\nclass EnumName([mix-in, ...,] [data-type,] base-enum):\n pass\n```\n\nso the order is important when you add the `Interface` class on your enum class, `Enum` class should always be the last one.\n\n```python\nfrom dataclasses import dataclass\nfrom enum import Enum\n\nfrom python_to_typescript_interfaces import Interface\n\n@dataclass\nclass Animal(Interface, Enum):\n DOG = \"dog\"\n CAT = \"cat\"\n```\n\n### Troubleshooting with Enum and sqlalchemy\n\nIf you want to use Enums with `Interface` class in `sqlalchemy`, you will have an error saying that the type is `unhashable`. To avoid this, and make your Enums working with `sqlalchemy` you need to add a `__hash__` like bellow:\n\n```python\n@dataclass\nclass AnimalSpecies(Interface, Enum):\n DOG = \"dog\"\n CAT = \"cat\"\n\n def __hash__(self):\n return hash(self.name)\n```\n\n## Support for inheritance\n\nInheritance is supported only with classes also parsed in the process. For example below, `BaseModel` will not be extended as this class doesn't exist in the file.\n\n```python\n@dataclass\nclass Simple0(Interface):\n a: int\n b: str\n\n\n@dataclass\nclass Simple1(Interface):\n c: int\n\n\n@dataclass\nclass Simple2(BaseModel, Simple0, Simple1, Interface):\n d: str\n\n```\n\nAbove python code will be transformed like bellow typescript code :\n\n```typescript\nexport interface Simple0 {\n a: number;\n b: string;\n}\n\nexport interface Simple1 {\n c: number;\n}\n\nexport interface Simple2 extends Simple0, Simple1 {\n d: string;\n}\n```\n\n## Support for datetime and date\n\nSupport for `datetime` and `date` types is available through `-dt, --date-type` flag. We have decided to add this support in a configurable way as dates can be transformed in different types depending the projet / implementation. Possible choices are `[\"string\", \"number\", \"Date\"]` and the default one is `string` (because it's the return type of a `Date.prototype.toJSON()`).\n\nExamples with below python class:\n\n```python\nfrom dataclasses import dataclass\nfrom datetime import datetime, date\n\nfrom python_to_typescript_interfaces import Interface\n\n@dataclass\nclass DatedModel(Interface):\n created_at: datetime\n updated_at: date\n```\n\nIt will result in below interface:\n\n```typescript\n// Without flag\nexport interface DatedModel {\n created_at: string;\n updated_at: string;\n}\n// With flag: -dt Date\nexport interface DatedModel {\n created_at: Date;\n updated_at: Date;\n}\n// With flag: -dt number\nexport interface DatedModel {\n created_at: number;\n updated_at: number;\n}\n```\n\n## Planned Supported Mappings\n\n- String literals\n- Undefined type\n- isNaN type\n- ReadOnly types\n- Excess Properties\n\n## Unsupported/Rejected Mappings\n\nThe primary purpose of this library is to help type, first and foremost, _data_\nmoving back and forth from client to server. Many of these features, whether they be specific to TypeScript or Python, would be overkill to support.\n\n- void\n- callables/functions\n- generics, TypeVars\n- intersection types\n- mapped types\n- conditional types\n- classes\n\n## Contributing\n\nInterested in contributing? You're awesome! It's not much, but here's some notes to get you started [CONTRIBUTING.md](CONTRIBUTING.md).\n\n## Authors\n\n[Christopher Sabater Cordero](https://chrisdoescoding.com)\n[Nalyze team](https://nalyze.team/)\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "A library that converts Python dataclasses with type annotations to a TypeScript interface and serializes them to a file. Originally py-ts-interfaces",
"version": "0.2.0",
"project_urls": {
"Documentation": "https://github.com/NalyzeSolutions/python_to_typescript_interfaces",
"Homepage": "https://github.com/NalyzeSolutions/python_to_typescript_interfaces",
"Repository": "https://github.com/NalyzeSolutions/python_to_typescript_interfaces"
},
"split_keywords": [
"python",
" typescript",
" interfaces"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "04491249e7cfe7d5bf0b93bc9282d3b2a112d7e6310ee8993c5c7ef37a35324e",
"md5": "b1db67284ddc29726bbef89425c4bb28",
"sha256": "783c4b29ba39377c442831bc55f1c201d649e21fbeaba37bb3779ad605549404"
},
"downloads": -1,
"filename": "python_to_typescript_interfaces-0.2.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "b1db67284ddc29726bbef89425c4bb28",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": "<4.0.0,>=3.8.1",
"size": 15694,
"upload_time": "2024-07-02T12:33:49",
"upload_time_iso_8601": "2024-07-02T12:33:49.839402Z",
"url": "https://files.pythonhosted.org/packages/04/49/1249e7cfe7d5bf0b93bc9282d3b2a112d7e6310ee8993c5c7ef37a35324e/python_to_typescript_interfaces-0.2.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "f3b86f03316c7c4451da3cdb88a3403220875ae97d728c354aa1559974c3994c",
"md5": "3c38695785cb4961d7746af2122d5b71",
"sha256": "fd9a608f9f4059dd9febff61d057f434fb1a63ebc7b3459c1aea0e8087db49f0"
},
"downloads": -1,
"filename": "python_to_typescript_interfaces-0.2.0.tar.gz",
"has_sig": false,
"md5_digest": "3c38695785cb4961d7746af2122d5b71",
"packagetype": "sdist",
"python_version": "source",
"requires_python": "<4.0.0,>=3.8.1",
"size": 17322,
"upload_time": "2024-07-02T12:33:51",
"upload_time_iso_8601": "2024-07-02T12:33:51.073594Z",
"url": "https://files.pythonhosted.org/packages/f3/b8/6f03316c7c4451da3cdb88a3403220875ae97d728c354aa1559974c3994c/python_to_typescript_interfaces-0.2.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-07-02 12:33:51",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "NalyzeSolutions",
"github_project": "python_to_typescript_interfaces",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "python-to-typescript-interfaces"
}