soia-client 1.0.29__tar.gz → 1.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.
Potentially problematic release.
This version of soia-client might be problematic. Click here for more details.
- {soia_client-1.0.29 → soia_client-1.1.0}/PKG-INFO +1 -1
- {soia_client-1.0.29 → soia_client-1.1.0}/pyproject.toml +1 -1
- {soia_client-1.0.29 → soia_client-1.1.0}/soia/__init__.py +3 -0
- soia_client-1.1.0/soia/_impl/keep.py +20 -0
- {soia_client-1.0.29 → soia_client-1.1.0}/soia/_impl/primitives.py +8 -4
- {soia_client-1.0.29 → soia_client-1.1.0}/soia/_impl/structs.py +90 -21
- {soia_client-1.0.29 → soia_client-1.1.0}/soia/_impl/timestamp.py +41 -4
- {soia_client-1.0.29 → soia_client-1.1.0}/soia_client.egg-info/PKG-INFO +1 -1
- {soia_client-1.0.29 → soia_client-1.1.0}/soia_client.egg-info/SOURCES.txt +1 -0
- {soia_client-1.0.29 → soia_client-1.1.0}/tests/test_module_initializer.py +87 -41
- {soia_client-1.0.29 → soia_client-1.1.0}/tests/test_timestamp.py +20 -2
- {soia_client-1.0.29 → soia_client-1.1.0}/LICENSE +0 -0
- {soia_client-1.0.29 → soia_client-1.1.0}/README.md +0 -0
- {soia_client-1.0.29 → soia_client-1.1.0}/setup.cfg +0 -0
- {soia_client-1.0.29 → soia_client-1.1.0}/soia/_impl/__init__.py +0 -0
- {soia_client-1.0.29 → soia_client-1.1.0}/soia/_impl/arrays.py +0 -0
- {soia_client-1.0.29 → soia_client-1.1.0}/soia/_impl/enums.py +0 -0
- {soia_client-1.0.29 → soia_client-1.1.0}/soia/_impl/function_maker.py +0 -0
- {soia_client-1.0.29 → soia_client-1.1.0}/soia/_impl/keyed_items.py +0 -0
- {soia_client-1.0.29 → soia_client-1.1.0}/soia/_impl/method.py +0 -0
- {soia_client-1.0.29 → soia_client-1.1.0}/soia/_impl/never.py +0 -0
- {soia_client-1.0.29 → soia_client-1.1.0}/soia/_impl/optionals.py +0 -0
- {soia_client-1.0.29 → soia_client-1.1.0}/soia/_impl/repr.py +0 -0
- {soia_client-1.0.29 → soia_client-1.1.0}/soia/_impl/serializer.py +0 -0
- {soia_client-1.0.29 → soia_client-1.1.0}/soia/_impl/serializers.py +0 -0
- {soia_client-1.0.29 → soia_client-1.1.0}/soia/_impl/service.py +0 -0
- {soia_client-1.0.29 → soia_client-1.1.0}/soia/_impl/service_client.py +0 -0
- {soia_client-1.0.29 → soia_client-1.1.0}/soia/_impl/type_adapter.py +0 -0
- {soia_client-1.0.29 → soia_client-1.1.0}/soia/_module_initializer.py +0 -0
- {soia_client-1.0.29 → soia_client-1.1.0}/soia/_spec.py +0 -0
- {soia_client-1.0.29 → soia_client-1.1.0}/soia/reflection.py +0 -0
- {soia_client-1.0.29 → soia_client-1.1.0}/soia_client.egg-info/dependency_links.txt +0 -0
- {soia_client-1.0.29 → soia_client-1.1.0}/soia_client.egg-info/top_level.txt +0 -0
- {soia_client-1.0.29 → soia_client-1.1.0}/tests/test_serializers.py +0 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import typing as _typing
|
|
2
2
|
|
|
3
|
+
from soia._impl.keep import KEEP, Keep
|
|
3
4
|
from soia._impl.keyed_items import KeyedItems
|
|
4
5
|
from soia._impl.method import Method
|
|
5
6
|
from soia._impl.serializer import Serializer
|
|
@@ -16,6 +17,8 @@ _: _typing.Final[_typing.Any] = None
|
|
|
16
17
|
|
|
17
18
|
__all__ = [
|
|
18
19
|
"_",
|
|
20
|
+
"Keep",
|
|
21
|
+
"KEEP",
|
|
19
22
|
"KeyedItems",
|
|
20
23
|
"Method",
|
|
21
24
|
"RawServiceResponse",
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Final, cast, final
|
|
3
|
+
|
|
4
|
+
from soia._impl.never import Never
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@final
|
|
8
|
+
@dataclass(frozen=True)
|
|
9
|
+
class Keep:
|
|
10
|
+
"""
|
|
11
|
+
Type of the KEEP constant, which indicates that a value should not be replaced.
|
|
12
|
+
|
|
13
|
+
Do not instantiate.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, never: Never):
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
KEEP: Final = Keep(cast(Never, None))
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import base64
|
|
1
2
|
from collections.abc import Callable
|
|
2
3
|
from dataclasses import dataclass
|
|
3
4
|
from typing import Any, Final, final
|
|
@@ -280,8 +281,6 @@ STRING_ADAPTER: Final[TypeAdapter] = _StringAdapter()
|
|
|
280
281
|
|
|
281
282
|
|
|
282
283
|
class _BytesAdapter(AbstractPrimitiveAdapter):
|
|
283
|
-
_fromhex_fn: Final = bytes.fromhex
|
|
284
|
-
|
|
285
284
|
def default_expr(self) -> ExprLike:
|
|
286
285
|
return 'b""'
|
|
287
286
|
|
|
@@ -296,11 +295,16 @@ class _BytesAdapter(AbstractPrimitiveAdapter):
|
|
|
296
295
|
in_expr: ExprLike,
|
|
297
296
|
readable: bool,
|
|
298
297
|
) -> Expr:
|
|
299
|
-
return Expr.join(
|
|
298
|
+
return Expr.join(
|
|
299
|
+
Expr.local("b64encode", base64.b64encode),
|
|
300
|
+
"(",
|
|
301
|
+
in_expr,
|
|
302
|
+
").decode('utf-8')",
|
|
303
|
+
)
|
|
300
304
|
|
|
301
305
|
def from_json_expr(self, json_expr: ExprLike) -> Expr:
|
|
302
306
|
return Expr.join(
|
|
303
|
-
Expr.local("
|
|
307
|
+
Expr.local("b64decode", base64.b64decode), "(", json_expr, ' or "")'
|
|
304
308
|
)
|
|
305
309
|
|
|
306
310
|
def get_type(self) -> reflection.Type:
|
|
@@ -14,6 +14,7 @@ from soia._impl.function_maker import (
|
|
|
14
14
|
Params,
|
|
15
15
|
make_function,
|
|
16
16
|
)
|
|
17
|
+
from soia._impl.keep import KEEP
|
|
17
18
|
from soia._impl.repr import repr_impl
|
|
18
19
|
from soia._impl.type_adapter import TypeAdapter
|
|
19
20
|
|
|
@@ -134,8 +135,12 @@ class StructAdapter(TypeAdapter):
|
|
|
134
135
|
simple_class=simple_class,
|
|
135
136
|
),
|
|
136
137
|
)
|
|
137
|
-
mutable_class.__init__ =
|
|
138
|
-
frozen_class.
|
|
138
|
+
mutable_class.__init__ = _make_mutable_class_init_fn(fields)
|
|
139
|
+
frozen_class.partial = _make_partial_static_factory_method(
|
|
140
|
+
fields,
|
|
141
|
+
frozen_class,
|
|
142
|
+
)
|
|
143
|
+
frozen_class.replace = _make_replace_method(fields, frozen_class)
|
|
139
144
|
|
|
140
145
|
frozen_class.__eq__ = _make_eq_fn(fields)
|
|
141
146
|
frozen_class.__hash__ = cast(Any, _make_hash_fn(fields, self.record_hash))
|
|
@@ -270,13 +275,7 @@ def _make_frozen_class_init_fn(
|
|
|
270
275
|
params: Params = ["_self"]
|
|
271
276
|
if fields:
|
|
272
277
|
params.append("*")
|
|
273
|
-
for field in fields
|
|
274
|
-
params.append(
|
|
275
|
-
Param(
|
|
276
|
-
name=field.field.attribute,
|
|
277
|
-
default=field.type.default_expr(),
|
|
278
|
-
)
|
|
279
|
-
)
|
|
278
|
+
params.extend(field.field.attribute for field in fields)
|
|
280
279
|
|
|
281
280
|
builder = BodyBuilder()
|
|
282
281
|
# Since __setattr__() was overridden to raise errors in order to make the class
|
|
@@ -384,10 +383,85 @@ def _make_mutable_class_init_fn(fields: Sequence[_Field]) -> Callable[..., None]
|
|
|
384
383
|
)
|
|
385
384
|
|
|
386
385
|
|
|
387
|
-
def
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
386
|
+
def _make_partial_static_factory_method(
|
|
387
|
+
fields: Sequence[_Field],
|
|
388
|
+
frozen_class: type,
|
|
389
|
+
) -> Callable[..., None]:
|
|
390
|
+
"""
|
|
391
|
+
Returns the implementation of the partial() method of the frozen class.
|
|
392
|
+
"""
|
|
393
|
+
|
|
394
|
+
params: Params = []
|
|
395
|
+
if fields:
|
|
396
|
+
params.append("*")
|
|
397
|
+
params.extend(
|
|
398
|
+
Param(
|
|
399
|
+
name=field.field.attribute,
|
|
400
|
+
default=field.type.default_expr(),
|
|
401
|
+
)
|
|
402
|
+
for field in fields
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
builder = BodyBuilder()
|
|
406
|
+
builder.append_ln(
|
|
407
|
+
"return ",
|
|
408
|
+
Expr.local("Frozen", frozen_class),
|
|
409
|
+
"(",
|
|
410
|
+
", ".join(
|
|
411
|
+
f"{field.field.attribute}={field.field.attribute}" for field in fields
|
|
412
|
+
),
|
|
413
|
+
")",
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
return make_function(
|
|
417
|
+
name="partial",
|
|
418
|
+
params=params,
|
|
419
|
+
body=builder.build(),
|
|
420
|
+
)
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
def _make_replace_method(
|
|
424
|
+
fields: Sequence[_Field],
|
|
425
|
+
frozen_class: type,
|
|
426
|
+
) -> Callable[..., None]:
|
|
427
|
+
"""
|
|
428
|
+
Returns the implementation of the replace() method of the frozen class.
|
|
429
|
+
"""
|
|
430
|
+
|
|
431
|
+
keep_local = Expr.local("KEEP", KEEP)
|
|
432
|
+
params: Params = []
|
|
433
|
+
if fields:
|
|
434
|
+
params.append("*")
|
|
435
|
+
params.extend(
|
|
436
|
+
Param(
|
|
437
|
+
name=field.field.attribute,
|
|
438
|
+
default=keep_local,
|
|
439
|
+
)
|
|
440
|
+
for field in fields
|
|
441
|
+
)
|
|
442
|
+
|
|
443
|
+
def field_to_arg_assigment(attr: str) -> LineSpan:
|
|
444
|
+
return LineSpan.join(
|
|
445
|
+
f"{attr}=self.{attr} if {attr} is ", keep_local, " else {attr}"
|
|
446
|
+
)
|
|
447
|
+
|
|
448
|
+
builder = BodyBuilder()
|
|
449
|
+
builder.append_ln(
|
|
450
|
+
"return ",
|
|
451
|
+
Expr.local("Frozen", frozen_class),
|
|
452
|
+
"(",
|
|
453
|
+
LineSpan.join(
|
|
454
|
+
*(field_to_arg_assigment(field.field.attribute) for field in fields),
|
|
455
|
+
separator=", ",
|
|
456
|
+
),
|
|
457
|
+
")",
|
|
458
|
+
)
|
|
459
|
+
|
|
460
|
+
return make_function(
|
|
461
|
+
name="partial",
|
|
462
|
+
params=params,
|
|
463
|
+
body=builder.build(),
|
|
464
|
+
)
|
|
391
465
|
|
|
392
466
|
|
|
393
467
|
def _make_to_mutable_fn(
|
|
@@ -549,14 +623,9 @@ def _make_repr_fn(fields: Sequence[_Field]) -> Callable[[Any], str]:
|
|
|
549
623
|
for field in fields:
|
|
550
624
|
attribute = field.field.attribute
|
|
551
625
|
# is_not_default_expr only works on a frozen expression.
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
)
|
|
556
|
-
builder.append_ln("if is_mutable or ", is_not_default_expr, ":")
|
|
557
|
-
builder.append_ln(" r = ", repr_local, f"(self.{attribute})")
|
|
558
|
-
builder.append_ln(f" assignments.append(f'{attribute}={{r.indented}}')")
|
|
559
|
-
builder.append_ln(" any_complex = any_complex or r.complex")
|
|
626
|
+
builder.append_ln("r = ", repr_local, f"(self.{attribute})")
|
|
627
|
+
builder.append_ln(f"assignments.append(f'{attribute}={{r.indented}}')")
|
|
628
|
+
builder.append_ln("any_complex = any_complex or r.complex")
|
|
560
629
|
builder.append_ln("if len(assignments) <= 1 and not any_complex:")
|
|
561
630
|
builder.append_ln(" body = ''.join(assignments)")
|
|
562
631
|
builder.append_ln("else:")
|
|
@@ -5,6 +5,14 @@ from typing import Any, Final, Union, cast, final, overload
|
|
|
5
5
|
|
|
6
6
|
@final
|
|
7
7
|
class Timestamp:
|
|
8
|
+
"""
|
|
9
|
+
A number of milliseconds since the Unix epoch (1970-01-01T00:00:00Z).
|
|
10
|
+
|
|
11
|
+
Does not contain any timezone information.
|
|
12
|
+
Convertible to and from datetime objects.
|
|
13
|
+
Immutable.
|
|
14
|
+
"""
|
|
15
|
+
|
|
8
16
|
__slots__ = ("unix_millis",)
|
|
9
17
|
|
|
10
18
|
unix_millis: int
|
|
@@ -26,7 +34,14 @@ class Timestamp:
|
|
|
26
34
|
|
|
27
35
|
@staticmethod
|
|
28
36
|
def from_datetime(dt: datetime.datetime) -> "Timestamp":
|
|
29
|
-
|
|
37
|
+
# dt.timestamp() mail fail if the year is not in [1970, 2038]
|
|
38
|
+
if dt.tzinfo is None:
|
|
39
|
+
timestamp = (
|
|
40
|
+
dt - _EPOCH_DT.astimezone().replace(tzinfo=None)
|
|
41
|
+
).total_seconds()
|
|
42
|
+
else:
|
|
43
|
+
timestamp = (dt - _EPOCH_DT).total_seconds()
|
|
44
|
+
return Timestamp.from_unix_seconds(timestamp)
|
|
30
45
|
|
|
31
46
|
@staticmethod
|
|
32
47
|
def now() -> "Timestamp":
|
|
@@ -41,9 +56,26 @@ class Timestamp:
|
|
|
41
56
|
return self.unix_millis / 1000.0
|
|
42
57
|
|
|
43
58
|
def to_datetime_or_raise(self) -> datetime.datetime:
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
59
|
+
"""
|
|
60
|
+
Returns a datetime object representing the timestamp in UTC timezone.
|
|
61
|
+
|
|
62
|
+
Raises an exception if the timestamp is out of bounds for datetime.
|
|
63
|
+
If you don't want the exception, use 'to_datetime_or_limit()' instead.
|
|
64
|
+
"""
|
|
65
|
+
return _EPOCH_DT + datetime.timedelta(seconds=self.unix_seconds)
|
|
66
|
+
|
|
67
|
+
def to_datetime_or_limit(self) -> datetime.datetime:
|
|
68
|
+
"""
|
|
69
|
+
Returns a datetime object representing the timestamp in UTC timezone.
|
|
70
|
+
|
|
71
|
+
Clamps the timestamp to the minimum or maximum datetime if it is out of bounds.
|
|
72
|
+
"""
|
|
73
|
+
if self.unix_seconds <= (_MIN_DT_UTC - _EPOCH_DT).total_seconds():
|
|
74
|
+
return datetime.datetime.min.replace(tzinfo=datetime.timezone.utc)
|
|
75
|
+
elif self.unix_seconds >= (_MAX_DT_UTC - _EPOCH_DT).total_seconds():
|
|
76
|
+
return datetime.datetime.max.replace(tzinfo=datetime.timezone.utc)
|
|
77
|
+
else:
|
|
78
|
+
return self.to_datetime_or_raise()
|
|
47
79
|
|
|
48
80
|
def __add__(self, td: datetime.timedelta) -> "Timestamp":
|
|
49
81
|
return Timestamp(
|
|
@@ -126,3 +158,8 @@ class Timestamp:
|
|
|
126
158
|
setattr(Timestamp, "EPOCH", Timestamp.from_unix_millis(0))
|
|
127
159
|
setattr(Timestamp, "MIN", Timestamp.from_unix_millis(-8640000000000000))
|
|
128
160
|
setattr(Timestamp, "MAX", Timestamp.from_unix_millis(8640000000000000))
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
_EPOCH_DT: Final = datetime.datetime(1970, 1, 1, tzinfo=datetime.timezone.utc)
|
|
164
|
+
_MIN_DT_UTC: Final = datetime.datetime.min.replace(tzinfo=datetime.timezone.utc)
|
|
165
|
+
_MAX_DT_UTC: Final = datetime.datetime.max.replace(tzinfo=datetime.timezone.utc)
|
|
@@ -363,11 +363,11 @@ class ModuleInitializerTestCase(unittest.TestCase):
|
|
|
363
363
|
self.assertEqual(point.x, 1.5)
|
|
364
364
|
self.assertEqual(point.y, 2.5)
|
|
365
365
|
|
|
366
|
-
def
|
|
366
|
+
def test_partial_static_factory_method(self):
|
|
367
367
|
point_cls = self.init_test_module()["Point"]
|
|
368
|
-
point = point_cls.
|
|
368
|
+
point = point_cls.partial(x=1.5)
|
|
369
369
|
self.assertEqual(point.x, 1.5)
|
|
370
|
-
self.assertEqual(point.y,
|
|
370
|
+
self.assertEqual(point.y, 0.0)
|
|
371
371
|
|
|
372
372
|
def test_to_mutable(self):
|
|
373
373
|
point_cls = self.init_test_module()["Point"]
|
|
@@ -388,7 +388,8 @@ class ModuleInitializerTestCase(unittest.TestCase):
|
|
|
388
388
|
self.assertEqual(hash(a), hash(b))
|
|
389
389
|
self.assertNotEqual(a, c)
|
|
390
390
|
self.assertNotEqual(a, "foo")
|
|
391
|
-
self.assertEqual(point_cls(), point_cls(x=0.0, y=0.0))
|
|
391
|
+
self.assertEqual(point_cls.partial(), point_cls(x=0.0, y=0.0))
|
|
392
|
+
self.assertEqual(point_cls.DEFAULT, point_cls(x=0.0, y=0.0))
|
|
392
393
|
|
|
393
394
|
def test_or_mutable(self):
|
|
394
395
|
point_cls = self.init_test_module()["Point"]
|
|
@@ -405,8 +406,9 @@ class ModuleInitializerTestCase(unittest.TestCase):
|
|
|
405
406
|
i64=0,
|
|
406
407
|
u64=0,
|
|
407
408
|
t=Timestamp.EPOCH,
|
|
409
|
+
s="",
|
|
408
410
|
)
|
|
409
|
-
b = primitives_cls()
|
|
411
|
+
b = primitives_cls.partial()
|
|
410
412
|
self.assertEqual(a, b)
|
|
411
413
|
self.assertEqual(hash(a), hash(b))
|
|
412
414
|
|
|
@@ -422,8 +424,9 @@ class ModuleInitializerTestCase(unittest.TestCase):
|
|
|
422
424
|
i64=2,
|
|
423
425
|
u64=3,
|
|
424
426
|
t=Timestamp.from_unix_millis(4),
|
|
427
|
+
s="",
|
|
425
428
|
)
|
|
426
|
-
self.assertEqual(serializer.to_json(p), [1, "
|
|
429
|
+
self.assertEqual(serializer.to_json(p), [1, "YQ==", 3.14, 3.14, 1, 2, 3, "", 4])
|
|
427
430
|
|
|
428
431
|
def test_primitives_from_json(self):
|
|
429
432
|
primitives_cls = self.init_test_module()["Primitives"]
|
|
@@ -431,7 +434,7 @@ class ModuleInitializerTestCase(unittest.TestCase):
|
|
|
431
434
|
json = [0] * 100
|
|
432
435
|
self.assertEqual(serializer.from_json(json), primitives_cls.DEFAULT)
|
|
433
436
|
self.assertEqual(
|
|
434
|
-
serializer.from_json([1, "
|
|
437
|
+
serializer.from_json([1, "YQ==", 3.14, 3.14, 1, 2, 3, "", 4]),
|
|
435
438
|
primitives_cls(
|
|
436
439
|
bool=True,
|
|
437
440
|
bytes=b"a",
|
|
@@ -441,6 +444,7 @@ class ModuleInitializerTestCase(unittest.TestCase):
|
|
|
441
444
|
i64=2,
|
|
442
445
|
u64=3,
|
|
443
446
|
t=Timestamp.from_unix_millis(4),
|
|
447
|
+
s="",
|
|
444
448
|
),
|
|
445
449
|
)
|
|
446
450
|
|
|
@@ -455,10 +459,11 @@ class ModuleInitializerTestCase(unittest.TestCase):
|
|
|
455
459
|
i64=2,
|
|
456
460
|
u64=3,
|
|
457
461
|
t=Timestamp.from_unix_millis(4),
|
|
462
|
+
s="",
|
|
458
463
|
)
|
|
459
464
|
self.assertEqual(
|
|
460
465
|
str(p),
|
|
461
|
-
"Primitives(\n bool=True,\n bytes=b'a',\n f32=3.14,\n f64=3.14,\n i32=1,\n i64=2,\n u64=3,\n t=Timestamp(\n unix_millis=4,\n _formatted='1970-01-01T00:00:00.004000Z',\n ),\n)",
|
|
466
|
+
"Primitives(\n bool=True,\n bytes=b'a',\n f32=3.14,\n f64=3.14,\n i32=1,\n i64=2,\n u64=3,\n s='',\n t=Timestamp(\n unix_millis=4,\n _formatted='1970-01-01T00:00:00.004000Z',\n ),\n)",
|
|
462
467
|
)
|
|
463
468
|
|
|
464
469
|
def test_from_json_converts_between_ints_and_floats(self):
|
|
@@ -503,7 +508,7 @@ class ModuleInitializerTestCase(unittest.TestCase):
|
|
|
503
508
|
point_cls = self.init_test_module()["Point"]
|
|
504
509
|
serializer = point_cls.SERIALIZER
|
|
505
510
|
self.assertEqual(serializer.from_json([1.5, 0, 2.5]), point_cls(x=1.5, y=2.5))
|
|
506
|
-
self.assertEqual(serializer.from_json([1.5]), point_cls(x=1.5))
|
|
511
|
+
self.assertEqual(serializer.from_json([1.5]), point_cls(x=1.5, y=0.0))
|
|
507
512
|
self.assertEqual(serializer.from_json([0.0]), point_cls.DEFAULT)
|
|
508
513
|
self.assertEqual(serializer.from_json(0), point_cls.DEFAULT)
|
|
509
514
|
|
|
@@ -534,28 +539,30 @@ class ModuleInitializerTestCase(unittest.TestCase):
|
|
|
534
539
|
foobar_cls = test_module["Foobar"]
|
|
535
540
|
point_cls = test_module["Point"]
|
|
536
541
|
serializer = foobar_cls.SERIALIZER
|
|
537
|
-
foobar = foobar_cls()
|
|
542
|
+
foobar = foobar_cls.partial()
|
|
538
543
|
self.assertEqual(serializer.to_json_code(foobar), "[]")
|
|
539
544
|
self.assertEqual(serializer.from_json_code("[]"), foobar)
|
|
540
|
-
foobar = foobar_cls(a=3)
|
|
545
|
+
foobar = foobar_cls.partial(a=3)
|
|
541
546
|
self.assertEqual(serializer.to_json_code(foobar), "[0,3]")
|
|
542
547
|
self.assertEqual(serializer.from_json_code("[0,3]"), foobar)
|
|
543
548
|
self.assertEqual(serializer.from_json_code("[0,3.1]"), foobar)
|
|
544
|
-
foobar = foobar_cls(b=3, point=point_cls.DEFAULT)
|
|
549
|
+
foobar = foobar_cls(a=0, b=3, point=point_cls.DEFAULT)
|
|
545
550
|
self.assertEqual(serializer.to_json_code(foobar), "[0,0,0,3]")
|
|
546
551
|
self.assertEqual(serializer.from_json_code("[0,0,0,3]"), foobar)
|
|
547
552
|
self.assertEqual(serializer.from_json_code("[0,0,0,3.1]"), foobar)
|
|
548
|
-
foobar = foobar_cls(point=point_cls(x=2))
|
|
553
|
+
foobar = foobar_cls.partial(point=point_cls.partial(x=2))
|
|
549
554
|
self.assertEqual(serializer.to_json_code(foobar), "[0,0,0,0,[2.0]]")
|
|
550
555
|
self.assertEqual(serializer.from_json_code("[0,0,0,0,[2.0]]"), foobar)
|
|
551
556
|
|
|
552
557
|
def test_recursive_struct(self):
|
|
553
558
|
rec_cls = self.init_test_module()["Rec"]
|
|
554
|
-
r = rec_cls(r=rec_cls(r=rec_cls.DEFAULT, x=1))
|
|
559
|
+
r = rec_cls.partial(r=rec_cls(r=rec_cls.DEFAULT, x=1))
|
|
555
560
|
serializer = rec_cls.SERIALIZER
|
|
556
561
|
self.assertEqual(serializer.to_json_code(r), "[[[],1]]")
|
|
557
562
|
self.assertEqual(serializer.from_json_code("[[[],1]]"), r)
|
|
558
|
-
self.assertEqual(
|
|
563
|
+
self.assertEqual(
|
|
564
|
+
str(r), "Rec(\n r=Rec(\n r=Rec.DEFAULT,\n x=1,\n ),\n x=0,\n)"
|
|
565
|
+
)
|
|
559
566
|
|
|
560
567
|
def test_struct_ctor_accepts_mutable_struct(self):
|
|
561
568
|
module = self.init_test_module()
|
|
@@ -564,6 +571,7 @@ class ModuleInitializerTestCase(unittest.TestCase):
|
|
|
564
571
|
segment = segment_cls(
|
|
565
572
|
a=point_cls(x=1.0, y=2.0).to_mutable(),
|
|
566
573
|
b=point_cls(x=3.0, y=4.0),
|
|
574
|
+
c=None,
|
|
567
575
|
)
|
|
568
576
|
self.assertEqual(
|
|
569
577
|
segment,
|
|
@@ -578,7 +586,7 @@ class ModuleInitializerTestCase(unittest.TestCase):
|
|
|
578
586
|
module = self.init_test_module()
|
|
579
587
|
segment_cls = module["Segment"]
|
|
580
588
|
try:
|
|
581
|
-
segment_cls(
|
|
589
|
+
segment_cls.partial(
|
|
582
590
|
# Should be a Point
|
|
583
591
|
a=segment_cls.DEFAULT,
|
|
584
592
|
)
|
|
@@ -588,9 +596,18 @@ class ModuleInitializerTestCase(unittest.TestCase):
|
|
|
588
596
|
|
|
589
597
|
def test_struct_ctor_raises_error_if_unknown_arg(self):
|
|
590
598
|
module = self.init_test_module()
|
|
591
|
-
|
|
599
|
+
point_cls = module["Point"]
|
|
592
600
|
try:
|
|
593
|
-
|
|
601
|
+
point_cls(x=1, b=2, foo=4)
|
|
602
|
+
self.fail("Expected to fail")
|
|
603
|
+
except Exception:
|
|
604
|
+
pass
|
|
605
|
+
|
|
606
|
+
def test_struct_ctor_raises_error_if_missing_arg(self):
|
|
607
|
+
module = self.init_test_module()
|
|
608
|
+
point_cls = module["Point"]
|
|
609
|
+
try:
|
|
610
|
+
point_cls(x=1)
|
|
594
611
|
self.fail("Expected to fail")
|
|
595
612
|
except Exception:
|
|
596
613
|
pass
|
|
@@ -655,13 +672,13 @@ class ModuleInitializerTestCase(unittest.TestCase):
|
|
|
655
672
|
module = self.init_test_module()
|
|
656
673
|
segment_cls = module["Segment"]
|
|
657
674
|
point_cls = module["Point"]
|
|
658
|
-
segment = segment_cls(
|
|
675
|
+
segment = segment_cls.partial(
|
|
659
676
|
c=point_cls.Mutable(x=1.0, y=2.0),
|
|
660
677
|
)
|
|
661
678
|
other_segment = segment.to_mutable().to_frozen()
|
|
662
679
|
self.assertEqual(
|
|
663
680
|
other_segment,
|
|
664
|
-
segment_cls(
|
|
681
|
+
segment_cls.partial(
|
|
665
682
|
c=point_cls(x=1.0, y=2.0),
|
|
666
683
|
),
|
|
667
684
|
)
|
|
@@ -711,7 +728,9 @@ class ModuleInitializerTestCase(unittest.TestCase):
|
|
|
711
728
|
module = self.init_test_module()
|
|
712
729
|
json_value_cls = module["JsonValue"]
|
|
713
730
|
json_object_cls = json_value_cls.Object
|
|
714
|
-
json_object = json_value_cls.wrap_object(
|
|
731
|
+
json_object = json_value_cls.wrap_object(
|
|
732
|
+
json_object_cls(entries=[]).to_mutable()
|
|
733
|
+
)
|
|
715
734
|
self.assertEqual(json_object.kind, "object")
|
|
716
735
|
self.assertEqual(json_object.value, json_object_cls.DEFAULT)
|
|
717
736
|
self.assertEqual(
|
|
@@ -780,7 +799,7 @@ class ModuleInitializerTestCase(unittest.TestCase):
|
|
|
780
799
|
json_value_cls.wrap_object(
|
|
781
800
|
json_value_cls.Object(
|
|
782
801
|
entries=[
|
|
783
|
-
json_value_cls.ObjectEntry(),
|
|
802
|
+
json_value_cls.ObjectEntry.partial(),
|
|
784
803
|
json_value_cls.ObjectEntry.DEFAULT,
|
|
785
804
|
],
|
|
786
805
|
)
|
|
@@ -815,7 +834,7 @@ class ModuleInitializerTestCase(unittest.TestCase):
|
|
|
815
834
|
json_value_cls = self.init_test_module()["JsonValue"]
|
|
816
835
|
json_object_entry_cls = json_value_cls.ObjectEntry
|
|
817
836
|
self.assertEqual(json_object_entry_cls.DEFAULT.value, json_value_cls.UNKNOWN)
|
|
818
|
-
self.assertEqual(json_object_entry_cls().value, json_value_cls.UNKNOWN)
|
|
837
|
+
self.assertEqual(json_object_entry_cls.partial().value, json_value_cls.UNKNOWN)
|
|
819
838
|
|
|
820
839
|
def test_enum_with_unrecognized_and_removed_fields(self):
|
|
821
840
|
json_value_cls = self.init_test_module()["JsonValue"]
|
|
@@ -849,11 +868,18 @@ class ModuleInitializerTestCase(unittest.TestCase):
|
|
|
849
868
|
module = self.init_test_module()
|
|
850
869
|
point_cls = module["Point"]
|
|
851
870
|
self.assertEqual(
|
|
852
|
-
repr(point_cls(x=1.5)),
|
|
853
|
-
"
|
|
871
|
+
repr(point_cls.partial(x=1.5)),
|
|
872
|
+
"\n".join(
|
|
873
|
+
[
|
|
874
|
+
"Point(",
|
|
875
|
+
" x=1.5,",
|
|
876
|
+
" y=0.0,",
|
|
877
|
+
")",
|
|
878
|
+
]
|
|
879
|
+
),
|
|
854
880
|
)
|
|
855
881
|
self.assertEqual(
|
|
856
|
-
repr(point_cls(x=1.5).to_mutable()),
|
|
882
|
+
repr(point_cls.partial(x=1.5).to_mutable()),
|
|
857
883
|
"\n".join(
|
|
858
884
|
[
|
|
859
885
|
"Point.Mutable(",
|
|
@@ -875,8 +901,15 @@ class ModuleInitializerTestCase(unittest.TestCase):
|
|
|
875
901
|
),
|
|
876
902
|
)
|
|
877
903
|
self.assertEqual(
|
|
878
|
-
repr(point_cls()),
|
|
879
|
-
"
|
|
904
|
+
repr(point_cls.partial()),
|
|
905
|
+
"\n".join(
|
|
906
|
+
[
|
|
907
|
+
"Point(",
|
|
908
|
+
" x=0.0,",
|
|
909
|
+
" y=0.0,",
|
|
910
|
+
")",
|
|
911
|
+
]
|
|
912
|
+
),
|
|
880
913
|
)
|
|
881
914
|
self.assertEqual(
|
|
882
915
|
repr(point_cls.DEFAULT),
|
|
@@ -896,19 +929,22 @@ class ModuleInitializerTestCase(unittest.TestCase):
|
|
|
896
929
|
shape_cls = module["Shape"]
|
|
897
930
|
self.assertEqual(
|
|
898
931
|
repr(shape_cls(points=[])),
|
|
899
|
-
"Shape()",
|
|
932
|
+
"Shape(points=[])",
|
|
900
933
|
)
|
|
901
934
|
self.assertEqual(
|
|
902
935
|
repr(shape_cls(points=[]).to_mutable()),
|
|
903
936
|
"Shape.Mutable(points=[])",
|
|
904
937
|
)
|
|
905
938
|
self.assertEqual(
|
|
906
|
-
repr(shape_cls(points=[point_cls(x=1.5)])),
|
|
939
|
+
repr(shape_cls(points=[point_cls(x=1.5, y=0.0)])),
|
|
907
940
|
"\n".join(
|
|
908
941
|
[
|
|
909
942
|
"Shape(",
|
|
910
943
|
" points=[",
|
|
911
|
-
" Point(
|
|
944
|
+
" Point(",
|
|
945
|
+
" x=1.5,",
|
|
946
|
+
" y=0.0,",
|
|
947
|
+
" ),",
|
|
912
948
|
" ],",
|
|
913
949
|
")",
|
|
914
950
|
]
|
|
@@ -918,8 +954,8 @@ class ModuleInitializerTestCase(unittest.TestCase):
|
|
|
918
954
|
repr(
|
|
919
955
|
shape_cls(
|
|
920
956
|
points=[
|
|
921
|
-
point_cls(x=1.5),
|
|
922
|
-
point_cls(y=2.5),
|
|
957
|
+
point_cls.partial(x=1.5),
|
|
958
|
+
point_cls.partial(y=2.5),
|
|
923
959
|
],
|
|
924
960
|
)
|
|
925
961
|
),
|
|
@@ -927,8 +963,14 @@ class ModuleInitializerTestCase(unittest.TestCase):
|
|
|
927
963
|
[
|
|
928
964
|
"Shape(",
|
|
929
965
|
" points=[",
|
|
930
|
-
" Point(
|
|
931
|
-
"
|
|
966
|
+
" Point(",
|
|
967
|
+
" x=1.5,",
|
|
968
|
+
" y=0.0,",
|
|
969
|
+
" ),",
|
|
970
|
+
" Point(",
|
|
971
|
+
" x=0.0,",
|
|
972
|
+
" y=2.5,",
|
|
973
|
+
" ),",
|
|
932
974
|
" ],",
|
|
933
975
|
")",
|
|
934
976
|
]
|
|
@@ -938,8 +980,8 @@ class ModuleInitializerTestCase(unittest.TestCase):
|
|
|
938
980
|
repr(
|
|
939
981
|
shape_cls.Mutable(
|
|
940
982
|
points=[
|
|
941
|
-
point_cls(x=1.5),
|
|
942
|
-
point_cls(y=2.5).to_mutable(),
|
|
983
|
+
point_cls.partial(x=1.5),
|
|
984
|
+
point_cls.partial(y=2.5).to_mutable(),
|
|
943
985
|
]
|
|
944
986
|
)
|
|
945
987
|
),
|
|
@@ -947,7 +989,10 @@ class ModuleInitializerTestCase(unittest.TestCase):
|
|
|
947
989
|
[
|
|
948
990
|
"Shape.Mutable(",
|
|
949
991
|
" points=[",
|
|
950
|
-
" Point(
|
|
992
|
+
" Point(",
|
|
993
|
+
" x=1.5,",
|
|
994
|
+
" y=0.0,",
|
|
995
|
+
" ),",
|
|
951
996
|
" Point.Mutable(",
|
|
952
997
|
" x=0.0,",
|
|
953
998
|
" y=2.5,",
|
|
@@ -991,15 +1036,15 @@ class ModuleInitializerTestCase(unittest.TestCase):
|
|
|
991
1036
|
),
|
|
992
1037
|
)
|
|
993
1038
|
self.assertEqual(
|
|
994
|
-
repr(json_value_cls.wrap_object(json_object_cls
|
|
1039
|
+
repr(json_value_cls.wrap_object(json_object_cls.DEFAULT)),
|
|
995
1040
|
"JsonValue.wrap_object(JsonValue.Object.DEFAULT)",
|
|
996
1041
|
)
|
|
997
1042
|
self.assertEqual(
|
|
998
|
-
repr(json_value_cls.wrap_object(json_object_cls())),
|
|
1043
|
+
repr(json_value_cls.wrap_object(json_object_cls.partial())),
|
|
999
1044
|
"\n".join(
|
|
1000
1045
|
[
|
|
1001
1046
|
"JsonValue.wrap_object(",
|
|
1002
|
-
" JsonValue.Object()",
|
|
1047
|
+
" JsonValue.Object(entries=[])",
|
|
1003
1048
|
")",
|
|
1004
1049
|
]
|
|
1005
1050
|
),
|
|
@@ -1082,6 +1127,7 @@ class ModuleInitializerTestCase(unittest.TestCase):
|
|
|
1082
1127
|
segment = segment_cls(
|
|
1083
1128
|
a=point_cls(x=1.0, y=2.0),
|
|
1084
1129
|
b=point_cls(x=3.0, y=4.0),
|
|
1130
|
+
c=None,
|
|
1085
1131
|
).to_mutable()
|
|
1086
1132
|
a = segment.mutable_a
|
|
1087
1133
|
self.assertIsInstance(a, point_cls.Mutable)
|
|
@@ -2,6 +2,7 @@ import dataclasses
|
|
|
2
2
|
import unittest
|
|
3
3
|
from datetime import datetime, timedelta, timezone
|
|
4
4
|
from typing import Any
|
|
5
|
+
from zoneinfo import ZoneInfo
|
|
5
6
|
|
|
6
7
|
from soia import Timestamp
|
|
7
8
|
|
|
@@ -23,6 +24,14 @@ class TimestampTestCase(unittest.TestCase):
|
|
|
23
24
|
def test_from_datetime(self):
|
|
24
25
|
ts = Timestamp.from_datetime(datetime.fromtimestamp(200, tz=timezone.utc))
|
|
25
26
|
self.assertEqual(ts.unix_millis, 200000)
|
|
27
|
+
ts = Timestamp.from_datetime(
|
|
28
|
+
datetime.fromtimestamp(200, tz=ZoneInfo("America/New_York"))
|
|
29
|
+
)
|
|
30
|
+
self.assertEqual(ts.unix_millis, 200000)
|
|
31
|
+
ts = Timestamp.from_datetime(datetime.fromtimestamp(200))
|
|
32
|
+
self.assertEqual(ts.unix_millis, 200000)
|
|
33
|
+
ts = Timestamp.from_datetime(datetime.min)
|
|
34
|
+
ts = Timestamp.from_datetime(datetime.max)
|
|
26
35
|
|
|
27
36
|
def test_epoch(self):
|
|
28
37
|
self.assertEqual(Timestamp.EPOCH.unix_millis, 0)
|
|
@@ -76,11 +85,12 @@ class TimestampTestCase(unittest.TestCase):
|
|
|
76
85
|
pass
|
|
77
86
|
|
|
78
87
|
def test_to_datetime(self):
|
|
79
|
-
ts = Timestamp.from_unix_seconds(200)
|
|
80
88
|
self.assertEqual(
|
|
81
|
-
|
|
89
|
+
Timestamp.from_unix_seconds(200).to_datetime_or_raise(),
|
|
82
90
|
datetime.fromtimestamp(200, tz=timezone.utc),
|
|
83
91
|
)
|
|
92
|
+
Timestamp.from_datetime(datetime.min + timedelta(days=1)).to_datetime_or_raise()
|
|
93
|
+
Timestamp.from_datetime(datetime.max - timedelta(days=1)).to_datetime_or_raise()
|
|
84
94
|
|
|
85
95
|
def test_to_datetime_out_of_bound(self):
|
|
86
96
|
try:
|
|
@@ -88,6 +98,14 @@ class TimestampTestCase(unittest.TestCase):
|
|
|
88
98
|
self.fail("Expected to fail with OverflowError or ValueError")
|
|
89
99
|
except Exception:
|
|
90
100
|
pass
|
|
101
|
+
self.assertEqual(
|
|
102
|
+
Timestamp.MIN.to_datetime_or_limit(),
|
|
103
|
+
datetime.min.replace(tzinfo=timezone.utc),
|
|
104
|
+
)
|
|
105
|
+
self.assertEqual(
|
|
106
|
+
Timestamp.MAX.to_datetime_or_limit(),
|
|
107
|
+
datetime.max.replace(tzinfo=timezone.utc),
|
|
108
|
+
)
|
|
91
109
|
|
|
92
110
|
def test_add_timedelta(self):
|
|
93
111
|
ts = Timestamp.from_unix_seconds(200)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|