ts2python Documentation¶
ts2python is a transpiler that converts TypeScript-interfaces to Python TypedDict-classes and provides runtime json-validation against those interfaces/TypedDicts.
ts2python is licensed under the Apache-2.0 open source license. The source code can be cloned freely from: https://github.com/jecki/ts2python
Installation¶
ts2python can be installed as usual with Python’s package-manager “pip”:
$ pip install ts2python
The ts2python-transpiler requires at least Python Version 3.8 to run. However, the output that ts2python produces is backwards compatible with Python 3.7, unless otherwise specified (see below). The only dependency of ts2python is the parser-generator DHParser.
Usage¶
The easiest way to use ts2python is by running ts2pythonExplorer, which provides a simple graphical user interface to ts2python. You can just paste your Typescript-Interface definitions into the top text-area and click on the “Generate Python-Code”-button. You can then simply copy the generated code from the text-area below into you Python source code.
ts2pythonExplorer also allows you to experiment with different version-compatibility settings for the generated Python-Code. These settings can be saved as a configuration file to your project’s home-directory from where the ts2pyhon-command will automatically apply them without the need of specifying any particular command line options every time it is called.
From the command line, generating Python-pendants for Typescript-interfaces is as simple as calling:
$ ts2python interfaces.ts
and then importing the generated interfaces.py by:
from interfaces import *
For every typescript interface in the interfaces.ts file the generated
Python module contains a TypedDict-class with the same name that defines
the same data structure as the Typescript-interface. Typescript-data serialized
as json can simply be deserialized by Python-code as long as you know the
type of the root data structure beforehand. Say, the root data structure
is RequestMessage than retrieving the RequestMessage-object from a
json-string generated by a Typescript-script requires nothing more than
deserializing this string:
import json
request_msg: RequestMessage = json.loads(input_data)
The only requirement is that the root-type of the json-data is known beforehand. Everything else simply falls into place.
Backwards compatibility¶
ts2python tries to be as backwards compatible as possible. To run ts2python you need at least Python version 3.8. The code ts2python generates is backwards compatible down to version 3.7. If you do not need to be compatible with older versions, you can use the –compatibility [VERSION] switch to generate code for newer versions only, e.g.:
$ ts2python --compatibility 3.11 [FILENAME.ts]
Usually, this code is somewhat cleaner than the fully
compatible code. Also, certain features like type-statments (Python 3.12 and
above) or the ReadOnly-qualifier (Python 3.13 and higher) are only available
at higher compatibility levels!
In order to achieve full conformity with most type-checkers, it is advisable
to use compatibility level 3.11 and also add the -a toplevel switch
to always turn anonymous TypeScript-interfaces into top-level classes, rather
than locally defined classes. Local classes are not allowed for Python TypedDicts,
although they work perfectly well - except that type-checkers like pylance
emit an error-message.
With compatibility-level 3.11 and above, the generated code does not need to use ts2python’s “typeddict_shim”-compatibility layer, any more. This greatly simplifies the import-block at the beginning of the generated code and eliminates one of two dependencies of the generated code on the ts2python-package.
The other remaining dependency “singledispatch_shim” can be removed from the generated code by hand, if there are no single-dispatch functions in the code that dispatch on a forward-referenced type, i.e. a type that is denoted in the code by a string containing the type-name rather than the type-name directly. (It’s a current limitation of functools.singledispatch that it cannot handle forward references.)
Command-line switches¶
The Python output of ts2pythonParser can be controlled by the following command line-switches:
-cfollowed by a Python Version-number, e.g.-c 3.12-pfollowed by a comma separated list of PEP-numbers, e.g.-p 563,601. Supported PEPs are:435 - use Enums (Python 3.4)
563 - use postponed evaluation (Python 3.7)
604 - use type union (Python 3.10)
613 - use explicit type alias (Python 3.10 - 3.11)
646 - use variadic Generics (Python 3.11)
655 - use NotRequired instead of Optional (Python3.11)
695 - use type parameters (Python 3.12)
705 - allow ReadOnly (Python 3.13)
Setting a Python Version-Number with the
-cswitch also automatically sets all PEPs that have been implemented with that version, except for PEP 563 which must be set explicitly with the-p 563switch as this concerns an optional feature for Python-Versions 3.7-3.12 which will be turned on with thefrom future import __annotation__statement at the beginnig of the generated source code.-kpreserves Typescript-multiline comments and adds them as Python-comments to the generated source-code-afollowed by one of the four possible keywordslocal(default),toplevel,functional,type. These are four different styles for transpiling anonymous interfaces. The default rulelocalis not strictly conformant with the type-rules for TypedDicts. For full type-checker conformance usetoplevel. The other two keywords, “functional” and “type” should be considered as experimental as they have seen little testing.-ofollowed by the name of an output-directory for the generated Python code. If an output-directory is chosen the results will be written as files to this directory, rather than printed to the console (stdout).
Current Limitations¶
Presently, ts2python is mostly limited to Typescript-Interfaces that do not contain any methods. The language server-protocol-definitions can be transpiled successfully.
Hacking ts2python¶
Hacking ts2python is not easy. (Sorry!) The reason is that ts2python was primarily designed for a relatively limited application case, i.e. transpiling interface definitions. In order to keep things simple, the abstract syntax tree (AST) from the TypeScript source is directly converted to Python code, rather than transforming the TypeScript-AST to a Python-AST first.
Adding such a tree-to-tree-transformation before the Python code generation stage certainly would have made sense - among other things, because some components need to be re-ordered, since Python does not know anonymous classes/interfaces. However, for the above mentioned restricted purpose this appeared to me like overengineering. Meanwhile, I have come to regret that, because it makes adding more features harder.
In order to keep track of code snippets
that must be reordered or of names and scopes that need to be completed
or filled in later or, more generally, for any task that cannot be
completed locally, when transforming a particular node of the AST,
the present implementation keeps several different stacks in
instance-variables of the ts2pythonCompiler-object, namely,
known_types, local_classes, base_classes, obj_name,
scope_type, optional_keys. Lookout for where those stacks are used
when you to change something in the ts2python-source yourself!
Alternatively, you can also use ts2python to just output the AST of
a Typescript-file with interface definitions by using the --target AST
command line swith and then start from there. Example:
$ ts2python --target AST --serialize XML interfaces.ts
Other serialiazations for the AST are available: SXML (S-expression), ndst, json or simply an indented tree.
Contents:
- Basic Usage
- Type Mapping Explained
- Mapping of Interfaces
- Mapping of Field Types
- Mapping of Literals
- Mapping of Enumerations
- Mapping of Index Signatures
- Mapping of Tuple Types
- Mapping of Records
- Mapping of Anonymous Interfaces
- Alternative Representations for Anonymous Interfaces
- Namespaces and Generics
- TypeAliases
- Imports
- Types derived from other Types
- Runtime Validation