Skip to main content

基于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.

蓦然回首,那人却在灯火阑珊处

CodeFactor GitHub CodeQL

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())

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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

typegraph3-0.2.0.tar.gz (12.2 kB view details)

Uploaded Source

Built Distribution

typegraph3-0.2.0-py3-none-any.whl (8.9 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: typegraph3-0.2.0.tar.gz
  • Upload date:
  • Size: 12.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: pdm/2.17.3 CPython/3.10.12 Linux/6.5.0-1024-azure

File hashes

Hashes for typegraph3-0.2.0.tar.gz
Algorithm Hash digest
SHA256 c72788b70380959ba500d6a3985222720d66f865f7b628e7c36dc8704d8730c1
MD5 595f77ae75fb833a6cb5b32c66760135
BLAKE2b-256 757f346dcfbfd1b7347cf07d1689873552dc2d67cf3b47061c4f297d19bf2506

See more details on using hashes here.

File details

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

File metadata

  • Download URL: typegraph3-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 8.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: pdm/2.17.3 CPython/3.10.12 Linux/6.5.0-1024-azure

File hashes

Hashes for typegraph3-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 374e6395270cf380bb3867e0b5b105c3a83a2e9efa6821c933520022dae25399
MD5 0fa46c4f81fd1d2be0b140239b7e8b7b
BLAKE2b-256 2ac4deab3920b2e3ae9051a814c903113bfb42707ebd4aec5170da8b00592618

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