Skip to main content

Python-Interoperability for Typescript-Interfaces

Project description

ts2python

Stand With Ukraine

Python-interoperability for Typescript-Interfaces. Transpiles TypeScript-Interface-definitions to Python TypedDicts, plus support for run-time type-checking of JSON-data.

License and Source Code

ts2python is open source software under the Apache 2.0 License

Copyright 2021-2023 Eckhart Arnold arnold@badw.de, Bavarian Academy of Sciences and Humanities

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

https://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

The complete source-code of ts2python can be downloaded from the git-repository.

Purpose

When processing JSON data, as for example, form a JSON-RPC call, with Python, it would be helpful to have Python-definitions of the JSON-structures at hand, in order to solicit IDE-Support, static type checking and, potentially to enable structural validation at runtime.

There exist different technologies for defining the structure of JSON-data. Next to JSON-schema, a de facto very popular technology for defining JSON-obejcts are Typescript-Interfaces. For example, the language server protocol defines the structure of the JSON-data exchanged between client and server with Typescript-Interfaces.

In order to enable structural validation on the Python-side, ts2python transpiles the typescript-interface definitions to Python-data structure definitions, primarily, TypedDicts, but with some postprocessing it can also be adjusted to other popular models for records or data structures in Python, e.g. pydantic-Classes and the like.

ts2python aims to support translation of TypeScript-Interfaces on two different tiers:

  1. Tier 1: Transpilation of passive data-structures, that is, Typescript-definition-files that contain only data definitions and no function definitions and, in particular, only "passive" Typescript-Interface that define data-structures but do not contain any methods.

  2. Tier 2: Tanspilation of active data-structures, function- and method-definitions, i.e. Translation of (almost) any Typescript-definition-file.

Status

Presently, Tier 1 support, i.e. transpilation of passive data structures works quite well. So, for example, all Interfaces from the language server protocol V3.16 can be transpiled to Python Typed-Dicts.

Tier 2 support is still very much work in progress. I am using "vscode.d.ts"-definition file as test-case. Some things work, but there are still some unsupported constructs, and the Python code emitted for features that go beyond Tier 1 may not even be valid Python all the time! Please, keep that in mind.

The documentation presently only covers Tier 1 support.

Installation

ts2python can be installed from the command line with the command:

$ pip install ts2python

ts2python requires the parsing-expression-grammar-framework DHParser which will automatically be installed as a dependency by the pip-command. ts2python requires at least Python Version 3.8 to run. (If there is any interest, I might backport it to Python 3.6.) However, the Python-code it produces is backwards compatible down to Python 3.6, if the typing extensions have been installed.

Usage

In order to generate TypedDict-classes from Typescript-Interfaces, run ts2python on the Typescript-Interface definitions:

$ ts2python interfaces.ts

This generates a .py-file in same directory as the source file that contains the TypedDict-classes and can simpy be imported in Python-Code:

from interfaces import *

JSON-data which adheres to a specific structure (no matter whether defined on the typescript side via interfaces or on the Python-side via TypedDicts) can easily be interchanged and deserialized:

import json
request_msg: RequestMessage = json.loads(input_data)

The root-type (RootMessage in the above example) can be arbitrarily complex and deeply nested.

Calling ts2python from another Python-script:

The ts2python-Parser can, of course, also be accessed directly from Python with the followin imports and function calls:

from ts2python.ts2pthonParser import process_file
...
process_file("SOURCE.ts", "DESTINATION.py")

Or, use:

from ts2pthon.ts2pythonParser import compile_src, serialize_result
...
result, errors = compile_src(DOCUMENT)
if errors:
    for e in errors:  print(e)
else:
   print(serialize_result(result))

Validation

ts2python ships support for runtime-type validation. While type errors can be detected by static type checkers, runtime type validation can be useful when processing data from an outside source which cannot statically be checked, like, for example, json-data stemming from an RPC-call. ts2python runtime-type validation can be invoked via dedicated functions or via decorator as in this example:

from ts2python.json_validation import TypedDict, type_check

class Position(TypedDict, total=True):
    line: int
    character: int 

class Range(TypedDict, total=True):
    start: Position
    end: Position

@type_check
def middle_line(rng: Range) -> Position:
    line = (rng['start']['line'] + rng['end']['line']) // 2
    character = 0
    return Position(line=line, character=character)

data = {'start': {'line': 1, 'character': 1},
       'end': {'line': 8, 'character': 17}}
assert middle_line(data) == {'line': 4, 'character': 0}

