基于graph的类型转换工具
Project description
TypeGraph
TypeGraph is a Python library designed for type conversion between various types, including custom types, built-in types, and structural types (such as lists, sets, and dictionaries). It is also compatible with Pydantic Annotated[T, Field(...)]
. The library supports both synchronous and asynchronous conversion methods.
蓦然回首,那人却在灯火阑珊处
English | 简体中文
Features
- Register type converters for synchronous and asynchronous functions.
- Automatically convert function arguments based on type annotations.
- Support for subclass, union types, and structural types conversion.
- Recursive Generic Calculation.
- Visualize the conversion graph using mermaid syntax.
Installation
Install the required dependencies with the following command:
pip install typegraph3
Or
pdm add typegraph3
Getting Started
Example: Synchronous Converter
Register and use a synchronous converter:
from typegraph import PdtConverter
converter = PdtConverter()
@converter.register_converter(int, str)
def int_to_str(value: int) -> str:
return str(value)
result = converter.convert(10, str) # "10"
print(result)
Example: Asynchronous Converter
Register and use an asynchronous converter:
import asyncio
from typegraph import PdtConverter
converter = PdtConverter()
@converter.async_register_converter(str, int)
async def str_to_int(value: str) -> int:
return int(value)
async def test_async_conversion():
result = await converter.async_convert("10", int) # 10
print(result)
asyncio.run(test_async_conversion())
Example: Protocol Types
from typing import Protocol, TypedDict, runtime_checkable
from dataclasses import dataclass
from typegraph import PdtConverter
t = PdtConverter()
class Person(Protocol):
name: str
phone: str
address: str
def get_name(self) -> str:
...
class PersonDict(TypedDict):
name: str
phone: str
address: str
class A:
name: str
phone: str
address: str
def __init__(self, name: str, phone: str, address: str):
self.name = name
self.phone = phone
self.address = address
def get_name(self) -> str:
return self.name
@dataclass
class B:
name: str
phone: str
address: str
@t.register_converter(dict, PersonDict)
def convert_dict_to_persondict(data: dict):
return PersonDict(
name=data["name"],
phone=data["phone"],
address=data["address"]
)
@t.register_converter(Person, str)
def convert_person_to_str(data: Person):
return f"{data.name} {data.phone} {data.address}"
@t.register_converter(dict, A)
def convert_dict_to_a(data: dict):
return A(data["name"], data["phone"], data["address"])
@t.register_converter(dict, B)
def convert_dict_to_b(data: dict):
return B(data["name"], data["phone"], data["address"])
@t.auto_convert()
def test(a: str):
return a
d = {"name": "John", "phone": "123", "address": "123"}
t.convert([d], list[str], debug=True)
t.show_mermaid_graph()
graph TD;
dict-->PersonDict
dict-->A
dict-->B
Person-->str
t.show_mermaid_graph()
graph TD;
dict-->PersonDict
dict-->A
dict-->B
Person-->str
A-.->Person
Converting dict[str, str] to <class 'str'> using [<class 'dict'>, <class '__main__.A'>, <class '__main__.Person'>, <class 'str'>], <function TypeConverter.get_converter.<locals>.<lambda>.<locals>.<lambda> at 0x7f1f3306fac0>
['John 123 123']
Recursive Generic Calculation
Default recursion depth is two
from typing import Iterable, TypeVar, Annotated
from pydantic import Field
from typegraph import PdtConverter
t = PdtConverter()
K = TypeVar("K")
V = TypeVar("V")
P = Annotated[int, Field(ge=0, le=10)]
@t.register_generic_converter(dict[K, V], dict[V, K])
def convert_dict_to_dict(value: dict[K, V]) -> dict[V, K]:
return {v: k for k, v in value.items()}
@t.register_generic_converter(V, Iterable[V])
def convert_to_iterable(value: V) -> Iterable[V]:
return [value]
@t.register_converter(P, int)
def convert_p_to_int(value: P) -> int:
return value
try:
t.convert(11, P)
except Exception as e:
print(e)
Pydantic Annotated[T, Feild(...)]
No converter registered for <class 'int'> to typing.Annotated[int, FieldInfo(annotation=NoneType, required=True, metadata=[Ge(ge=0), Le(le=10)])]
t.convert(5, P)
5
dict[K,V]->dict[V,K]
t.convert({1: "2", 3: "4"}, dict[int, int], debug=True)
Converting dict[int, str] to dict[str, int] using [dict[int, str], dict[str, int]], <function convert_dict_to_dict at 0x7f18e595c3a0>
{'2': 1, '4': 3}
V->Iterable[V]
t.convert(1, Iterable[Iterable[Iterable[P]]], debug=True)
Converting typing.Annotated[int, FieldInfo(annotation=NoneType, required=True, metadata=[Ge(ge=0), Le(le=10)])] to typing.Iterable[typing.Iterable[typing.Iterable[typing.Annotated[int, FieldInfo(annotation=NoneType, required=True, metadata=[Ge(ge=0), Le(le=10)])]]]] using [typing.Annotated[int, FieldInfo(annotation=NoneType, required=True, metadata=[Ge(ge=0), Le(le=10)])], typing.Iterable[typing.Annotated[int, FieldInfo(annotation=NoneType, required=True, metadata=[Ge(ge=0), Le(le=10)])]], typing.Iterable[typing.Iterable[typing.Annotated[int, FieldInfo(annotation=NoneType, required=True, metadata=[Ge(ge=0), Le(le=10)])]]], typing.Iterable[typing.Iterable[typing.Iterable[typing.Annotated[int, FieldInfo(annotation=NoneType, required=True, metadata=[Ge(ge=0), Le(le=10)])]]]]], <function PdtConverter.get_converter.<locals>.<lambda>.<locals>.<lambda> at 0x7f18e46ecf70>
[[[1]]]
Visualization
t.show_mermaid_graph()
graph TD;
node0["typing.Annotated[int, FieldInfo(annotation=NoneType, required=True, metadata=[Ge(ge=0), Le(le=10)])]"] --> node1["int"]
node0["typing.Annotated[int, FieldInfo(annotation=NoneType, required=True, metadata=[Ge(ge=0), Le(le=10)])]"] -.-> node2["typing.Iterable[typing.Annotated[int, FieldInfo(annotation=NoneType, required=True, metadata=[Ge(ge=0), Le(le=10)])]]"]
node1["int"] -.-> node3["typing.Iterable[int]"]
node3["typing.Iterable[int]"] -.-> node4["typing.Iterable[typing.Iterable[int]]"]
node2["typing.Iterable[typing.Annotated[int, FieldInfo(annotation=NoneType, required=True, metadata=[Ge(ge=0), Le(le=10)])]]"] -.-> node5["typing.Iterable[typing.Iterable[typing.Annotated[int, FieldInfo(annotation=NoneType, required=True, metadata=[Ge(ge=0), Le(le=10)])]]]"]
node5["typing.Iterable[typing.Iterable[typing.Annotated[int, FieldInfo(annotation=NoneType, required=True, metadata=[Ge(ge=0), Le(le=10)])]]]"] -.-> node6["typing.Iterable[typing.Iterable[typing.Iterable[typing.Annotated[int, FieldInfo(annotation=NoneType, required=True, metadata=[Ge(ge=0), Le(le=10)])]]]]"]
node7["dict[int, str]"] -.-> node8["dict[str, int]"]
node7["dict[int, str]"] -.-> node9["typing.Iterable[dict[int, str]]"]
node8["dict[str, int]"] -.-> node7["dict[int, str]"]
node8["dict[str, int]"] -.-> node10["typing.Iterable[dict[str, int]]"]
node9["typing.Iterable[dict[int, str]]"] -.-> node11["typing.Iterable[typing.Iterable[dict[int, str]]]"]
node10["typing.Iterable[dict[str, int]]"] -.-> node12["typing.Iterable[typing.Iterable[dict[str, int]]]"]
node6["typing.Iterable[typing.Iterable[typing.Iterable[typing.Annotated[int, FieldInfo(annotation=NoneType, required=True, metadata=[Ge(ge=0), Le(le=10)])]]]]"] -.-> node13["typing.Iterable[typing.Iterable[typing.Iterable[typing.Iterable[typing.Annotated[int, FieldInfo(annotation=NoneType, required=True, metadata=[Ge(ge=0), Le(le=10)])]]]]]"]
node13["typing.Iterable[typing.Iterable[typing.Iterable[typing.Iterable[typing.Annotated[int, FieldInfo(annotation=NoneType, required=True, metadata=[Ge(ge=0), Le(le=10)])]]]]]"] -.-> node14["typing.Iterable[typing.Iterable[typing.Iterable[typing.Iterable[typing.Iterable[typing.Annotated[int, FieldInfo(annotation=NoneType, required=True, metadata=[Ge(ge=0), Le(le=10)])]]]]]]"]
Auto-Convert Decorator
Automatically convert function arguments based on type annotations:
Synchronous
from typegraph import PdtConverter
converter = PdtConverter()
@converter.register_converter(str, int)
def str_to_int(value: str) -> int:
return int(value)
@converter.auto_convert()
def add_one(x: int) -> int:
return x + 1
result = add_one("10") # 11
print(result)
Asynchronous
from typegraph import PdtConverter
import asyncio
converter = PdtConverter()
@converter.async_register_converter(str, int)
async def str_to_int(value: str) -> int:
return int(value)
@converter.async_auto_convert()
async def add_one(x: int) -> int:
return x + 1
async def test_async():
result = await add_one("10") # 11
print(result)
asyncio.run(test_async())
Multiple Converters
from typegraph import PdtConverter, Converter
import asyncio
pdt = PdtConverter()
converter = Converter()
pdt.register_converter(int, str)(str)
converter.register_converter(str, int)(int)
pdt.add_converter(converter)
Testing
Unit tests are provided to ensure the library functions correctly. Run the tests:
pdm test
Tests cover:
- Registration and execution of synchronous converters.
- Registration and execution of asynchronous converters.
- Conversion capability checks.
- Automatic conversion of function arguments (both synchronous and asynchronous).
Visualization
You can visualize the type conversion graph:
from typegraph import PdtConverter
t = PdtConverter()
class Test:
def __init__(self, t):
self.t = t
@t.register_converter(float, Test)
def str_to_Test(input_value):
return Test(input_value)
@t.register_converter(Test, float)
def B_to_float(input_value):
return float(input_value.t)
@t.register_converter(float, str)
async def float_to_str(input_value):
return str(input_value)
t.show_mermaid_graph()
graph TD;
float-->Test
float-->str
Test-->float
The graph will be displayed using mermaid syntax, which can be rendered online or in supported environments like Jupyter Notebooks.
Supported Types
- Subclass type
- Union type
- Annotated type
Pydantic Annotated[T, Feild(...)]
- Structural type
- Protocol type
- Generic Protocol type
- TypedDict type
- Generic type
- Dataclass (Dataclass/BaseModel)
License
This project is licensed under the MIT License.
Contributing
Contributions are welcome! Please open an issue or submit a pull request for any changes.
Contact
If you have any questions or concerns, please open an issue in this repository.
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
File details
Details for the file typegraph3-0.3.2.tar.gz
.
File metadata
- Download URL: typegraph3-0.3.2.tar.gz
- Upload date:
- Size: 12.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: pdm/2.19.1 CPython/3.10.12 Linux/6.8.0-1014-azure
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 809342c329159849cd55df7099e14b080a56d40d0ab72e5528b7d0f4abe28706 |
|
MD5 | c12cdc83bea134d8101d5d50e2e2ac3b |
|
BLAKE2b-256 | 48b1de743cf1bd8457923741603919741a0b600890fa156a6a3a56039f903581 |
File details
Details for the file typegraph3-0.3.2-py3-none-any.whl
.
File metadata
- Download URL: typegraph3-0.3.2-py3-none-any.whl
- Upload date:
- Size: 9.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: pdm/2.19.1 CPython/3.10.12 Linux/6.8.0-1014-azure
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 83815d1db319ff5f0956823bd62c85ef3467496d5a80bba3dcd6663cc1b2263b |
|
MD5 | 589cf24ee7b5d258bf4425e14abdefd9 |
|
BLAKE2b-256 | 1074a083fe521f61b8628ab8877292a914216651232d98d1d5595d02d95e1747 |