soia-client 1.0.30__tar.gz → 1.1.1__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.30 → soia_client-1.1.1}/PKG-INFO +1 -1
- {soia_client-1.0.30 → soia_client-1.1.1}/pyproject.toml +1 -1
- {soia_client-1.0.30 → soia_client-1.1.1}/soia/__init__.py +3 -0
- soia_client-1.1.1/soia/_impl/keep.py +20 -0
- {soia_client-1.0.30 → soia_client-1.1.1}/soia/_impl/primitives.py +5 -2
- {soia_client-1.0.30 → soia_client-1.1.1}/soia/_impl/structs.py +90 -21
- {soia_client-1.0.30 → soia_client-1.1.1}/soia/_impl/timestamp.py +41 -4
- {soia_client-1.0.30 → soia_client-1.1.1}/soia_client.egg-info/PKG-INFO +1 -1
- {soia_client-1.0.30 → soia_client-1.1.1}/soia_client.egg-info/SOURCES.txt +1 -0
- {soia_client-1.0.30 → soia_client-1.1.1}/tests/test_module_initializer.py +93 -40
- {soia_client-1.0.30 → soia_client-1.1.1}/tests/test_timestamp.py +20 -2
- {soia_client-1.0.30 → soia_client-1.1.1}/LICENSE +0 -0
- {soia_client-1.0.30 → soia_client-1.1.1}/README.md +0 -0
- {soia_client-1.0.30 → soia_client-1.1.1}/setup.cfg +0 -0
- {soia_client-1.0.30 → soia_client-1.1.1}/soia/_impl/__init__.py +0 -0
- {soia_client-1.0.30 → soia_client-1.1.1}/soia/_impl/arrays.py +0 -0
- {soia_client-1.0.30 → soia_client-1.1.1}/soia/_impl/enums.py +0 -0
- {soia_client-1.0.30 → soia_client-1.1.1}/soia/_impl/function_maker.py +0 -0
- {soia_client-1.0.30 → soia_client-1.1.1}/soia/_impl/keyed_items.py +0 -0
- {soia_client-1.0.30 → soia_client-1.1.1}/soia/_impl/method.py +0 -0
- {soia_client-1.0.30 → soia_client-1.1.1}/soia/_impl/never.py +0 -0
- {soia_client-1.0.30 → soia_client-1.1.1}/soia/_impl/optionals.py +0 -0
- {soia_client-1.0.30 → soia_client-1.1.1}/soia/_impl/repr.py +0 -0
- {soia_client-1.0.30 → soia_client-1.1.1}/soia/_impl/serializer.py +0 -0
- {soia_client-1.0.30 → soia_client-1.1.1}/soia/_impl/serializers.py +0 -0
- {soia_client-1.0.30 → soia_client-1.1.1}/soia/_impl/service.py +0 -0
- {soia_client-1.0.30 → soia_client-1.1.1}/soia/_impl/service_client.py +0 -0
- {soia_client-1.0.30 → soia_client-1.1.1}/soia/_impl/type_adapter.py +0 -0
- {soia_client-1.0.30 → soia_client-1.1.1}/soia/_module_initializer.py +0 -0
- {soia_client-1.0.30 → soia_client-1.1.1}/soia/_spec.py +0 -0
- {soia_client-1.0.30 → soia_client-1.1.1}/soia/reflection.py +0 -0
- {soia_client-1.0.30 → soia_client-1.1.1}/soia_client.egg-info/dependency_links.txt +0 -0
- {soia_client-1.0.30 → soia_client-1.1.1}/soia_client.egg-info/top_level.txt +0 -0
- {soia_client-1.0.30 → soia_client-1.1.1}/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))
|
|
@@ -3,11 +3,12 @@ from collections.abc import Callable
|
|
|
3
3
|
from dataclasses import dataclass
|
|
4
4
|
from typing import Any, Final, final
|
|
5
5
|
|
|
6
|
-
from soia import _spec, reflection
|
|
7
6
|
from soia._impl.function_maker import Expr, ExprLike
|
|
8
7
|
from soia._impl.timestamp import Timestamp
|
|
9
8
|
from soia._impl.type_adapter import TypeAdapter
|
|
10
9
|
|
|
10
|
+
from soia import _spec, reflection
|
|
11
|
+
|
|
11
12
|
|
|
12
13
|
class AbstractPrimitiveAdapter(TypeAdapter):
|
|
13
14
|
@final
|
|
@@ -296,7 +297,9 @@ class _BytesAdapter(AbstractPrimitiveAdapter):
|
|
|
296
297
|
) -> Expr:
|
|
297
298
|
return Expr.join(
|
|
298
299
|
Expr.local("b64encode", base64.b64encode),
|
|
299
|
-
"(",
|
|
300
|
+
"(",
|
|
301
|
+
in_expr,
|
|
302
|
+
").decode('utf-8')",
|
|
300
303
|
)
|
|
301
304
|
|
|
302
305
|
def from_json_expr(self, json_expr: ExprLike) -> Expr:
|
|
@@ -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 = ["_self"]
|
|
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, f" 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="replace",
|
|
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)
|
|
@@ -2,10 +2,11 @@ import dataclasses
|
|
|
2
2
|
import unittest
|
|
3
3
|
from typing import Any
|
|
4
4
|
|
|
5
|
-
from soia import KeyedItems, Method, Timestamp, _spec
|
|
6
5
|
from soia._module_initializer import init_module
|
|
7
6
|
from soia.reflection import TypeDescriptor
|
|
8
7
|
|
|
8
|
+
from soia import KeyedItems, Method, Timestamp, _spec
|
|
9
|
+
|
|
9
10
|
|
|
10
11
|
class ModuleInitializerTestCase(unittest.TestCase):
|
|
11
12
|
def init_test_module(self) -> dict[str, Any]:
|
|
@@ -362,11 +363,17 @@ class ModuleInitializerTestCase(unittest.TestCase):
|
|
|
362
363
|
self.assertEqual(point.x, 1.5)
|
|
363
364
|
self.assertEqual(point.y, 2.5)
|
|
364
365
|
|
|
365
|
-
def
|
|
366
|
+
def test_partial_static_factory_method(self):
|
|
366
367
|
point_cls = self.init_test_module()["Point"]
|
|
367
|
-
point = point_cls.
|
|
368
|
+
point = point_cls.partial(x=1.5)
|
|
368
369
|
self.assertEqual(point.x, 1.5)
|
|
369
|
-
self.assertEqual(point.y,
|
|
370
|
+
self.assertEqual(point.y, 0.0)
|
|
371
|
+
|
|
372
|
+
def test_replace(self):
|
|
373
|
+
point_cls = self.init_test_module()["Point"]
|
|
374
|
+
point = point_cls(x=1.5, y=2.5).replace(y=3.5)
|
|
375
|
+
self.assertEqual(point.x, 1.5)
|
|
376
|
+
self.assertEqual(point.y, 3.5)
|
|
370
377
|
|
|
371
378
|
def test_to_mutable(self):
|
|
372
379
|
point_cls = self.init_test_module()["Point"]
|
|
@@ -387,7 +394,8 @@ class ModuleInitializerTestCase(unittest.TestCase):
|
|
|
387
394
|
self.assertEqual(hash(a), hash(b))
|
|
388
395
|
self.assertNotEqual(a, c)
|
|
389
396
|
self.assertNotEqual(a, "foo")
|
|
390
|
-
self.assertEqual(point_cls(), point_cls(x=0.0, y=0.0))
|
|
397
|
+
self.assertEqual(point_cls.partial(), point_cls(x=0.0, y=0.0))
|
|
398
|
+
self.assertEqual(point_cls.DEFAULT, point_cls(x=0.0, y=0.0))
|
|
391
399
|
|
|
392
400
|
def test_or_mutable(self):
|
|
393
401
|
point_cls = self.init_test_module()["Point"]
|
|
@@ -404,8 +412,9 @@ class ModuleInitializerTestCase(unittest.TestCase):
|
|
|
404
412
|
i64=0,
|
|
405
413
|
u64=0,
|
|
406
414
|
t=Timestamp.EPOCH,
|
|
415
|
+
s="",
|
|
407
416
|
)
|
|
408
|
-
b = primitives_cls()
|
|
417
|
+
b = primitives_cls.partial()
|
|
409
418
|
self.assertEqual(a, b)
|
|
410
419
|
self.assertEqual(hash(a), hash(b))
|
|
411
420
|
|
|
@@ -421,6 +430,7 @@ class ModuleInitializerTestCase(unittest.TestCase):
|
|
|
421
430
|
i64=2,
|
|
422
431
|
u64=3,
|
|
423
432
|
t=Timestamp.from_unix_millis(4),
|
|
433
|
+
s="",
|
|
424
434
|
)
|
|
425
435
|
self.assertEqual(serializer.to_json(p), [1, "YQ==", 3.14, 3.14, 1, 2, 3, "", 4])
|
|
426
436
|
|
|
@@ -440,6 +450,7 @@ class ModuleInitializerTestCase(unittest.TestCase):
|
|
|
440
450
|
i64=2,
|
|
441
451
|
u64=3,
|
|
442
452
|
t=Timestamp.from_unix_millis(4),
|
|
453
|
+
s="",
|
|
443
454
|
),
|
|
444
455
|
)
|
|
445
456
|
|
|
@@ -454,10 +465,11 @@ class ModuleInitializerTestCase(unittest.TestCase):
|
|
|
454
465
|
i64=2,
|
|
455
466
|
u64=3,
|
|
456
467
|
t=Timestamp.from_unix_millis(4),
|
|
468
|
+
s="",
|
|
457
469
|
)
|
|
458
470
|
self.assertEqual(
|
|
459
471
|
str(p),
|
|
460
|
-
"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)",
|
|
472
|
+
"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)",
|
|
461
473
|
)
|
|
462
474
|
|
|
463
475
|
def test_from_json_converts_between_ints_and_floats(self):
|
|
@@ -502,7 +514,7 @@ class ModuleInitializerTestCase(unittest.TestCase):
|
|
|
502
514
|
point_cls = self.init_test_module()["Point"]
|
|
503
515
|
serializer = point_cls.SERIALIZER
|
|
504
516
|
self.assertEqual(serializer.from_json([1.5, 0, 2.5]), point_cls(x=1.5, y=2.5))
|
|
505
|
-
self.assertEqual(serializer.from_json([1.5]), point_cls(x=1.5))
|
|
517
|
+
self.assertEqual(serializer.from_json([1.5]), point_cls(x=1.5, y=0.0))
|
|
506
518
|
self.assertEqual(serializer.from_json([0.0]), point_cls.DEFAULT)
|
|
507
519
|
self.assertEqual(serializer.from_json(0), point_cls.DEFAULT)
|
|
508
520
|
|
|
@@ -533,28 +545,30 @@ class ModuleInitializerTestCase(unittest.TestCase):
|
|
|
533
545
|
foobar_cls = test_module["Foobar"]
|
|
534
546
|
point_cls = test_module["Point"]
|
|
535
547
|
serializer = foobar_cls.SERIALIZER
|
|
536
|
-
foobar = foobar_cls()
|
|
548
|
+
foobar = foobar_cls.partial()
|
|
537
549
|
self.assertEqual(serializer.to_json_code(foobar), "[]")
|
|
538
550
|
self.assertEqual(serializer.from_json_code("[]"), foobar)
|
|
539
|
-
foobar = foobar_cls(a=3)
|
|
551
|
+
foobar = foobar_cls.partial(a=3)
|
|
540
552
|
self.assertEqual(serializer.to_json_code(foobar), "[0,3]")
|
|
541
553
|
self.assertEqual(serializer.from_json_code("[0,3]"), foobar)
|
|
542
554
|
self.assertEqual(serializer.from_json_code("[0,3.1]"), foobar)
|
|
543
|
-
foobar = foobar_cls(b=3, point=point_cls.DEFAULT)
|
|
555
|
+
foobar = foobar_cls(a=0, b=3, point=point_cls.DEFAULT)
|
|
544
556
|
self.assertEqual(serializer.to_json_code(foobar), "[0,0,0,3]")
|
|
545
557
|
self.assertEqual(serializer.from_json_code("[0,0,0,3]"), foobar)
|
|
546
558
|
self.assertEqual(serializer.from_json_code("[0,0,0,3.1]"), foobar)
|
|
547
|
-
foobar = foobar_cls(point=point_cls(x=2))
|
|
559
|
+
foobar = foobar_cls.partial(point=point_cls.partial(x=2))
|
|
548
560
|
self.assertEqual(serializer.to_json_code(foobar), "[0,0,0,0,[2.0]]")
|
|
549
561
|
self.assertEqual(serializer.from_json_code("[0,0,0,0,[2.0]]"), foobar)
|
|
550
562
|
|
|
551
563
|
def test_recursive_struct(self):
|
|
552
564
|
rec_cls = self.init_test_module()["Rec"]
|
|
553
|
-
r = rec_cls(r=rec_cls(r=rec_cls.DEFAULT, x=1))
|
|
565
|
+
r = rec_cls.partial(r=rec_cls(r=rec_cls.DEFAULT, x=1))
|
|
554
566
|
serializer = rec_cls.SERIALIZER
|
|
555
567
|
self.assertEqual(serializer.to_json_code(r), "[[[],1]]")
|
|
556
568
|
self.assertEqual(serializer.from_json_code("[[[],1]]"), r)
|
|
557
|
-
self.assertEqual(
|
|
569
|
+
self.assertEqual(
|
|
570
|
+
str(r), "Rec(\n r=Rec(\n r=Rec.DEFAULT,\n x=1,\n ),\n x=0,\n)"
|
|
571
|
+
)
|
|
558
572
|
|
|
559
573
|
def test_struct_ctor_accepts_mutable_struct(self):
|
|
560
574
|
module = self.init_test_module()
|
|
@@ -563,6 +577,7 @@ class ModuleInitializerTestCase(unittest.TestCase):
|
|
|
563
577
|
segment = segment_cls(
|
|
564
578
|
a=point_cls(x=1.0, y=2.0).to_mutable(),
|
|
565
579
|
b=point_cls(x=3.0, y=4.0),
|
|
580
|
+
c=None,
|
|
566
581
|
)
|
|
567
582
|
self.assertEqual(
|
|
568
583
|
segment,
|
|
@@ -577,7 +592,7 @@ class ModuleInitializerTestCase(unittest.TestCase):
|
|
|
577
592
|
module = self.init_test_module()
|
|
578
593
|
segment_cls = module["Segment"]
|
|
579
594
|
try:
|
|
580
|
-
segment_cls(
|
|
595
|
+
segment_cls.partial(
|
|
581
596
|
# Should be a Point
|
|
582
597
|
a=segment_cls.DEFAULT,
|
|
583
598
|
)
|
|
@@ -587,9 +602,18 @@ class ModuleInitializerTestCase(unittest.TestCase):
|
|
|
587
602
|
|
|
588
603
|
def test_struct_ctor_raises_error_if_unknown_arg(self):
|
|
589
604
|
module = self.init_test_module()
|
|
590
|
-
|
|
605
|
+
point_cls = module["Point"]
|
|
591
606
|
try:
|
|
592
|
-
|
|
607
|
+
point_cls(x=1, b=2, foo=4)
|
|
608
|
+
self.fail("Expected to fail")
|
|
609
|
+
except Exception:
|
|
610
|
+
pass
|
|
611
|
+
|
|
612
|
+
def test_struct_ctor_raises_error_if_missing_arg(self):
|
|
613
|
+
module = self.init_test_module()
|
|
614
|
+
point_cls = module["Point"]
|
|
615
|
+
try:
|
|
616
|
+
point_cls(x=1)
|
|
593
617
|
self.fail("Expected to fail")
|
|
594
618
|
except Exception:
|
|
595
619
|
pass
|
|
@@ -654,13 +678,13 @@ class ModuleInitializerTestCase(unittest.TestCase):
|
|
|
654
678
|
module = self.init_test_module()
|
|
655
679
|
segment_cls = module["Segment"]
|
|
656
680
|
point_cls = module["Point"]
|
|
657
|
-
segment = segment_cls(
|
|
681
|
+
segment = segment_cls.partial(
|
|
658
682
|
c=point_cls.Mutable(x=1.0, y=2.0),
|
|
659
683
|
)
|
|
660
684
|
other_segment = segment.to_mutable().to_frozen()
|
|
661
685
|
self.assertEqual(
|
|
662
686
|
other_segment,
|
|
663
|
-
segment_cls(
|
|
687
|
+
segment_cls.partial(
|
|
664
688
|
c=point_cls(x=1.0, y=2.0),
|
|
665
689
|
),
|
|
666
690
|
)
|
|
@@ -710,7 +734,9 @@ class ModuleInitializerTestCase(unittest.TestCase):
|
|
|
710
734
|
module = self.init_test_module()
|
|
711
735
|
json_value_cls = module["JsonValue"]
|
|
712
736
|
json_object_cls = json_value_cls.Object
|
|
713
|
-
json_object = json_value_cls.wrap_object(
|
|
737
|
+
json_object = json_value_cls.wrap_object(
|
|
738
|
+
json_object_cls(entries=[]).to_mutable()
|
|
739
|
+
)
|
|
714
740
|
self.assertEqual(json_object.kind, "object")
|
|
715
741
|
self.assertEqual(json_object.value, json_object_cls.DEFAULT)
|
|
716
742
|
self.assertEqual(
|
|
@@ -779,7 +805,7 @@ class ModuleInitializerTestCase(unittest.TestCase):
|
|
|
779
805
|
json_value_cls.wrap_object(
|
|
780
806
|
json_value_cls.Object(
|
|
781
807
|
entries=[
|
|
782
|
-
json_value_cls.ObjectEntry(),
|
|
808
|
+
json_value_cls.ObjectEntry.partial(),
|
|
783
809
|
json_value_cls.ObjectEntry.DEFAULT,
|
|
784
810
|
],
|
|
785
811
|
)
|
|
@@ -814,7 +840,7 @@ class ModuleInitializerTestCase(unittest.TestCase):
|
|
|
814
840
|
json_value_cls = self.init_test_module()["JsonValue"]
|
|
815
841
|
json_object_entry_cls = json_value_cls.ObjectEntry
|
|
816
842
|
self.assertEqual(json_object_entry_cls.DEFAULT.value, json_value_cls.UNKNOWN)
|
|
817
|
-
self.assertEqual(json_object_entry_cls().value, json_value_cls.UNKNOWN)
|
|
843
|
+
self.assertEqual(json_object_entry_cls.partial().value, json_value_cls.UNKNOWN)
|
|
818
844
|
|
|
819
845
|
def test_enum_with_unrecognized_and_removed_fields(self):
|
|
820
846
|
json_value_cls = self.init_test_module()["JsonValue"]
|
|
@@ -848,11 +874,18 @@ class ModuleInitializerTestCase(unittest.TestCase):
|
|
|
848
874
|
module = self.init_test_module()
|
|
849
875
|
point_cls = module["Point"]
|
|
850
876
|
self.assertEqual(
|
|
851
|
-
repr(point_cls(x=1.5)),
|
|
852
|
-
"
|
|
877
|
+
repr(point_cls.partial(x=1.5)),
|
|
878
|
+
"\n".join(
|
|
879
|
+
[
|
|
880
|
+
"Point(",
|
|
881
|
+
" x=1.5,",
|
|
882
|
+
" y=0.0,",
|
|
883
|
+
")",
|
|
884
|
+
]
|
|
885
|
+
),
|
|
853
886
|
)
|
|
854
887
|
self.assertEqual(
|
|
855
|
-
repr(point_cls(x=1.5).to_mutable()),
|
|
888
|
+
repr(point_cls.partial(x=1.5).to_mutable()),
|
|
856
889
|
"\n".join(
|
|
857
890
|
[
|
|
858
891
|
"Point.Mutable(",
|
|
@@ -874,8 +907,15 @@ class ModuleInitializerTestCase(unittest.TestCase):
|
|
|
874
907
|
),
|
|
875
908
|
)
|
|
876
909
|
self.assertEqual(
|
|
877
|
-
repr(point_cls()),
|
|
878
|
-
"
|
|
910
|
+
repr(point_cls.partial()),
|
|
911
|
+
"\n".join(
|
|
912
|
+
[
|
|
913
|
+
"Point(",
|
|
914
|
+
" x=0.0,",
|
|
915
|
+
" y=0.0,",
|
|
916
|
+
")",
|
|
917
|
+
]
|
|
918
|
+
),
|
|
879
919
|
)
|
|
880
920
|
self.assertEqual(
|
|
881
921
|
repr(point_cls.DEFAULT),
|
|
@@ -895,19 +935,22 @@ class ModuleInitializerTestCase(unittest.TestCase):
|
|
|
895
935
|
shape_cls = module["Shape"]
|
|
896
936
|
self.assertEqual(
|
|
897
937
|
repr(shape_cls(points=[])),
|
|
898
|
-
"Shape()",
|
|
938
|
+
"Shape(points=[])",
|
|
899
939
|
)
|
|
900
940
|
self.assertEqual(
|
|
901
941
|
repr(shape_cls(points=[]).to_mutable()),
|
|
902
942
|
"Shape.Mutable(points=[])",
|
|
903
943
|
)
|
|
904
944
|
self.assertEqual(
|
|
905
|
-
repr(shape_cls(points=[point_cls(x=1.5)])),
|
|
945
|
+
repr(shape_cls(points=[point_cls(x=1.5, y=0.0)])),
|
|
906
946
|
"\n".join(
|
|
907
947
|
[
|
|
908
948
|
"Shape(",
|
|
909
949
|
" points=[",
|
|
910
|
-
" Point(
|
|
950
|
+
" Point(",
|
|
951
|
+
" x=1.5,",
|
|
952
|
+
" y=0.0,",
|
|
953
|
+
" ),",
|
|
911
954
|
" ],",
|
|
912
955
|
")",
|
|
913
956
|
]
|
|
@@ -917,8 +960,8 @@ class ModuleInitializerTestCase(unittest.TestCase):
|
|
|
917
960
|
repr(
|
|
918
961
|
shape_cls(
|
|
919
962
|
points=[
|
|
920
|
-
point_cls(x=1.5),
|
|
921
|
-
point_cls(y=2.5),
|
|
963
|
+
point_cls.partial(x=1.5),
|
|
964
|
+
point_cls.partial(y=2.5),
|
|
922
965
|
],
|
|
923
966
|
)
|
|
924
967
|
),
|
|
@@ -926,8 +969,14 @@ class ModuleInitializerTestCase(unittest.TestCase):
|
|
|
926
969
|
[
|
|
927
970
|
"Shape(",
|
|
928
971
|
" points=[",
|
|
929
|
-
" Point(
|
|
930
|
-
"
|
|
972
|
+
" Point(",
|
|
973
|
+
" x=1.5,",
|
|
974
|
+
" y=0.0,",
|
|
975
|
+
" ),",
|
|
976
|
+
" Point(",
|
|
977
|
+
" x=0.0,",
|
|
978
|
+
" y=2.5,",
|
|
979
|
+
" ),",
|
|
931
980
|
" ],",
|
|
932
981
|
")",
|
|
933
982
|
]
|
|
@@ -937,8 +986,8 @@ class ModuleInitializerTestCase(unittest.TestCase):
|
|
|
937
986
|
repr(
|
|
938
987
|
shape_cls.Mutable(
|
|
939
988
|
points=[
|
|
940
|
-
point_cls(x=1.5),
|
|
941
|
-
point_cls(y=2.5).to_mutable(),
|
|
989
|
+
point_cls.partial(x=1.5),
|
|
990
|
+
point_cls.partial(y=2.5).to_mutable(),
|
|
942
991
|
]
|
|
943
992
|
)
|
|
944
993
|
),
|
|
@@ -946,7 +995,10 @@ class ModuleInitializerTestCase(unittest.TestCase):
|
|
|
946
995
|
[
|
|
947
996
|
"Shape.Mutable(",
|
|
948
997
|
" points=[",
|
|
949
|
-
" Point(
|
|
998
|
+
" Point(",
|
|
999
|
+
" x=1.5,",
|
|
1000
|
+
" y=0.0,",
|
|
1001
|
+
" ),",
|
|
950
1002
|
" Point.Mutable(",
|
|
951
1003
|
" x=0.0,",
|
|
952
1004
|
" y=2.5,",
|
|
@@ -990,15 +1042,15 @@ class ModuleInitializerTestCase(unittest.TestCase):
|
|
|
990
1042
|
),
|
|
991
1043
|
)
|
|
992
1044
|
self.assertEqual(
|
|
993
|
-
repr(json_value_cls.wrap_object(json_object_cls
|
|
1045
|
+
repr(json_value_cls.wrap_object(json_object_cls.DEFAULT)),
|
|
994
1046
|
"JsonValue.wrap_object(JsonValue.Object.DEFAULT)",
|
|
995
1047
|
)
|
|
996
1048
|
self.assertEqual(
|
|
997
|
-
repr(json_value_cls.wrap_object(json_object_cls())),
|
|
1049
|
+
repr(json_value_cls.wrap_object(json_object_cls.partial())),
|
|
998
1050
|
"\n".join(
|
|
999
1051
|
[
|
|
1000
1052
|
"JsonValue.wrap_object(",
|
|
1001
|
-
" JsonValue.Object()",
|
|
1053
|
+
" JsonValue.Object(entries=[])",
|
|
1002
1054
|
")",
|
|
1003
1055
|
]
|
|
1004
1056
|
),
|
|
@@ -1081,6 +1133,7 @@ class ModuleInitializerTestCase(unittest.TestCase):
|
|
|
1081
1133
|
segment = segment_cls(
|
|
1082
1134
|
a=point_cls(x=1.0, y=2.0),
|
|
1083
1135
|
b=point_cls(x=3.0, y=4.0),
|
|
1136
|
+
c=None,
|
|
1084
1137
|
).to_mutable()
|
|
1085
1138
|
a = segment.mutable_a
|
|
1086
1139
|
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
|