malformed_data = {'start': 1, 'end': 8}
middle_line(malformed_data)  # <- TypeError raised by @type_check 

With the type decorator the last call fails with a TypeError:

TypeError: Parameter "rng" of function "middle_line" failed the type-check, because:
Type error(s) in dictionary of type <class '__main__.Range'>:
Field start: '1' is not of <class '__main__.Position'>, but of type <class 'int'>
Field end: '8' is not of <class '__main__.Position'>, but of type <class 'int'>

Both the call and the return types can be validated.

Python-Version compatibility

By default, ts2python generates code that is compatibel with Python 3.7 and above. This code is not strictly standard-conform, because it imitates certain features that became available only with higher versions (like "NotRequired" for individual TypedDict-Fields) with other means. "Not strictly standard-conform" means, the code works, but type-checkers might complain.

In order to generate code for higher Python-Versions only, you can use the compatibility switch from the command line:

$ ts2python --compatibility 3.11 interfaces.ts

In this example, the generated code is compatible only with Python version 3.11 and above. To achive full compatibility with type checkers (e.g. Pylance) it is advisable also use the -a toplevel switch (see below).

Type Checkers

The output ts2python is somewhat more liberal than what strict typecheckers like mypy or pylance seem to allow. In particular class definitions inside TypedDicts are considered illegal by the specification und thus marked as an error by some type-checkers. Use the command-line switch -a toplevel to generate Python-code that is more acceptable to type checkers, e.g.:

$ ts2python --compatibility 3.11 -a toplevel interfaces.ts

However, IMHO defining nested anonymous TypeScript interfaces on the toplevel in the Python code can make the code quite a bit less readable thatn allowing ts2python to define them as local classes of TypedDict-classes.

Full Documentation

See ts2python.readthedocs.io for the comprehensive documentation of ts2python

Tests and Demonstration

The git-repository of ts2python contains unit-tests as well as doctests. After cloning ts2python from the git-repository with:

$ git clone https://github.com/jecki/ts2python

the unit tests can be found in the tests subdirectory. Both the unit and the doctests can be run by changing to the tests-sub-directory and calling the runner.py-skript therein.

$ cd tests
$ python runner.py

It is also possible to run the tests with pytest or nose, in case you have either of theses testing-frameworks installed.

For a demonstration how the TypeScript-Interfaces are transpiled to Python-code, run the demo.sh-script (or demo.bat on Windows) in the "demo"-sub-directory or the ts2python-repository.

Once, you have run the demo.sh-script you can also run the test.sh which tests the compatibility with every Python-Version higher or equal than 3.7 that is installed on your system.

Or, run the tst_ts2python_grammar.py in the ts2python-directory and look up the grammar-test-reports in the "REPORT"-sub-directory of the "test_grammar"-subdirectory.

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

ts2python-0.7.5.tar.gz (70.0 kB view details)

Uploaded Source

Built Distribution

ts2python-0.7.5-py3-none-any.whl (83.8 kB view details)

Uploaded Python 3

File details

Details for the file ts2python-0.7.5.tar.gz.

File metadata

  • Download URL: ts2python-0.7.5.tar.gz
  • Upload date:
  • Size: 70.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/1.8.3 CPython/3.12.4 Linux/6.6.40-1-MANJARO

File hashes

Hashes for ts2python-0.7.5.tar.gz
Algorithm Hash digest
SHA256 85a68b8dfc159296fb6151c347c508df69694fadde0c64e21d396da4103972f4
MD5 c8e64a11fc35d3172dfca473dd8510bf
BLAKE2b-256 10b28ba22e1de6f8611c8692610ddc037377ea08314a73490e962e4213a106d8

See more details on using hashes here.

File details

Details for the file ts2python-0.7.5-py3-none-any.whl.

File metadata

  • Download URL: ts2python-0.7.5-py3-none-any.whl
  • Upload date:
  • Size: 83.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/1.8.3 CPython/3.12.4 Linux/6.6.40-1-MANJARO

File hashes

Hashes for ts2python-0.7.5-py3-none-any.whl
Algorithm Hash digest
SHA256 8fb212ced861bce3f053b08a4fbf57cf2e949ecf28eeb92bd133d26e3d02b9d2
MD5 71949928271a94b9380ec191970eabc5
BLAKE2b-256 d5882b02e69ffe34562c856bff8589c7c350950a9e450333e38d870fab0a78a6

See more details on using hashes here.

Supported by

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