xian-tech-runtime-types 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- xian_tech_runtime_types-0.1.0/PKG-INFO +12 -0
- xian_tech_runtime_types-0.1.0/README.md +3 -0
- xian_tech_runtime_types-0.1.0/pyproject.toml +18 -0
- xian_tech_runtime_types-0.1.0/setup.cfg +4 -0
- xian_tech_runtime_types-0.1.0/src/xian_runtime_types/__init__.py +71 -0
- xian_tech_runtime_types-0.1.0/src/xian_runtime_types/decimal.py +203 -0
- xian_tech_runtime_types-0.1.0/src/xian_runtime_types/encoding.py +163 -0
- xian_tech_runtime_types-0.1.0/src/xian_runtime_types/time.py +251 -0
- xian_tech_runtime_types-0.1.0/src/xian_tech_runtime_types.egg-info/PKG-INFO +12 -0
- xian_tech_runtime_types-0.1.0/src/xian_tech_runtime_types.egg-info/SOURCES.txt +10 -0
- xian_tech_runtime_types-0.1.0/src/xian_tech_runtime_types.egg-info/dependency_links.txt +1 -0
- xian_tech_runtime_types-0.1.0/src/xian_tech_runtime_types.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: xian-tech-runtime-types
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Shared deterministic runtime types for the Xian stack
|
|
5
|
+
Author-email: Xian Network <info@xian.org>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Requires-Python: >=3.11
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
|
|
10
|
+
# xian-tech-runtime-types
|
|
11
|
+
|
|
12
|
+
Shared deterministic runtime types for the Xian stack.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "xian-tech-runtime-types"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Shared deterministic runtime types for the Xian stack"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
authors = [{ name = "Xian Network", email = "info@xian.org" }]
|
|
12
|
+
requires-python = ">=3.11"
|
|
13
|
+
|
|
14
|
+
[tool.setuptools]
|
|
15
|
+
package-dir = {"" = "src"}
|
|
16
|
+
|
|
17
|
+
[tool.setuptools.packages.find]
|
|
18
|
+
where = ["src"]
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
from xian_runtime_types.decimal import (
|
|
2
|
+
CONTEXT,
|
|
3
|
+
MAX_DECIMAL,
|
|
4
|
+
MAX_LOWER_PRECISION,
|
|
5
|
+
MAX_UPPER_PRECISION,
|
|
6
|
+
MIN_DECIMAL,
|
|
7
|
+
ContractingDecimal,
|
|
8
|
+
DecimalOverflowError,
|
|
9
|
+
exports,
|
|
10
|
+
fix_precision,
|
|
11
|
+
neg_sci_not,
|
|
12
|
+
)
|
|
13
|
+
from xian_runtime_types.encoding import (
|
|
14
|
+
Encoder,
|
|
15
|
+
as_object,
|
|
16
|
+
convert,
|
|
17
|
+
convert_dict,
|
|
18
|
+
decode,
|
|
19
|
+
decode_kv,
|
|
20
|
+
encode,
|
|
21
|
+
encode_int,
|
|
22
|
+
encode_ints_in_dict,
|
|
23
|
+
encode_kv,
|
|
24
|
+
safe_repr,
|
|
25
|
+
)
|
|
26
|
+
from xian_runtime_types.time import (
|
|
27
|
+
DAYS,
|
|
28
|
+
HOURS,
|
|
29
|
+
MINUTES,
|
|
30
|
+
SECONDS,
|
|
31
|
+
WEEKS,
|
|
32
|
+
Datetime,
|
|
33
|
+
Timedelta,
|
|
34
|
+
datetime_module,
|
|
35
|
+
get_raw_seconds,
|
|
36
|
+
to_contract_time,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
__all__ = [
|
|
40
|
+
"CONTEXT",
|
|
41
|
+
"MAX_DECIMAL",
|
|
42
|
+
"MAX_LOWER_PRECISION",
|
|
43
|
+
"MAX_UPPER_PRECISION",
|
|
44
|
+
"MIN_DECIMAL",
|
|
45
|
+
"ContractingDecimal",
|
|
46
|
+
"DecimalOverflowError",
|
|
47
|
+
"exports",
|
|
48
|
+
"fix_precision",
|
|
49
|
+
"neg_sci_not",
|
|
50
|
+
"Encoder",
|
|
51
|
+
"as_object",
|
|
52
|
+
"convert",
|
|
53
|
+
"convert_dict",
|
|
54
|
+
"decode",
|
|
55
|
+
"decode_kv",
|
|
56
|
+
"encode",
|
|
57
|
+
"encode_int",
|
|
58
|
+
"encode_ints_in_dict",
|
|
59
|
+
"encode_kv",
|
|
60
|
+
"safe_repr",
|
|
61
|
+
"DAYS",
|
|
62
|
+
"HOURS",
|
|
63
|
+
"MINUTES",
|
|
64
|
+
"SECONDS",
|
|
65
|
+
"WEEKS",
|
|
66
|
+
"Datetime",
|
|
67
|
+
"Timedelta",
|
|
68
|
+
"datetime_module",
|
|
69
|
+
"get_raw_seconds",
|
|
70
|
+
"to_contract_time",
|
|
71
|
+
]
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import decimal
|
|
2
|
+
from decimal import ROUND_DOWN, Context, Decimal, InvalidOperation
|
|
3
|
+
|
|
4
|
+
MAX_UPPER_PRECISION = 61
|
|
5
|
+
MAX_LOWER_PRECISION = 30
|
|
6
|
+
|
|
7
|
+
CONTEXT = Context(
|
|
8
|
+
prec=MAX_UPPER_PRECISION + MAX_LOWER_PRECISION,
|
|
9
|
+
rounding=ROUND_DOWN,
|
|
10
|
+
Emin=-100,
|
|
11
|
+
Emax=100,
|
|
12
|
+
)
|
|
13
|
+
decimal.setcontext(CONTEXT)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def make_min_decimal_str(prec):
|
|
17
|
+
return "0." + "0" * (prec - 1) + "1"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def make_max_decimal_str(upper_prec, lower_prec=0):
|
|
21
|
+
whole = "9" * upper_prec
|
|
22
|
+
if lower_prec <= 0:
|
|
23
|
+
return whole
|
|
24
|
+
return f"{whole}.{'9' * lower_prec}"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def neg_sci_not(s: str):
|
|
28
|
+
try:
|
|
29
|
+
base, exp = s.split("e-")
|
|
30
|
+
if float(base) > 9:
|
|
31
|
+
return s
|
|
32
|
+
|
|
33
|
+
base = base.replace(".", "")
|
|
34
|
+
numbers = ("0" * (int(exp) - 1)) + base
|
|
35
|
+
|
|
36
|
+
if int(exp) > 0:
|
|
37
|
+
numbers = "0." + numbers
|
|
38
|
+
|
|
39
|
+
return numbers
|
|
40
|
+
except ValueError:
|
|
41
|
+
return s
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
MAX_DECIMAL = Decimal(
|
|
45
|
+
make_max_decimal_str(MAX_UPPER_PRECISION, MAX_LOWER_PRECISION)
|
|
46
|
+
)
|
|
47
|
+
MIN_DECIMAL = Decimal(make_min_decimal_str(MAX_LOWER_PRECISION))
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class DecimalOverflowError(OverflowError):
|
|
51
|
+
pass
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def fix_precision(x: Decimal):
|
|
55
|
+
try:
|
|
56
|
+
quantized = x.quantize(MIN_DECIMAL, rounding=ROUND_DOWN).normalize()
|
|
57
|
+
except InvalidOperation as exc:
|
|
58
|
+
raise DecimalOverflowError(
|
|
59
|
+
f"Value {x} exceeds the supported decimal range."
|
|
60
|
+
) from exc
|
|
61
|
+
if quantized == 0:
|
|
62
|
+
return Decimal("0")
|
|
63
|
+
if quantized > MAX_DECIMAL or quantized < -MAX_DECIMAL:
|
|
64
|
+
raise DecimalOverflowError(
|
|
65
|
+
f"Value {x} exceeds the supported decimal range."
|
|
66
|
+
)
|
|
67
|
+
return quantized
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class ContractingDecimal:
|
|
71
|
+
def _get_other(self, other):
|
|
72
|
+
if isinstance(other, ContractingDecimal):
|
|
73
|
+
return other._d
|
|
74
|
+
elif isinstance(other, (float, int)):
|
|
75
|
+
return fix_precision(Decimal(neg_sci_not(str(other))))
|
|
76
|
+
return other
|
|
77
|
+
|
|
78
|
+
def __init__(self, a):
|
|
79
|
+
if isinstance(a, (float, int)):
|
|
80
|
+
self._d = Decimal(neg_sci_not(str(a)))
|
|
81
|
+
elif isinstance(a, str):
|
|
82
|
+
self._d = Decimal(neg_sci_not(a))
|
|
83
|
+
elif isinstance(a, Decimal):
|
|
84
|
+
self._d = a
|
|
85
|
+
else:
|
|
86
|
+
self._d = Decimal(a)
|
|
87
|
+
|
|
88
|
+
self._d = fix_precision(self._d)
|
|
89
|
+
|
|
90
|
+
def __bool__(self):
|
|
91
|
+
return self._d != 0
|
|
92
|
+
|
|
93
|
+
def __eq__(self, other):
|
|
94
|
+
return self._d == self._get_other(other)
|
|
95
|
+
|
|
96
|
+
def __lt__(self, other):
|
|
97
|
+
return self._d < self._get_other(other)
|
|
98
|
+
|
|
99
|
+
def __le__(self, other):
|
|
100
|
+
return self._d <= self._get_other(other)
|
|
101
|
+
|
|
102
|
+
def __gt__(self, other):
|
|
103
|
+
return self._d > self._get_other(other)
|
|
104
|
+
|
|
105
|
+
def __ge__(self, other):
|
|
106
|
+
return self._d >= self._get_other(other)
|
|
107
|
+
|
|
108
|
+
def __str__(self):
|
|
109
|
+
return self._d.to_eng_string()
|
|
110
|
+
|
|
111
|
+
def __repr__(self):
|
|
112
|
+
return self._d.to_eng_string()
|
|
113
|
+
|
|
114
|
+
def __neg__(self):
|
|
115
|
+
return ContractingDecimal(-self._d)
|
|
116
|
+
|
|
117
|
+
def __pos__(self):
|
|
118
|
+
return self
|
|
119
|
+
|
|
120
|
+
def __abs__(self):
|
|
121
|
+
return ContractingDecimal(abs(self._d))
|
|
122
|
+
|
|
123
|
+
def __add__(self, other):
|
|
124
|
+
return ContractingDecimal(
|
|
125
|
+
fix_precision(self._d + self._get_other(other))
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
def __radd__(self, other):
|
|
129
|
+
return ContractingDecimal(
|
|
130
|
+
fix_precision(self._get_other(other) + self._d)
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
def __sub__(self, other):
|
|
134
|
+
return ContractingDecimal(
|
|
135
|
+
fix_precision(self._d - self._get_other(other))
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
def __rsub__(self, other):
|
|
139
|
+
return ContractingDecimal(
|
|
140
|
+
fix_precision(self._get_other(other) - self._d)
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
def __mul__(self, other):
|
|
144
|
+
return ContractingDecimal(
|
|
145
|
+
fix_precision(self._d * self._get_other(other))
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
def __rmul__(self, other):
|
|
149
|
+
return ContractingDecimal(
|
|
150
|
+
fix_precision(self._get_other(other) * self._d)
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
def __truediv__(self, other):
|
|
154
|
+
return ContractingDecimal(
|
|
155
|
+
fix_precision(self._d / self._get_other(other))
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
def __rtruediv__(self, other):
|
|
159
|
+
return ContractingDecimal(
|
|
160
|
+
fix_precision(self._get_other(other) / self._d)
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
def __mod__(self, other):
|
|
164
|
+
return ContractingDecimal(
|
|
165
|
+
fix_precision(self._d % self._get_other(other))
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
def __rmod__(self, other):
|
|
169
|
+
return ContractingDecimal(
|
|
170
|
+
fix_precision(self._get_other(other) % self._d)
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
def __floordiv__(self, other):
|
|
174
|
+
return ContractingDecimal(
|
|
175
|
+
fix_precision(self._d // self._get_other(other))
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
def __rfloordiv__(self, other):
|
|
179
|
+
return ContractingDecimal(
|
|
180
|
+
fix_precision(self._get_other(other) // self._d)
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
def __pow__(self, other):
|
|
184
|
+
return ContractingDecimal(
|
|
185
|
+
fix_precision(self._d ** self._get_other(other))
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
def __rpow__(self, other):
|
|
189
|
+
return ContractingDecimal(
|
|
190
|
+
fix_precision(self._get_other(other) ** self._d)
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
def __int__(self):
|
|
194
|
+
return int(self._d)
|
|
195
|
+
|
|
196
|
+
def __float__(self):
|
|
197
|
+
return float(self._d)
|
|
198
|
+
|
|
199
|
+
def __round__(self, n=None):
|
|
200
|
+
return round(self._d, n)
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
exports = {"decimal": ContractingDecimal}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import decimal
|
|
2
|
+
import json
|
|
3
|
+
|
|
4
|
+
from xian_runtime_types.decimal import ContractingDecimal, fix_precision
|
|
5
|
+
from xian_runtime_types.time import Datetime, Timedelta
|
|
6
|
+
|
|
7
|
+
MIN_INT = -(2**63)
|
|
8
|
+
MAX_INT = 2**63 - 1
|
|
9
|
+
TYPES = {"__fixed__", "__delta__", "__bytes__", "__time__", "__big_int__"}
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def safe_repr(obj, max_len=1024):
|
|
13
|
+
try:
|
|
14
|
+
raw = obj.__repr__()
|
|
15
|
+
parts = raw.split(" at 0x")
|
|
16
|
+
if len(parts) > 1:
|
|
17
|
+
return parts[0] + ">"
|
|
18
|
+
return parts[0][:max_len]
|
|
19
|
+
except Exception:
|
|
20
|
+
return None
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class Encoder(json.JSONEncoder):
|
|
24
|
+
def default(self, value, *args):
|
|
25
|
+
if (
|
|
26
|
+
isinstance(value, Datetime)
|
|
27
|
+
or value.__class__.__name__ == Datetime.__name__
|
|
28
|
+
):
|
|
29
|
+
return {
|
|
30
|
+
"__time__": [
|
|
31
|
+
value.year,
|
|
32
|
+
value.month,
|
|
33
|
+
value.day,
|
|
34
|
+
value.hour,
|
|
35
|
+
value.minute,
|
|
36
|
+
value.second,
|
|
37
|
+
value.microsecond,
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
if (
|
|
41
|
+
isinstance(value, Timedelta)
|
|
42
|
+
or value.__class__.__name__ == Timedelta.__name__
|
|
43
|
+
):
|
|
44
|
+
return {
|
|
45
|
+
"__delta__": [
|
|
46
|
+
value._timedelta.days,
|
|
47
|
+
value._timedelta.seconds,
|
|
48
|
+
]
|
|
49
|
+
}
|
|
50
|
+
if isinstance(value, bytes):
|
|
51
|
+
return {"__bytes__": value.hex()}
|
|
52
|
+
if (
|
|
53
|
+
isinstance(value, decimal.Decimal)
|
|
54
|
+
or value.__class__.__name__ == decimal.Decimal.__name__
|
|
55
|
+
):
|
|
56
|
+
return {"__fixed__": str(fix_precision(value))}
|
|
57
|
+
if (
|
|
58
|
+
isinstance(value, ContractingDecimal)
|
|
59
|
+
or value.__class__.__name__ == ContractingDecimal.__name__
|
|
60
|
+
):
|
|
61
|
+
return {"__fixed__": str(fix_precision(value._d))}
|
|
62
|
+
return super().default(value)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def encode_int(value: int):
|
|
66
|
+
if MIN_INT < value < MAX_INT:
|
|
67
|
+
return value
|
|
68
|
+
return {"__big_int__": str(value)}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def encode_ints_in_dict(data: dict):
|
|
72
|
+
encoded = {}
|
|
73
|
+
for key, value in data.items():
|
|
74
|
+
if isinstance(value, int):
|
|
75
|
+
encoded[key] = encode_int(value)
|
|
76
|
+
elif isinstance(value, dict):
|
|
77
|
+
encoded[key] = encode_ints_in_dict(value)
|
|
78
|
+
elif isinstance(value, list):
|
|
79
|
+
encoded[key] = []
|
|
80
|
+
for item in value:
|
|
81
|
+
if isinstance(item, dict):
|
|
82
|
+
encoded[key].append(encode_ints_in_dict(item))
|
|
83
|
+
elif isinstance(item, int):
|
|
84
|
+
encoded[key].append(encode_int(item))
|
|
85
|
+
else:
|
|
86
|
+
encoded[key].append(item)
|
|
87
|
+
else:
|
|
88
|
+
encoded[key] = value
|
|
89
|
+
return encoded
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def encode(data):
|
|
93
|
+
if isinstance(data, int):
|
|
94
|
+
data = encode_int(data)
|
|
95
|
+
elif isinstance(data, dict):
|
|
96
|
+
data = encode_ints_in_dict(data)
|
|
97
|
+
return json.dumps(data, cls=Encoder, separators=(",", ":"))
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def as_object(value):
|
|
101
|
+
if "__time__" in value:
|
|
102
|
+
return Datetime(*value["__time__"])
|
|
103
|
+
if "__delta__" in value:
|
|
104
|
+
return Timedelta(
|
|
105
|
+
days=value["__delta__"][0], seconds=value["__delta__"][1]
|
|
106
|
+
)
|
|
107
|
+
if "__bytes__" in value:
|
|
108
|
+
return bytes.fromhex(value["__bytes__"])
|
|
109
|
+
if "__fixed__" in value:
|
|
110
|
+
return ContractingDecimal(value["__fixed__"])
|
|
111
|
+
if "__big_int__" in value:
|
|
112
|
+
return int(value["__big_int__"])
|
|
113
|
+
return dict(value)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def decode(data):
|
|
117
|
+
if data is None:
|
|
118
|
+
return None
|
|
119
|
+
if isinstance(data, bytes):
|
|
120
|
+
data = data.decode()
|
|
121
|
+
try:
|
|
122
|
+
return json.loads(data, object_hook=as_object)
|
|
123
|
+
except json.decoder.JSONDecodeError:
|
|
124
|
+
return None
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def encode_kv(key, value):
|
|
128
|
+
return key.encode(), encode(value).encode()
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def decode_kv(key, value):
|
|
132
|
+
return key.decode(), decode(value)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def convert(key, value):
|
|
136
|
+
if key == "__fixed__":
|
|
137
|
+
return ContractingDecimal(value)
|
|
138
|
+
if key == "__delta__":
|
|
139
|
+
return Timedelta(days=value[0], seconds=value[1])
|
|
140
|
+
if key == "__bytes__":
|
|
141
|
+
return bytes.fromhex(value)
|
|
142
|
+
if key == "__time__":
|
|
143
|
+
return Datetime(*value)
|
|
144
|
+
if key == "__big_int__":
|
|
145
|
+
return int(value)
|
|
146
|
+
return value
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def convert_dict(data):
|
|
150
|
+
if not isinstance(data, dict):
|
|
151
|
+
return data
|
|
152
|
+
|
|
153
|
+
converted = {}
|
|
154
|
+
for key, value in data.items():
|
|
155
|
+
if key in TYPES:
|
|
156
|
+
return convert(key, value)
|
|
157
|
+
if isinstance(value, dict):
|
|
158
|
+
converted[key] = convert_dict(value)
|
|
159
|
+
elif isinstance(value, list):
|
|
160
|
+
converted[key] = [convert_dict(item) for item in value]
|
|
161
|
+
else:
|
|
162
|
+
converted[key] = value
|
|
163
|
+
return converted
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
from datetime import datetime as dt
|
|
2
|
+
from datetime import timedelta as td
|
|
3
|
+
from types import ModuleType
|
|
4
|
+
|
|
5
|
+
SECONDS_IN_MINUTE = 60
|
|
6
|
+
SECONDS_IN_HOUR = 3600
|
|
7
|
+
SECONDS_IN_DAY = 86400
|
|
8
|
+
SECONDS_IN_WEEK = 604800
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def get_raw_seconds(weeks, days, hours, minutes, seconds):
|
|
12
|
+
m_sec = minutes * SECONDS_IN_MINUTE
|
|
13
|
+
h_sec = hours * SECONDS_IN_HOUR
|
|
14
|
+
d_sec = days * SECONDS_IN_DAY
|
|
15
|
+
w_sec = weeks * SECONDS_IN_WEEK
|
|
16
|
+
return seconds + m_sec + h_sec + d_sec + w_sec
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def to_contract_time(py_dt):
|
|
20
|
+
if isinstance(py_dt, Datetime):
|
|
21
|
+
dt_obj = py_dt
|
|
22
|
+
elif isinstance(py_dt, dt):
|
|
23
|
+
dt_obj = py_dt
|
|
24
|
+
else:
|
|
25
|
+
raise TypeError("Expected datetime.datetime or Datetime")
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
"__time__": [
|
|
29
|
+
dt_obj.year,
|
|
30
|
+
dt_obj.month,
|
|
31
|
+
dt_obj.day,
|
|
32
|
+
dt_obj.hour,
|
|
33
|
+
dt_obj.minute,
|
|
34
|
+
dt_obj.second,
|
|
35
|
+
]
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class Datetime:
|
|
40
|
+
def __init__(
|
|
41
|
+
self, year, month, day, hour=0, minute=0, second=0, microsecond=0
|
|
42
|
+
):
|
|
43
|
+
self._datetime = dt(
|
|
44
|
+
year=year,
|
|
45
|
+
month=month,
|
|
46
|
+
day=day,
|
|
47
|
+
hour=hour,
|
|
48
|
+
minute=minute,
|
|
49
|
+
second=second,
|
|
50
|
+
microsecond=microsecond,
|
|
51
|
+
)
|
|
52
|
+
self.year = self._datetime.year
|
|
53
|
+
self.month = self._datetime.month
|
|
54
|
+
self.day = self._datetime.day
|
|
55
|
+
self.hour = self._datetime.hour
|
|
56
|
+
self.minute = self._datetime.minute
|
|
57
|
+
self.second = self._datetime.second
|
|
58
|
+
self.microsecond = self._datetime.microsecond
|
|
59
|
+
|
|
60
|
+
def __lt__(self, other):
|
|
61
|
+
if not isinstance(other, Datetime):
|
|
62
|
+
raise TypeError(f"{type(other)} is not a Datetime!")
|
|
63
|
+
return self._datetime < other._datetime
|
|
64
|
+
|
|
65
|
+
def __le__(self, other):
|
|
66
|
+
if not isinstance(other, Datetime):
|
|
67
|
+
raise TypeError(f"{type(other)} is not a Datetime!")
|
|
68
|
+
return self._datetime <= other._datetime
|
|
69
|
+
|
|
70
|
+
def __eq__(self, other):
|
|
71
|
+
if not isinstance(other, Datetime):
|
|
72
|
+
raise TypeError(f"{type(other)} is not a Datetime!")
|
|
73
|
+
return self._datetime == other._datetime
|
|
74
|
+
|
|
75
|
+
def __ge__(self, other):
|
|
76
|
+
if not isinstance(other, Datetime):
|
|
77
|
+
raise TypeError(f"{type(other)} is not a Datetime!")
|
|
78
|
+
return self._datetime >= other._datetime
|
|
79
|
+
|
|
80
|
+
def __gt__(self, other):
|
|
81
|
+
if not isinstance(other, Datetime):
|
|
82
|
+
raise TypeError(f"{type(other)} is not a Datetime!")
|
|
83
|
+
return self._datetime > other._datetime
|
|
84
|
+
|
|
85
|
+
def __ne__(self, other):
|
|
86
|
+
if not isinstance(other, Datetime):
|
|
87
|
+
raise TypeError(f"{type(other)} is not a Datetime!")
|
|
88
|
+
return self._datetime != other._datetime
|
|
89
|
+
|
|
90
|
+
def __sub__(self, other):
|
|
91
|
+
if isinstance(other, Datetime):
|
|
92
|
+
delta = self._datetime - other._datetime
|
|
93
|
+
return Timedelta(days=delta.days, seconds=delta.seconds)
|
|
94
|
+
return NotImplemented
|
|
95
|
+
|
|
96
|
+
def __add__(self, other):
|
|
97
|
+
if isinstance(other, Timedelta):
|
|
98
|
+
return Datetime._from_datetime(self._datetime + other._timedelta)
|
|
99
|
+
return NotImplemented
|
|
100
|
+
|
|
101
|
+
def __str__(self):
|
|
102
|
+
return str(self._datetime)
|
|
103
|
+
|
|
104
|
+
def __repr__(self):
|
|
105
|
+
return self.__str__()
|
|
106
|
+
|
|
107
|
+
@classmethod
|
|
108
|
+
def _from_datetime(cls, value: dt):
|
|
109
|
+
return cls(
|
|
110
|
+
year=value.year,
|
|
111
|
+
month=value.month,
|
|
112
|
+
day=value.day,
|
|
113
|
+
hour=value.hour,
|
|
114
|
+
minute=value.minute,
|
|
115
|
+
second=value.second,
|
|
116
|
+
microsecond=value.microsecond,
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
@classmethod
|
|
120
|
+
def strptime(cls, date_string, format):
|
|
121
|
+
return cls._from_datetime(dt.strptime(date_string, format))
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class Timedelta:
|
|
125
|
+
def __init__(self, weeks=0, days=0, hours=0, minutes=0, seconds=0):
|
|
126
|
+
self._timedelta = td(
|
|
127
|
+
weeks=int(weeks),
|
|
128
|
+
days=int(days),
|
|
129
|
+
hours=int(hours),
|
|
130
|
+
minutes=int(minutes),
|
|
131
|
+
seconds=int(seconds),
|
|
132
|
+
)
|
|
133
|
+
self.__raw_seconds = get_raw_seconds(
|
|
134
|
+
weeks=int(weeks),
|
|
135
|
+
days=int(days),
|
|
136
|
+
hours=int(hours),
|
|
137
|
+
minutes=int(minutes),
|
|
138
|
+
seconds=int(seconds),
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
def __lt__(self, other):
|
|
142
|
+
if not isinstance(other, Timedelta):
|
|
143
|
+
raise TypeError(f"{type(other)} is not a Timedelta!")
|
|
144
|
+
return self._timedelta < other._timedelta
|
|
145
|
+
|
|
146
|
+
def __le__(self, other):
|
|
147
|
+
if not isinstance(other, Timedelta):
|
|
148
|
+
raise TypeError(f"{type(other)} is not a Timedelta!")
|
|
149
|
+
return self._timedelta <= other._timedelta
|
|
150
|
+
|
|
151
|
+
def __eq__(self, other):
|
|
152
|
+
if not isinstance(other, Timedelta):
|
|
153
|
+
raise TypeError(f"{type(other)} is not a Timedelta!")
|
|
154
|
+
return self._timedelta == other._timedelta
|
|
155
|
+
|
|
156
|
+
def __ge__(self, other):
|
|
157
|
+
if not isinstance(other, Timedelta):
|
|
158
|
+
raise TypeError(f"{type(other)} is not a Timedelta!")
|
|
159
|
+
return self._timedelta >= other._timedelta
|
|
160
|
+
|
|
161
|
+
def __gt__(self, other):
|
|
162
|
+
if not isinstance(other, Timedelta):
|
|
163
|
+
raise TypeError(f"{type(other)} is not a Timedelta!")
|
|
164
|
+
return self._timedelta > other._timedelta
|
|
165
|
+
|
|
166
|
+
def __ne__(self, other):
|
|
167
|
+
if not isinstance(other, Timedelta):
|
|
168
|
+
raise TypeError(f"{type(other)} is not a Timedelta!")
|
|
169
|
+
return self._timedelta != other._timedelta
|
|
170
|
+
|
|
171
|
+
def __add__(self, other):
|
|
172
|
+
if isinstance(other, Timedelta):
|
|
173
|
+
return Timedelta(
|
|
174
|
+
days=self._timedelta.days + other._timedelta.days,
|
|
175
|
+
seconds=self._timedelta.seconds + other._timedelta.seconds,
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
if isinstance(other, Datetime):
|
|
179
|
+
return Datetime._from_datetime(other._datetime + self._timedelta)
|
|
180
|
+
|
|
181
|
+
return NotImplemented
|
|
182
|
+
|
|
183
|
+
def __sub__(self, other):
|
|
184
|
+
if isinstance(other, Timedelta):
|
|
185
|
+
return Timedelta(
|
|
186
|
+
days=self._timedelta.days - other._timedelta.days,
|
|
187
|
+
seconds=self._timedelta.seconds - other._timedelta.seconds,
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
if isinstance(other, Datetime):
|
|
191
|
+
return Datetime._from_datetime(other._datetime - self._timedelta)
|
|
192
|
+
|
|
193
|
+
return NotImplemented
|
|
194
|
+
|
|
195
|
+
def __mul__(self, other):
|
|
196
|
+
if isinstance(other, Timedelta):
|
|
197
|
+
return Timedelta(
|
|
198
|
+
days=self._timedelta.days * other._timedelta.days,
|
|
199
|
+
seconds=self._timedelta.seconds * other._timedelta.seconds,
|
|
200
|
+
)
|
|
201
|
+
if isinstance(other, int):
|
|
202
|
+
return Timedelta(
|
|
203
|
+
days=self._timedelta.days * other,
|
|
204
|
+
seconds=self._timedelta.seconds * other,
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
return NotImplemented
|
|
208
|
+
|
|
209
|
+
def __str__(self):
|
|
210
|
+
return str(self._timedelta)
|
|
211
|
+
|
|
212
|
+
def __repr__(self):
|
|
213
|
+
return self.__str__()
|
|
214
|
+
|
|
215
|
+
@property
|
|
216
|
+
def seconds(self):
|
|
217
|
+
return self.__raw_seconds
|
|
218
|
+
|
|
219
|
+
@property
|
|
220
|
+
def minutes(self):
|
|
221
|
+
return self.__raw_seconds // SECONDS_IN_MINUTE
|
|
222
|
+
|
|
223
|
+
@property
|
|
224
|
+
def hours(self):
|
|
225
|
+
return self.__raw_seconds // SECONDS_IN_HOUR
|
|
226
|
+
|
|
227
|
+
@property
|
|
228
|
+
def days(self):
|
|
229
|
+
return self.__raw_seconds // SECONDS_IN_DAY
|
|
230
|
+
|
|
231
|
+
@property
|
|
232
|
+
def weeks(self):
|
|
233
|
+
return self.__raw_seconds // SECONDS_IN_WEEK
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
WEEKS = Timedelta(weeks=1)
|
|
237
|
+
DAYS = Timedelta(days=1)
|
|
238
|
+
HOURS = Timedelta(hours=1)
|
|
239
|
+
MINUTES = Timedelta(minutes=1)
|
|
240
|
+
SECONDS = Timedelta(seconds=1)
|
|
241
|
+
|
|
242
|
+
datetime_module = ModuleType("datetime")
|
|
243
|
+
datetime_module.datetime = Datetime
|
|
244
|
+
datetime_module.timedelta = Timedelta
|
|
245
|
+
datetime_module.WEEKS = WEEKS
|
|
246
|
+
datetime_module.DAYS = DAYS
|
|
247
|
+
datetime_module.HOURS = HOURS
|
|
248
|
+
datetime_module.MINUTES = MINUTES
|
|
249
|
+
datetime_module.SECONDS = SECONDS
|
|
250
|
+
|
|
251
|
+
exports = {"datetime": datetime_module}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: xian-tech-runtime-types
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Shared deterministic runtime types for the Xian stack
|
|
5
|
+
Author-email: Xian Network <info@xian.org>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Requires-Python: >=3.11
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
|
|
10
|
+
# xian-tech-runtime-types
|
|
11
|
+
|
|
12
|
+
Shared deterministic runtime types for the Xian stack.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
src/xian_runtime_types/__init__.py
|
|
4
|
+
src/xian_runtime_types/decimal.py
|
|
5
|
+
src/xian_runtime_types/encoding.py
|
|
6
|
+
src/xian_runtime_types/time.py
|
|
7
|
+
src/xian_tech_runtime_types.egg-info/PKG-INFO
|
|
8
|
+
src/xian_tech_runtime_types.egg-info/SOURCES.txt
|
|
9
|
+
src/xian_tech_runtime_types.egg-info/dependency_links.txt
|
|
10
|
+
src/xian_tech_runtime_types.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
xian_runtime_types
|