Skip to content

Commit 68805a6

Browse files
feat(spider-py): Define TDL types; Add support for converting Python types into TDL types. (#187)
Co-authored-by: Lin Zhihao <[email protected]>
1 parent 1661410 commit 68805a6

File tree

8 files changed

+417
-0
lines changed

8 files changed

+417
-0
lines changed

python/spider-py/pyproject.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,13 +60,15 @@ ignore = [
6060
"FIX002", # Allow todo statements
6161
"PERF401", # Allow for loops when creating lists
6262
"PERF403", # Allow for loops when creating dicts
63+
"PLR1716", # Allow chained boolean comparisons
6364
"S311", # Allow usage of `random` package
6465
"SIM102", # Allow collapsible if statements for readability
6566
"TD002", # Author unnecessary for todo statement
6667
"TD003", # Issue link unnecessary for todo statement
6768
"UP015", # Explicit open modes are helpful
6869
]
6970
isort.order-by-type = false
71+
typing-extensions = false # Disable type extensions for 3.10 compatibility
7072

7173
[tool.ruff.lint.per-file-ignores]
7274
"tests/**" = [
@@ -75,3 +77,6 @@ isort.order-by-type = false
7577
"S603", # Allow use of `subprocess.Popen` (security warning)
7678
"T201", # Allow use of `print` (testing)
7779
]
80+
81+
[tool.ruff.lint.pydocstyle]
82+
ignore-decorators = ["typing.override"]
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,12 @@
11
"""Spider package root."""
2+
3+
from spider_py.type import Double, Float, Int8, Int16, Int32, Int64
4+
5+
__all__ = [
6+
"Double",
7+
"Float",
8+
"Int8",
9+
"Int16",
10+
"Int32",
11+
"Int64",
12+
]
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
"""Spider type package."""
2+
3+
from spider_py.type.tdl_convert import to_tdl_type_str
4+
from spider_py.type.type import Double, Float, Int8, Int16, Int32, Int64
5+
6+
__all__ = ["Double", "Float", "Int8", "Int16", "Int32", "Int64", "to_tdl_type_str"]
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
"""Converts native types to TDL types."""
2+
3+
from collections.abc import Collection
4+
from types import GenericAlias
5+
from typing import get_args, get_origin
6+
7+
from spider_py.type.tdl_type import (
8+
BoolType,
9+
ClassType,
10+
DoubleType,
11+
FloatType,
12+
Int8Type,
13+
Int16Type,
14+
Int32Type,
15+
Int64Type,
16+
ListType,
17+
MapType,
18+
TdlType,
19+
)
20+
from spider_py.type.type import Double, Float, Int8, Int16, Int32, Int64
21+
from spider_py.type.utils import get_class_name
22+
23+
TypeDict = {
24+
Int8: Int8Type(),
25+
Int16: Int16Type(),
26+
Int32: Int32Type(),
27+
Int64: Int64Type(),
28+
Float: FloatType(),
29+
Double: DoubleType(),
30+
bool: BoolType(),
31+
}
32+
33+
34+
def _to_primitive_tdl_type(native_type: type | GenericAlias) -> TdlType | None:
35+
"""
36+
Converts a native type to primitive TDL type.
37+
:param native_type:
38+
:return:
39+
- The converted TDL primitive if `native_type` is supported.
40+
- None if `native_type` is not a primitive Python type.
41+
:raises TypeError: If `native_type` is a primitive Python type not supported by TDL.
42+
"""
43+
if isinstance(native_type, type) and native_type in TypeDict:
44+
return TypeDict[native_type]
45+
46+
if native_type in (int, float, str, complex, bytes):
47+
msg = f"{native_type} is not a TDL type. Please use the corresponding TDL primitive type."
48+
raise TypeError(msg)
49+
50+
return None
51+
52+
53+
def to_tdl_type(native_type: type | GenericAlias) -> TdlType:
54+
"""
55+
Converts a Python type to TDL type.
56+
:param native_type:
57+
:return: The converted TDL type.
58+
:raises TypeError: If `native_type` is not a valid TDL type.
59+
"""
60+
primitive_tdl_type = _to_primitive_tdl_type(native_type)
61+
if primitive_tdl_type is not None:
62+
return primitive_tdl_type
63+
64+
if isinstance(native_type, GenericAlias):
65+
origin = get_origin(native_type)
66+
if origin is list:
67+
args = get_args(native_type)
68+
if len(args) == 0:
69+
msg = "List does not have an element type."
70+
raise TypeError(msg)
71+
arg = args[0]
72+
return ListType(to_tdl_type(arg))
73+
74+
if origin is dict:
75+
args = get_args(native_type)
76+
msg = "Dict does not have a key/value type."
77+
if len(args) != 2: # noqa: PLR2004
78+
raise TypeError(msg)
79+
key = args[0]
80+
value = args[1]
81+
return MapType(to_tdl_type(key), to_tdl_type(value))
82+
83+
msg = f"{native_type} is not a valid TDL type."
84+
raise TypeError(msg)
85+
86+
if issubclass(native_type, Collection):
87+
msg = f"{native_type} is not a valid TDL type."
88+
raise TypeError(msg)
89+
90+
return ClassType(get_class_name(native_type))
91+
92+
93+
def to_tdl_type_str(native_type: type | GenericAlias) -> str:
94+
"""
95+
:param native_type: A Python native type.
96+
:return: A string representation of the TDL type.
97+
:raises TypeError: If `native_type` is not a valid TDL type.
98+
"""
99+
return to_tdl_type(native_type).type_str()
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
"""Spider TDL types."""
2+
3+
from abc import ABC, abstractmethod
4+
5+
from typing_extensions import override
6+
7+
8+
class TdlType(ABC):
9+
"""Abstract base class for all TDL types."""
10+
11+
@abstractmethod
12+
def type_str(self) -> str:
13+
""":return: String representation of the TDL type."""
14+
15+
16+
class DoubleType(TdlType):
17+
"""TDL double type."""
18+
19+
@override
20+
def type_str(self) -> str:
21+
return "double"
22+
23+
24+
class FloatType(TdlType):
25+
"""TDL float type."""
26+
27+
@override
28+
def type_str(self) -> str:
29+
return "float"
30+
31+
32+
class Int8Type(TdlType):
33+
"""TDL int8 type."""
34+
35+
@override
36+
def type_str(self) -> str:
37+
return "int8"
38+
39+
40+
class Int16Type(TdlType):
41+
"""TDL int16 type."""
42+
43+
@override
44+
def type_str(self) -> str:
45+
return "int16"
46+
47+
48+
class Int32Type(TdlType):
49+
"""TDL int32 type."""
50+
51+
@override
52+
def type_str(self) -> str:
53+
return "int32"
54+
55+
56+
class Int64Type(TdlType):
57+
"""TDL int64 type."""
58+
59+
@override
60+
def type_str(self) -> str:
61+
return "int64"
62+
63+
64+
class BoolType(TdlType):
65+
"""TDL bool type."""
66+
67+
@override
68+
def type_str(self) -> str:
69+
return "bool"
70+
71+
72+
class ClassType(TdlType):
73+
"""TDL Custom class type."""
74+
75+
def __init__(self, name: str) -> None:
76+
"""
77+
Creates a TDL custom class type.
78+
:param name: The name of the class.
79+
"""
80+
self._name = name
81+
82+
@override
83+
def type_str(self) -> str:
84+
return self._name
85+
86+
87+
class ListType(TdlType):
88+
"""TDL List type."""
89+
90+
def __init__(self, element_type: TdlType) -> None:
91+
"""
92+
Creates a TDL list type.
93+
:param element_type:
94+
"""
95+
self.element_type = element_type
96+
97+
@override
98+
def type_str(self) -> str:
99+
return f"List<{self.element_type.type_str()}>"
100+
101+
102+
def is_integral(tdl_type: TdlType) -> bool:
103+
"""
104+
:param tdl_type:
105+
:return: Whether `tdl_type` is a TDL integral type.
106+
"""
107+
return isinstance(tdl_type, (Int8Type, Int16Type, Int32Type, Int64Type))
108+
109+
110+
def is_string(tdl_type: TdlType) -> bool:
111+
"""
112+
:param tdl_type:
113+
:return: Whether `tdl_type` is a TDL string type, i.e. `List<int8>`.
114+
"""
115+
return isinstance(tdl_type, ListType) and isinstance(tdl_type.element_type, Int8Type)
116+
117+
118+
def is_map_key(tdl_type: TdlType) -> bool:
119+
"""
120+
:param tdl_type:
121+
:return: Whether `tdl_type` is a supported key type of a map.
122+
"""
123+
return is_integral(tdl_type) or is_string(tdl_type)
124+
125+
126+
class MapType(TdlType):
127+
"""TDL Map type."""
128+
129+
def __init__(self, key_type: TdlType, value_type: TdlType) -> None:
130+
"""
131+
Creates a TDL map type.
132+
:param key_type:
133+
:param value_type:
134+
:raises TypeError: If key is not a supported type.
135+
"""
136+
if not is_map_key(key_type):
137+
msg = f"{key_type} is not a supported type for map key."
138+
raise TypeError(msg)
139+
self.key_type = key_type
140+
self.value_type = value_type
141+
142+
@override
143+
def type_str(self) -> str:
144+
return f"Map<{self.key_type.type_str()},{self.value_type.type_str()}>"
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
"""Custom type module for Spider."""
2+
3+
from __future__ import annotations
4+
5+
from typing import cast
6+
7+
8+
class BoundedInt(int):
9+
"""Bounded integer type."""
10+
11+
def __new__(cls, value: int, bits: int = 32) -> BoundedInt:
12+
"""Creates a bounded integer."""
13+
if bits not in (8, 16, 32, 64):
14+
msg = f"Unsupported bits size: {bits}. Supported sizes are 8, 16, 32, or 64."
15+
raise ValueError(msg)
16+
17+
lower_bound = -(1 << (bits - 1))
18+
upper_bound = (1 << (bits - 1)) - 1
19+
20+
if not (lower_bound <= value and value <= upper_bound):
21+
msg = (
22+
f"Bounded integer value ({value}) must be between {lower_bound} and {upper_bound}."
23+
)
24+
raise ValueError(msg)
25+
26+
return super().__new__(cls, value)
27+
28+
29+
class Int8(BoundedInt):
30+
"""8 bits integer type."""
31+
32+
def __new__(cls, value: int) -> Int8:
33+
"""Creates an int8 integer."""
34+
return cast("Int8", super().__new__(cls, value, bits=8))
35+
36+
37+
class Int16(BoundedInt):
38+
"""16 bits integer type."""
39+
40+
def __new__(cls, value: int) -> Int16:
41+
"""Creates an int16 integer."""
42+
return cast("Int16", super().__new__(cls, value, bits=16))
43+
44+
45+
class Int32(BoundedInt):
46+
"""32 bits integer type."""
47+
48+
def __new__(cls, value: int) -> Int32:
49+
"""Creates an int32 integer."""
50+
return cast("Int32", super().__new__(cls, value, bits=32))
51+
52+
53+
class Int64(BoundedInt):
54+
"""64 bits integer type."""
55+
56+
def __new__(cls, value: int) -> Int64:
57+
"""Creates an int64 integer."""
58+
return cast("Int64", super().__new__(cls, value, bits=64))
59+
60+
61+
class Float(float):
62+
"""Float type."""
63+
64+
def __new__(cls, value: float) -> Float:
65+
"""Creates a float number."""
66+
return super().__new__(cls, value)
67+
68+
69+
class Double(float):
70+
"""Double type."""
71+
72+
def __new__(cls, value: float) -> Double:
73+
"""Creates a double number."""
74+
return super().__new__(cls, value)
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
"""Utility for TDL types."""
2+
3+
4+
def get_class_name(cls: type) -> str:
5+
"""
6+
:param cls:
7+
:return: Full name of `cls`.
8+
"""
9+
return f"{cls.__module__}.{cls.__qualname__}"

0 commit comments

Comments
 (0)