Skip to main content

A library that converts Python dataclasses with type annotations to a TypeScript interface and serializes them to a file. Originally py-ts-interfaces

Project description

python-to-typescript-interfaces

Python to TypeScript Interfaces

MIT License GitHub Workflow Status PyPI

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, 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:
# 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
  1. 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);
  1. The resulting file will look like this:
// interface.ts
interface MyComponentProps {
  name: string;
  show: boolean;
  value: number;
}

Why @dataclass?

Dataclasses 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 partialed 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
datetime Default : string
date Default : string

Supported Enum

According python Restricted Enum subclassing doc, 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:

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.

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:

@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.

@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 :

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:

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:

// 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.

Authors

Christopher Sabater Cordero Nalyze team

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

python_to_typescript_interfaces-0.2.0.tar.gz (17.3 kB view details)

Uploaded Source

Built Distribution

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

File details

Details for the file python_to_typescript_interfaces-0.2.0.tar.gz.

File metadata

File hashes

Hashes for python_to_typescript_interfaces-0.2.0.tar.gz
Algorithm Hash digest
SHA256 fd9a608f9f4059dd9febff61d057f434fb1a63ebc7b3459c1aea0e8087db49f0
MD5 3c38695785cb4961d7746af2122d5b71
BLAKE2b-256 f3b86f03316c7c4451da3cdb88a3403220875ae97d728c354aa1559974c3994c

See more details on using hashes here.

File details

Details for the file python_to_typescript_interfaces-0.2.0-py3-none-any.whl.

File metadata

File hashes

Hashes for python_to_typescript_interfaces-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 783c4b29ba39377c442831bc55f1c201d649e21fbeaba37bb3779ad605549404
MD5 b1db67284ddc29726bbef89425c4bb28
BLAKE2b-256 04491249e7cfe7d5bf0b93bc9282d3b2a112d7e6310ee8993c5c7ef37a35324e

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