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, andDecimal. - Simple usual combinators such as
List[T]andDict[A, B]. - Named product types such as
NamedTuples ordataclasses. - 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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3e5d40a7a194b93351b15ac779eb060d36d995eeeeb9f9dcdbde744f2962f2b2
|
|
| MD5 |
dc77794bb0d300ee999bbe9b2a2a707c
|
|
| BLAKE2b-256 |
ee8a7158a72df174d741aefa3ad1dce043c57fa1639f5e7cf8461420a6492215
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4eb40cbf22151f34bf026f039139bf90c06f73cf7a75222feaf1593b4bff8991
|
|
| MD5 |
74ac4e7877b7936e2bd8ee170f00df26
|
|
| BLAKE2b-256 |
bc366384bcc25d21decb5a8d633fd958fd561c727ce49bf9d9f2078544f7fc2b
|