# Welcome to `typelib`
[![Version][version]][version]
[![License: MIT][license]][license]
[![Python Versions][python]][python]
[![Code Size][code-size]][repo]
[![CI][ci-badge]][ci]
[![Coverage][cov-badge]][coverage]
[![Code Style][style-badge]][style-repo]
## Python's Typing Toolkit
`typelib` provides a sensible, non-invasive, production-ready toolkit for leveraging
Python type annotations at runtime.
## Quickstart
### Installation
```shell
poetry add 'typelib[json]'
```
### Bring Your Own Models
We don't care how your data model is implemented - you can use [`dataclasses`][],
[`TypedDict`][typing.TypedDict], [`NamedTuple`][typing.NamedTuple], a plain collection,
a custom class, or any other modeling library. As long as your type is valid at runtime,
we'll support it.
### The How and the Where
#### How: The High-Level API
We have a simple high-level API which should handle most production use-cases:
```python
from __future__ import annotations
import dataclasses
import datetime
import decimal
import typelib
@dataclasses.dataclass(slots=True, weakref_slot=True, kw_only=True)
class BusinessModel:
op: str
value: decimal.Decimal
id: int | None = None
created_at: datetime.datetime | None = None
codec = typelib.codec(BusinessModel)
instance = codec.decode(b'{"op":"add","value":"1.0"}')
print(instance)
#> BusinessModel(op='add', value=decimal.Decimal('1.0'), id=None, created_at=None)
encoded = codec.encode(instance)
print(encoded)
#> b'{"op":"add","value":"1.0","id":null,"created_at":null}'
```
/// tip
Looking for more? Check out our [API Reference][typelib] for the high-level API.
///
#### Where: At the Edges of Your Code
You can integrate this library at the "edges" of your code - e.g., at the integration
points between your application and your client or you application and your data-store:
```python
from __future__ import annotations
import dataclasses
import datetime
import decimal
import operator
import random
import typelib
class ClientRPC:
def __init__(self):
self.codec = typelib.codec(BusinessModel)
def call(self, inp: bytes) -> bytes:
model = self.receive(inp)
done = self.op(model)
return self.send(done)
@staticmethod
def op(model: BusinessModel) -> BusinessModel:
op = getattr(operator, model.op)
return dataclasses.replace(
model,
value=op(model.value, model.value),
id=random.getrandbits(64),
created_at=datetime.datetime.now(tz=datetime.UTC)
)
def send(self, model: BusinessModel) -> bytes:
return self.codec.encode(model)
def receive(self, data: bytes) -> BusinessModel:
return self.codec.decode(data)
@dataclasses.dataclass(slots=True, weakref_slot=True, kw_only=True)
class BusinessModel:
op: str
value: decimal.Decimal
id: int | None = None
created_at: datetime.datetime | None = None
```
#### Where: Between Layers in Your Code
You can integrate this library to ease the translation of one type to another:
```python
from __future__ import annotations
import dataclasses
import datetime
import decimal
import typing as t
import typelib
@dataclasses.dataclass(slots=True, weakref_slot=True, kw_only=True)
class BusinessModel:
op: str
value: decimal.Decimal
id: int | None = None
created_at: datetime.datetime | None = None
class ClientRepr(t.TypedDict):
op: str
value: str
id: str | None
created_at: datetime.datetime | None
business_codec = typelib.codec(BusinessModel)
client_codec = typelib.codec(ClientRepr)
# Initialize your business model directly from your input.
instance = business_codec.decode(
b'{"op":"add","value":"1.0","id":"10","created_at":"1970-01-01T00:00:00+0000}'
)
print(instance)
#> BusinessModel(op='add', value=Decimal('1.0'), id=10, created_at=datetime.datetime(1970, 1, 1, 0, 0, fold=1, tzinfo=Timezone('UTC')))
# Encode your business model into the format defined by your ClientRepr.
encoded = client_codec.encode(instance)
print(encoded)
#> b'{"op":"add","value":"1.0","id":"10","created_at":"1970-01-01T00:00:00+00:00"}'
```
/// tip
There's no need to initialize your ClientRepr instance to leverage its codec, as long
as:
1. The instance you pass in has the same overlap of required fields.
2. The values in the overlapping fields can be translated to the target type.
///
## Why `typelib`
`typelib` provides a **simple, non-invasive API** to make everyday data wrangling in
your production applications easy and reliable.
### We DO
1. Provide an API for marshalling and unmarshalling data based upon type annotations.
2. Provide an API for integrating our marshalling with over-the-wire serialization and
deserialization.
3. Provide fine-grained, high-performance, runtime introspection of Python types.
4. Provide future-proofing to allow for emerging type annotation syntax.
### We DON'T
1. Require you to inherit from a custom base class.
2. Require you to use custom class decorators.
3. Rely upon generated code.
## How It Works
`typelib`'s implementation is unique among runtime type analyzers - we use an iterative,
graph-based resolver to build a predictable, static ordering of the types represented by
an annotation. We have implemented our type-resolution algorithm in isolation from our
logic for marshalling and unmarshalling as a simple iterative loop, making the logic
simple to reason about.
/// tip
Read a detailed discussion [here](./graph.md).
///
[pypi]: https://pypi.org/project/typelib/
[version]: https://img.shields.io/pypi/v/typelib.svg
[license]: https://img.shields.io/pypi/l/typelib.svg
[python]: https://img.shields.io/pypi/pyversions/typelib.svg
[repo]: https://github.com/seandstewart/python-typelib
[code-size]: https://img.shields.io/github/languages/code-size/seandstewart/python-typelib.svg?style=flat
[ci-badge]: https://github.com/seandstewart/python-typelib/actions/workflows/validate.yml/badge.svg
[ci]: https://github.com/seandstewart/python-typelib/actions/workflows/validate.ym
[cov-badge]: https://codecov.io/gh/seandstewart/python-typelib/graph/badge.svg?token=TAM7VCTBHD
[coverage]: https://codecov.io/gh/seandstewart/python-typelib
[style-badge]: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json
[style-repo]: https://github.com/astral-sh/ruff
Raw data
{
"_id": null,
"home_page": "https://github.com/seandstewart/typelib",
"name": "typelib",
"maintainer": null,
"docs_url": null,
"requires_python": "<4.0,>=3.9",
"maintainer_email": null,
"keywords": "typing, data, annotations, validation, serdes",
"author": "Sean Stewart",
"author_email": "sean.stewart@hey.com",
"download_url": "https://files.pythonhosted.org/packages/ef/c8/10effcfe55c14f6c99d80aa31136bae5d3b3afc1f641e4b6a977ddc4801a/typelib-0.1.9.tar.gz",
"platform": null,
"description": "# Welcome to `typelib`\n\n[![Version][version]][version]\n[![License: MIT][license]][license]\n[![Python Versions][python]][python]\n[![Code Size][code-size]][repo]\n[![CI][ci-badge]][ci]\n[![Coverage][cov-badge]][coverage]\n[![Code Style][style-badge]][style-repo]\n\n## Python's Typing Toolkit\n\n`typelib` provides a sensible, non-invasive, production-ready toolkit for leveraging \nPython type annotations at runtime. \n\n## Quickstart\n\n### Installation\n\n```shell\npoetry add 'typelib[json]'\n```\n\n### Bring Your Own Models\n\nWe don't care how your data model is implemented - you can use [`dataclasses`][], \n[`TypedDict`][typing.TypedDict], [`NamedTuple`][typing.NamedTuple], a plain collection,\na custom class, or any other modeling library. As long as your type is valid at runtime, \nwe'll support it.\n\n\n### The How and the Where\n\n#### How: The High-Level API\n\nWe have a simple high-level API which should handle most production use-cases:\n\n```python\nfrom __future__ import annotations\n\nimport dataclasses\nimport datetime\nimport decimal\n\n\nimport typelib\n\n@dataclasses.dataclass(slots=True, weakref_slot=True, kw_only=True)\nclass BusinessModel:\n op: str\n value: decimal.Decimal\n id: int | None = None\n created_at: datetime.datetime | None = None\n \n\ncodec = typelib.codec(BusinessModel)\ninstance = codec.decode(b'{\"op\":\"add\",\"value\":\"1.0\"}')\nprint(instance)\n#> BusinessModel(op='add', value=decimal.Decimal('1.0'), id=None, created_at=None)\nencoded = codec.encode(instance)\nprint(encoded)\n#> b'{\"op\":\"add\",\"value\":\"1.0\",\"id\":null,\"created_at\":null}'\n```\n\n/// tip\nLooking for more? Check out our [API Reference][typelib] for the high-level API.\n///\n\n\n#### Where: At the Edges of Your Code\n\nYou can integrate this library at the \"edges\" of your code - e.g., at the integration\npoints between your application and your client or you application and your data-store:\n\n```python\nfrom __future__ import annotations\n\nimport dataclasses\nimport datetime\nimport decimal\nimport operator\nimport random\n\nimport typelib\n\n\nclass ClientRPC:\n def __init__(self):\n self.codec = typelib.codec(BusinessModel)\n\n def call(self, inp: bytes) -> bytes:\n model = self.receive(inp)\n done = self.op(model)\n return self.send(done)\n\n @staticmethod\n def op(model: BusinessModel) -> BusinessModel:\n op = getattr(operator, model.op)\n return dataclasses.replace(\n model,\n value=op(model.value, model.value),\n id=random.getrandbits(64),\n created_at=datetime.datetime.now(tz=datetime.UTC)\n )\n\n def send(self, model: BusinessModel) -> bytes:\n return self.codec.encode(model)\n\n def receive(self, data: bytes) -> BusinessModel:\n return self.codec.decode(data)\n\n\n@dataclasses.dataclass(slots=True, weakref_slot=True, kw_only=True)\nclass BusinessModel:\n op: str\n value: decimal.Decimal\n id: int | None = None\n created_at: datetime.datetime | None = None\n\n```\n\n#### Where: Between Layers in Your Code\n\nYou can integrate this library to ease the translation of one type to another:\n\n```python\nfrom __future__ import annotations\n\nimport dataclasses\nimport datetime\nimport decimal\nimport typing as t\n\n\nimport typelib\n\n@dataclasses.dataclass(slots=True, weakref_slot=True, kw_only=True)\nclass BusinessModel:\n op: str\n value: decimal.Decimal\n id: int | None = None\n created_at: datetime.datetime | None = None\n \n\nclass ClientRepr(t.TypedDict):\n op: str\n value: str\n id: str | None\n created_at: datetime.datetime | None\n\n\nbusiness_codec = typelib.codec(BusinessModel)\nclient_codec = typelib.codec(ClientRepr)\n# Initialize your business model directly from your input.\ninstance = business_codec.decode(\n b'{\"op\":\"add\",\"value\":\"1.0\",\"id\":\"10\",\"created_at\":\"1970-01-01T00:00:00+0000}'\n)\nprint(instance)\n#> BusinessModel(op='add', value=Decimal('1.0'), id=10, created_at=datetime.datetime(1970, 1, 1, 0, 0, fold=1, tzinfo=Timezone('UTC')))\n# Encode your business model into the format defined by your ClientRepr.\nencoded = client_codec.encode(instance)\nprint(encoded)\n#> b'{\"op\":\"add\",\"value\":\"1.0\",\"id\":\"10\",\"created_at\":\"1970-01-01T00:00:00+00:00\"}'\n\n```\n\n/// tip\nThere's no need to initialize your ClientRepr instance to leverage its codec, as long\nas:\n\n1. The instance you pass in has the same overlap of required fields.\n2. The values in the overlapping fields can be translated to the target type.\n///\n\n## Why `typelib`\n\n`typelib` provides a **simple, non-invasive API** to make everyday data wrangling in \nyour production applications easy and reliable.\n\n### We DO\n\n1. Provide an API for marshalling and unmarshalling data based upon type annotations.\n2. Provide an API for integrating our marshalling with over-the-wire serialization and \n deserialization.\n3. Provide fine-grained, high-performance, runtime introspection of Python types.\n4. Provide future-proofing to allow for emerging type annotation syntax.\n\n### We DON'T\n\n1. Require you to inherit from a custom base class.\n2. Require you to use custom class decorators.\n3. Rely upon generated code.\n\n## How It Works\n\n`typelib`'s implementation is unique among runtime type analyzers - we use an iterative,\ngraph-based resolver to build a predictable, static ordering of the types represented by\nan annotation. We have implemented our type-resolution algorithm in isolation from our \nlogic for marshalling and unmarshalling as a simple iterative loop, making the logic \nsimple to reason about.\n\n/// tip\nRead a detailed discussion [here](./graph.md).\n///\n\n\n[pypi]: https://pypi.org/project/typelib/\n[version]: https://img.shields.io/pypi/v/typelib.svg\n[license]: https://img.shields.io/pypi/l/typelib.svg\n[python]: https://img.shields.io/pypi/pyversions/typelib.svg\n[repo]: https://github.com/seandstewart/python-typelib\n[code-size]: https://img.shields.io/github/languages/code-size/seandstewart/python-typelib.svg?style=flat\n[ci-badge]: https://github.com/seandstewart/python-typelib/actions/workflows/validate.yml/badge.svg\n[ci]: https://github.com/seandstewart/python-typelib/actions/workflows/validate.ym\n[cov-badge]: https://codecov.io/gh/seandstewart/python-typelib/graph/badge.svg?token=TAM7VCTBHD\n[coverage]: https://codecov.io/gh/seandstewart/python-typelib\n[style-badge]: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json\n[style-repo]: https://github.com/astral-sh/ruff\n\n",
"bugtrack_url": null,
"license": null,
"summary": "A toolkit for marshalling, unmarshalling, and runtime validation leveraging type annotations.",
"version": "0.1.9",
"project_urls": {
"Homepage": "https://github.com/seandstewart/typelib",
"Repository": "https://github.com/seandstewart/typelib"
},
"split_keywords": [
"typing",
" data",
" annotations",
" validation",
" serdes"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "e297d80276ce4f1c2e2a5893f7ff075024fb86aa69f6e269e5cf3b94cff80321",
"md5": "a22d22974b04cd9bcc2bcd977b389a1f",
"sha256": "abb60a343db1432bd01d14af571d01d617da52e74dd9fdf77dc6b0c40269690e"
},
"downloads": -1,
"filename": "typelib-0.1.9-py3-none-any.whl",
"has_sig": false,
"md5_digest": "a22d22974b04cd9bcc2bcd977b389a1f",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": "<4.0,>=3.9",
"size": 51814,
"upload_time": "2024-10-30T17:29:24",
"upload_time_iso_8601": "2024-10-30T17:29:24.619410Z",
"url": "https://files.pythonhosted.org/packages/e2/97/d80276ce4f1c2e2a5893f7ff075024fb86aa69f6e269e5cf3b94cff80321/typelib-0.1.9-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "efc810effcfe55c14f6c99d80aa31136bae5d3b3afc1f641e4b6a977ddc4801a",
"md5": "a963761fbee4feca6e6c3afa6bbbe1b4",
"sha256": "ecf18daff8b03b17a5eaf921b99be6ee7922ec2a975976d1c45a0e58e47f8b8a"
},
"downloads": -1,
"filename": "typelib-0.1.9.tar.gz",
"has_sig": false,
"md5_digest": "a963761fbee4feca6e6c3afa6bbbe1b4",
"packagetype": "sdist",
"python_version": "source",
"requires_python": "<4.0,>=3.9",
"size": 46465,
"upload_time": "2024-10-30T17:29:26",
"upload_time_iso_8601": "2024-10-30T17:29:26.572894Z",
"url": "https://files.pythonhosted.org/packages/ef/c8/10effcfe55c14f6c99d80aa31136bae5d3b3afc1f641e4b6a977ddc4801a/typelib-0.1.9.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-10-30 17:29:26",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "seandstewart",
"github_project": "typelib",
"github_not_found": true,
"lcname": "typelib"
}