"""
This module contains type hinting utilities for the VmaxBuilder project. These include
ready-made Union types to be imported, as well as custom type hints for commonly
used data structures within the project. It also includes a function used in
development and debugigng for checking the necessary type hint for an object.
"""
from dataclasses import dataclass
from typing import Any, Dict, FrozenSet, List, Mapping, Tuple, Union
# create a list or dict type hint
ListOrDict = Union[List[Any], Dict[Any, Any]]
[docs]
@dataclass(frozen=True)
class TypeNode:
"""Generated: validation needed.
Description:
Immutable node representing a concrete named type with optional type arguments.
Args:
name (str): Type name, e.g. ``"int"`` or ``"List"``.
args (Tuple[TypeNode | UnionNode, ...]): Optional nested type arguments.
"""
name: str
args: Tuple[Union["TypeNode", "UnionNode"], ...] = ()
[docs]
@dataclass(frozen=True)
class UnionNode:
"""Generated: validation needed.
Description:
Immutable node representing a union of multiple type nodes.
Args:
members (FrozenSet[TypeNode]): Set of member type nodes forming the union.
"""
members: FrozenSet[TypeNode]
def _merge_types(types: set) -> UnionNode | TypeNode:
"""Generated: validation needed.
Description:
Collapse a set of type nodes into a single node or union node.
Args:
types (set): Set of ``TypeNode`` or ``UnionNode`` instances.
Returns:
UnionNode | TypeNode: Single node when set has one member, otherwise UnionNode.
"""
if len(types) == 1:
return next(iter(types))
return UnionNode(frozenset(types))
def _infer_type(obj: Any) -> TypeNode | UnionNode: # noqa: C901
"""Generated: validation needed.
Description:
Recursively infer the structural type of a Python object.
Args:
obj (Any): Object to inspect.
Returns:
TypeNode | UnionNode: Inferred type node tree.
"""
if obj is None:
return TypeNode("None")
if isinstance(obj, bool):
return TypeNode("bool")
if isinstance(obj, int):
return TypeNode("int")
if isinstance(obj, float):
return TypeNode("float")
if isinstance(obj, str):
return TypeNode("str")
if isinstance(obj, Mapping):
if not obj:
return TypeNode("Dict", (TypeNode("Any"), TypeNode("Any")))
key_types = {_infer_type(k) for k in obj.keys()}
value_types = {_infer_type(v) for v in obj.values()}
return TypeNode(
"Dict",
(
_merge_types(key_types),
_merge_types(value_types),
),
)
if isinstance(obj, list):
if not obj:
return TypeNode("List", (TypeNode("Any"),))
elem_types = {_infer_type(e) for e in obj}
return TypeNode(
"List",
(_merge_types(elem_types),),
)
elif isinstance(obj, set):
if not obj:
return TypeNode("Set", (TypeNode("Any"),))
elem_types = {_infer_type(e) for e in obj}
return TypeNode(
"Set",
(_merge_types(elem_types),),
)
elif isinstance(obj, tuple):
if not obj:
return TypeNode("Tuple", ())
elem_types = tuple(_infer_type(e) for e in obj)
return TypeNode(
"Tuple",
elem_types,
)
return TypeNode(type(obj).__name__)
def _render_type(node: TypeNode | UnionNode, *, use_Union: bool = False) -> str:
"""Generated: validation needed.
Description:
Render a type node tree to a human-readable type hint string.
Args:
node (TypeNode | UnionNode): Root of the type node tree to render.
use_Union (bool): When True use ``Union[...]`` syntax; otherwise use ``|``.
Returns:
str: Rendered type hint string.
"""
if isinstance(node, UnionNode):
members = sorted((_render_type(m, use_Union=use_Union) for m in node.members))
if use_Union:
return_string = f"Union[{', '.join(members)}]"
else:
return_string = " | ".join(members)
elif not node.args:
return_string = node.name
else:
return_string = (
f"{node.name}"
f"[{', '.join(_render_type(a, use_Union=use_Union) for a in node.args)}]"
)
return return_string
[docs]
def parse_type_hint(_object: object, *, use_Union: bool = False) -> str:
"""Generated: validation needed.
Description:
Infer and render the type hint for a given object.
Args:
_object (object): Object to infer the type hint for.
use_Union (bool): Whether to use ``Union[...]`` syntax instead of ``|``.
Returns:
str: Inferred type hint as a string.
Example:
>>> parse_type_hint({"key": [1, 2]})
'Dict[str, List[int]]'
"""
inferred = _infer_type(_object)
return_string = _render_type(inferred, use_Union=use_Union)
print(return_string)
return return_string
if __name__ == "__main__":
data = [
{
"id": 1,
"scores": [1.2, 3.4],
},
{
"id": 2,
1: ("a", "b"),
},
]
parse_type_hint(data)
data_2 = {
"name": "example",
"values": {1, 2, 3.5, None},
"attributes": {"key1": True, "key2": 42},
10: (
1,
2,
{
1: "a",
2: "b",
"string_key": [1, 2, 3],
},
),
}
data_3 = {
"dict_key": {
"nested_list": [
{"inner_dict": {"a": 1, "b": 2}},
{"inner_dict": {"c": 3.5, "d": None}},
],
"nested_set": {frozenset({1, 2}), frozenset({3, 4})},
"inter_tuple": (1, 2, 3),
"int": 42,
},
}
parse_type_hint(data_2)
parse_type_hint(data_3)