Skip to main content

Type-Driven Development for Python

Project description

pytyped

pytyped is a Python package whose goal is to enable as much type-driven development as possible in Python. We believe in using types to automate mundane and repetitive tasks. Currently, given a type, JSON decoders/encoders and metric extractors can be automatically extracted for that type.

Installation

You can install pytyped from PyPI:

pip install pytyped

pytyped is checked on Python 3.6+.

Why pytyped?

To our knowledge, pytyped is the only Python package that supports type-based automation for all typing constructs including even recursive types that, up to this day, is not even fully supported by Python itself yet. Additionally, pytyped is designed to be extensible. That is, despite the fact that JSON encoding/decoding provided by pytyped is a very nice feature, it's only one of the use cases of pytyped. In the past, we have successfully used pytyped in different scenarios such as automating report generation based on data types as well as automating DynamoDB interactions based on the type of the data that is being stored/retrieved.

In order to see how you can automate your own workflow on top of pytyped, see how JSON decoding/encoding and metric extractions use pytyped.macros to achieve type-driven meta-programming.

Currently, pytyped supports the following type classes:

  • Basic types such as int, bool, date, datetime, str, and Decimal.
  • Simple usual combinators such as List[T] and Dict[A, B].
  • Named product types such as NamedTuples or dataclasses.
  • Anonymous product types such as Tuple[T1, T2, ...].
  • Anonymous union types such as Optional[T], Union[T1, T2, ...], etc.
  • Named union types such as class hierarchies.
  • Generic types and type variables.
  • Custom functional types such as Set[T], Secret[T], etc.
  • Recursive types.

Using pytyped to extract JSON decoders/encoders

First, define your type. For example, in the following we want to define an account that can either be a personal account or a business account with personal account having one owner and possibly a co-owner while a business account has the company name as the owner and a list of persons that can represent the company.

from dataclasses import dataclass
from datetime import datetime
from typing import List
from typing import Optional

@dataclass
class Person:
    first_name: str
    last_name: str


@dataclass
class Account:
    created_at: datetime


@dataclass
class PersonalAccount(Account):
    owner: Person
    co_owner: Optional[Person]


@dataclass
class BusinessAccount(Account):
    owner: str
    representatives: List[Person]

Second, use an instance of AutoJsonDecoder and AutoJsonEncoder to extract JSON decoders and encoders as below:

from pytyped.json.decoder import AutoJsonDecoder
from pytyped.json.decoder import JsonDecoder
from pytyped.json.encoder import AutoJsonEncoder
from pytyped.json.encoder import JsonEncoder

_auto_json_decoder = AutoJsonDecoder()
_auto_json_encoder = AutoJsonEncoder()

account_decoder: JsonDecoder[Account] = _auto_json_decoder.extract(Account)
account_encoder: JsonEncoder[Account] = _auto_json_encoder.extract(Account)

Third, define some instances of the Account class:

personal_account = PersonalAccount(
    created_at = datetime.now(),
    owner = Person(first_name = "John", last_name = "Doe"),
    co_owner = None
)

business_account = BusinessAccount(
    created_at = datetime.now(),
    owner = "Doe Ltd.",
    representatives = [Person(first_name = "John", last_name = "Doe"), Person(first_name = "Jane", last_name = "Doe")]
) 

Finally, use account_encoder and account_decoder to convert data in your instances to/from JSON as below:

>>> json = account_encoder.write(personal_account)
>>> json
{'created_at': '2020-08-24T20:00:18.205347', 'owner': {'first_name': 'John', 'last_name': 'Doe'}, 'co_owner': None, 'Account': 'PersonalAccount'}
>>> account_decoder.read(json)
PersonalAccount(created_at=datetime.datetime(2020, 8, 24, 20, 0, 18, 205347), owner=Person(first_name='John', last_name='Doe'), co_owner=None)


>>> json = account_encoder.write(business_account)
>>> json
{'created_at': '2020-08-24T20:00:40.057088', 'owner': 'Doe Ltd.', 'representatives': [{'first_name': 'John', 'last_name': 'Doe'}, {'first_name': 'Jane', 'last_name': 'Doe'}], 'Account': 'BusinessAccount'}
>>> account_decoder.read(json)
BusinessAccount(created_at=datetime.datetime(2020, 8, 24, 20, 0, 40, 57088), owner='Doe Ltd.', representatives=[Person(first_name='John', last_name='Doe'), Person(first_name='Jane', last_name='Doe')])

To illustrate the types of validation that JSON decoders enable for you, consider the following example invalid JSONs:

>>> account_decoder.read({'created_at': '2020-08-24T20:00:40.057088', 'owner': 'Doe Ltd.', 'representatives': [{'first_name': 'John', 'last_name': 'Doe'}, {'first_name': 'Jane', 'last_name': 'Doe'}], 'Account': 'NewAccount'})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "~/Repos/pytyped/pytyped/json/decoder.py", line 91, in read
    raise JsDecodeException(t_or_error)
pytyped.json.decoder.JsDecodeException: Found 1 errors while validating JSON: [
  Error when decoding JSON: /Account: Unknown tag value NewAccount (possible values are: PersonalAccount, BusinessAccount).]


>>> account_decoder.read({'created_at': '2020-08-24T20:00:40.057088', 'owner': 'Doe Ltd.', 'representatives': [{'first_name': 'John', 'last_name': 'Doe'}, {'first_name': 'Jane', 'last': 'Doe'}], 'Account': 'BusinessAccount'})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/shahab/Repos/pytyped/pytyped/json/decoder.py", line 91, in read
    raise JsDecodeException(t_or_error)
pytyped.json.decoder.JsDecodeException: Found 1 errors while validating JSON: [
  Error when decoding JSON: /representatives[1]/last_name: Non-optional field was not found]

Using pytyped to extract metrics

Similar to extracting JSON decoders/encoders except that pytyped.metrics.exporter.AutoMetricExporter is used. Further explanation is WIP.

Defining New Type Automations

You can follow the examples of JSON decoders, JSON encoders, and metric exporters to define new type-based automations. You just need to extend pytyped.macros.extractor.Extractor and implement the abstrtact methods there. Further explanation is WIP.

Issues

Please report any issues to the GitHub repository for this package.

Contributors

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

pytyped-0.2.0.tar.gz (26.0 kB view details)

Uploaded Source

Built Distribution

pytyped-0.2.0-py3-none-any.whl (21.7 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: pytyped-0.2.0.tar.gz
  • Upload date:
  • Size: 26.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/4.0.0 CPython/3.9.10

File hashes

Hashes for pytyped-0.2.0.tar.gz
Algorithm Hash digest
SHA256 3e5d40a7a194b93351b15ac779eb060d36d995eeeeb9f9dcdbde744f2962f2b2
MD5 dc77794bb0d300ee999bbe9b2a2a707c
BLAKE2b-256 ee8a7158a72df174d741aefa3ad1dce043c57fa1639f5e7cf8461420a6492215

See more details on using hashes here.

File details

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

File metadata

  • Download URL: pytyped-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 21.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/4.0.0 CPython/3.9.10

File hashes

Hashes for pytyped-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 4eb40cbf22151f34bf026f039139bf90c06f73cf7a75222feaf1593b4bff8991
MD5 74ac4e7877b7936e2bd8ee170f00df26
BLAKE2b-256 bc366384bcc25d21decb5a8d633fd958fd561c727ce49bf9d9f2078544f7fc2b

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