soia-client 1.0.30__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.30 → soia_client-1.1.0}/PKG-INFO +1 -1
- {soia_client-1.0.30 → soia_client-1.1.0}/pyproject.toml +1 -1
- {soia_client-1.0.30 → 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.30 → soia_client-1.1.0}/soia/_impl/primitives.py +5 -2
- {soia_client-1.0.30 → soia_client-1.1.0}/soia/_impl/structs.py +90 -21
- {soia_client-1.0.30 → soia_client-1.1.0}/soia/_impl/timestamp.py +41 -4
- {soia_client-1.0.30 → soia_client-1.1.0}/soia_client.egg-info/PKG-INFO +1 -1
- {soia_client-1.0.30 → soia_client-1.1.0}/soia_client.egg-info/SOURCES.txt +1 -0
- {soia_client-1.0.30 → soia_client-1.1.0}/tests/test_module_initializer.py +87 -40
- {soia_client-1.0.30 → soia_client-1.1.0}/tests/test_timestamp.py +20 -2
- {soia_client-1.0.30 → soia_client-1.1.0}/LICENSE +0 -0
- {soia_client-1.0.30 → soia_client-1.1.0}/README.md +0 -0
- {soia_client-1.0.30 → soia_client-1.1.0}/setup.cfg +0 -0
- {soia_client-1.0.30 → soia_client-1.1.0}/soia/_impl/__init__.py +0 -0
- {soia_client-1.0.30 → soia_client-1.1.0}/soia/_impl/arrays.py +0 -0
- {soia_client-1.0.30 → soia_client-1.1.0}/soia/_impl/enums.py +0 -0
- {soia_client-1.0.30 → soia_client-1.1.0}/soia/_impl/function_maker.py +0 -0
- {soia_client-1.0.30 → soia_client-1.1.0}/soia/_impl/keyed_items.py +0 -0
- {soia_client-1.0.30 → soia_client-1.1.0}/soia/_impl/method.py +0 -0
- {soia_client-1.0.30 → soia_client-1.1.0}/soia/_impl/never.py +0 -0
- {soia_client-1.0.30 → soia_client-1.1.0}/soia/_impl/optionals.py +0 -0
- {soia_client-1.0.30 → soia_client-1.1.0}/soia/_impl/repr.py +0 -0
- {soia_client-1.0.30 → soia_client-1.1.0}/soia/_impl/serializer.py +0 -0
- {soia_client-1.0.30 → soia_client-1.1.0}/soia/_impl/serializers.py +0 -0
- {soia_client-1.0.30 → soia_client-1.1.0}/soia/_impl/service.py +0 -0
- {soia_client-1.0.30 → soia_client-1.1.0}/soia/_impl/service_client.py +0 -0
- {soia_client-1.0.30 → soia_client-1.1.0}/soia/_impl/type_adapter.py +0 -0
- {soia_client-1.0.30 → soia_client-1.1.0}/soia/_module_initializer.py +0 -0
- {soia_client-1.0.30 → soia_client-1.1.0}/soia/_spec.py +0 -0
- {soia_client-1.0.30 → soia_client-1.1.0}/soia/reflection.py +0 -0
- {soia_client-1.0.30 → soia_client-1.1.0}/soia_client.egg-info/dependency_links.txt +0 -0
- {soia_client-1.0.30 → soia_client-1.1.0}/soia_client.egg-info/top_level.txt +0 -0
- {soia_client-1.0.30 → 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))
|
|
@@ -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 = []
|
|
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)
|
|
@@ -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,11 @@ 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)
|
|
370
371
|
|
|
371
372
|
def test_to_mutable(self):
|
|
372
373
|
point_cls = self.init_test_module()["Point"]
|
|
@@ -387,7 +388,8 @@ class ModuleInitializerTestCase(unittest.TestCase):
|
|
|
387
388
|
self.assertEqual(hash(a), hash(b))
|
|
388
389
|
self.assertNotEqual(a, c)
|
|
389
390
|
self.assertNotEqual(a, "foo")
|
|
390
|
-
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))
|
|
391
393
|
|
|
392
394
|
def test_or_mutable(self):
|
|
393
395
|
point_cls = self.init_test_module()["Point"]
|
|
@@ -404,8 +406,9 @@ class ModuleInitializerTestCase(unittest.TestCase):
|
|
|
404
406
|
i64=0,
|
|
405
407
|
u64=0,
|
|
406
408
|
t=Timestamp.EPOCH,
|
|
409
|
+
s="",
|
|
407
410
|
)
|
|
408
|
-
b = primitives_cls()
|
|
411
|
+
b = primitives_cls.partial()
|
|
409
412
|
self.assertEqual(a, b)
|
|
410
413
|
self.assertEqual(hash(a), hash(b))
|
|
411
414
|
|
|
@@ -421,6 +424,7 @@ class ModuleInitializerTestCase(unittest.TestCase):
|
|
|
421
424
|
i64=2,
|
|
422
425
|
u64=3,
|
|
423
426
|
t=Timestamp.from_unix_millis(4),
|
|
427
|
+
s="",
|
|
424
428
|
)
|
|
425
429
|
self.assertEqual(serializer.to_json(p), [1, "YQ==", 3.14, 3.14, 1, 2, 3, "", 4])
|
|
426
430
|
|
|
@@ -440,6 +444,7 @@ class ModuleInitializerTestCase(unittest.TestCase):
|
|
|
440
444
|
i64=2,
|
|
441
445
|
u64=3,
|
|
442
446
|
t=Timestamp.from_unix_millis(4),
|
|
447
|
+
s="",
|
|
443
448
|
),
|
|
444
449
|
)
|
|
445
450
|
|
|
@@ -454,10 +459,11 @@ class ModuleInitializerTestCase(unittest.TestCase):
|
|
|
454
459
|
i64=2,
|
|
455
460
|
u64=3,
|
|
456
461
|
t=Timestamp.from_unix_millis(4),
|
|
462
|
+
s="",
|
|
457
463
|
)
|
|
458
464
|
self.assertEqual(
|
|
459
465
|
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)",
|
|
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)",
|
|
461
467
|
)
|
|
462
468
|
|
|
463
469
|
def test_from_json_converts_between_ints_and_floats(self):
|
|
@@ -502,7 +508,7 @@ class ModuleInitializerTestCase(unittest.TestCase):
|
|
|
502
508
|
point_cls = self.init_test_module()["Point"]
|
|
503
509
|
serializer = point_cls.SERIALIZER
|
|
504
510
|
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))
|
|
511
|
+
self.assertEqual(serializer.from_json([1.5]), point_cls(x=1.5, y=0.0))
|
|
506
512
|
self.assertEqual(serializer.from_json([0.0]), point_cls.DEFAULT)
|
|
507
513
|
self.assertEqual(serializer.from_json(0), point_cls.DEFAULT)
|
|
508
514
|
|
|
@@ -533,28 +539,30 @@ class ModuleInitializerTestCase(unittest.TestCase):
|
|
|
533
539
|
foobar_cls = test_module["Foobar"]
|
|
534
540
|
point_cls = test_module["Point"]
|
|
535
541
|
serializer = foobar_cls.SERIALIZER
|
|
536
|
-
foobar = foobar_cls()
|
|
542
|
+
foobar = foobar_cls.partial()
|
|
537
543
|
self.assertEqual(serializer.to_json_code(foobar), "[]")
|
|
538
544
|
self.assertEqual(serializer.from_json_code("[]"), foobar)
|
|
539
|
-
foobar = foobar_cls(a=3)
|
|
545
|
+
foobar = foobar_cls.partial(a=3)
|
|
540
546
|
self.assertEqual(serializer.to_json_code(foobar), "[0,3]")
|
|
541
547
|
self.assertEqual(serializer.from_json_code("[0,3]"), foobar)
|
|
542
548
|
self.assertEqual(serializer.from_json_code("[0,3.1]"), foobar)
|
|
543
|
-
foobar = foobar_cls(b=3, point=point_cls.DEFAULT)
|
|
549
|
+
foobar = foobar_cls(a=0, b=3, point=point_cls.DEFAULT)
|
|
544
550
|
self.assertEqual(serializer.to_json_code(foobar), "[0,0,0,3]")
|
|
545
551
|
self.assertEqual(serializer.from_json_code("[0,0,0,3]"), foobar)
|
|
546
552
|
self.assertEqual(serializer.from_json_code("[0,0,0,3.1]"), foobar)
|
|
547
|
-
foobar = foobar_cls(point=point_cls(x=2))
|
|
553
|
+
foobar = foobar_cls.partial(point=point_cls.partial(x=2))
|
|
548
554
|
self.assertEqual(serializer.to_json_code(foobar), "[0,0,0,0,[2.0]]")
|
|
549
555
|
self.assertEqual(serializer.from_json_code("[0,0,0,0,[2.0]]"), foobar)
|
|
550
556
|
|
|
551
557
|
def test_recursive_struct(self):
|
|
552
558
|
rec_cls = self.init_test_module()["Rec"]
|
|
553
|
-
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))
|
|
554
560
|
serializer = rec_cls.SERIALIZER
|
|
555
561
|
self.assertEqual(serializer.to_json_code(r), "[[[],1]]")
|
|
556
562
|
self.assertEqual(serializer.from_json_code("[[[],1]]"), r)
|
|
557
|
-
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
|
+
)
|
|
558
566
|
|
|
559
567
|
def test_struct_ctor_accepts_mutable_struct(self):
|
|
560
568
|
module = self.init_test_module()
|
|
@@ -563,6 +571,7 @@ class ModuleInitializerTestCase(unittest.TestCase):
|
|
|
563
571
|
segment = segment_cls(
|
|
564
572
|
a=point_cls(x=1.0, y=2.0).to_mutable(),
|
|
565
573
|
b=point_cls(x=3.0, y=4.0),
|
|
574
|
+
c=None,
|
|
566
575
|
)
|
|
567
576
|
self.assertEqual(
|
|
568
577
|
segment,
|
|
@@ -577,7 +586,7 @@ class ModuleInitializerTestCase(unittest.TestCase):
|
|
|
577
586
|
module = self.init_test_module()
|
|
578
587
|
segment_cls = module["Segment"]
|
|
579
588
|
try:
|
|
580
|
-
segment_cls(
|
|
589
|
+
segment_cls.partial(
|
|
581
590
|
# Should be a Point
|
|
582
591
|
a=segment_cls.DEFAULT,
|
|
583
592
|
)
|
|
@@ -587,9 +596,18 @@ class ModuleInitializerTestCase(unittest.TestCase):
|
|
|
587
596
|
|
|
588
597
|
def test_struct_ctor_raises_error_if_unknown_arg(self):
|
|
589
598
|
module = self.init_test_module()
|
|
590
|
-
|
|
599
|
+
point_cls = module["Point"]
|
|
591
600
|
try:
|
|
592
|
-
|
|
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)
|
|
593
611
|
self.fail("Expected to fail")
|
|
594
612
|
except Exception:
|
|
595
613
|
pass
|
|
@@ -654,13 +672,13 @@ class ModuleInitializerTestCase(unittest.TestCase):
|
|
|
654
672
|
module = self.init_test_module()
|
|
655
673
|
segment_cls = module["Segment"]
|
|
656
674
|
point_cls = module["Point"]
|
|
657
|
-
segment = segment_cls(
|
|
675
|
+
segment = segment_cls.partial(
|
|
658
676
|
c=point_cls.Mutable(x=1.0, y=2.0),
|
|
659
677
|
)
|
|
660
678
|
other_segment = segment.to_mutable().to_frozen()
|
|
661
679
|
self.assertEqual(
|
|
662
680
|
other_segment,
|
|
663
|
-
segment_cls(
|
|
681
|
+
segment_cls.partial(
|
|
664
682
|
c=point_cls(x=1.0, y=2.0),
|
|
665
683
|
),
|
|
666
684
|
)
|
|
@@ -710,7 +728,9 @@ class ModuleInitializerTestCase(unittest.TestCase):
|
|
|
710
728
|
module = self.init_test_module()
|
|
711
729
|
json_value_cls = module["JsonValue"]
|
|
712
730
|
json_object_cls = json_value_cls.Object
|
|
713
|
-
json_object = json_value_cls.wrap_object(
|
|
731
|
+
json_object = json_value_cls.wrap_object(
|
|
732
|
+
json_object_cls(entries=[]).to_mutable()
|
|
733
|
+
)
|
|
714
734
|
self.assertEqual(json_object.kind, "object")
|
|
715
735
|
self.assertEqual(json_object.value, json_object_cls.DEFAULT)
|
|
716
736
|
self.assertEqual(
|
|
@@ -779,7 +799,7 @@ class ModuleInitializerTestCase(unittest.TestCase):
|
|
|
779
799
|
json_value_cls.wrap_object(
|
|
780
800
|
json_value_cls.Object(
|
|
781
801
|
entries=[
|
|
782
|
-
json_value_cls.ObjectEntry(),
|
|
802
|
+
json_value_cls.ObjectEntry.partial(),
|
|
783
803
|
json_value_cls.ObjectEntry.DEFAULT,
|
|
784
804
|
],
|
|
785
805
|
)
|
|
@@ -814,7 +834,7 @@ class ModuleInitializerTestCase(unittest.TestCase):
|
|
|
814
834
|
json_value_cls = self.init_test_module()["JsonValue"]
|
|
815
835
|
json_object_entry_cls = json_value_cls.ObjectEntry
|
|
816
836
|
self.assertEqual(json_object_entry_cls.DEFAULT.value, json_value_cls.UNKNOWN)
|
|
817
|
-
self.assertEqual(json_object_entry_cls().value, json_value_cls.UNKNOWN)
|
|
837
|
+
self.assertEqual(json_object_entry_cls.partial().value, json_value_cls.UNKNOWN)
|
|
818
838
|
|
|
819
839
|
def test_enum_with_unrecognized_and_removed_fields(self):
|
|
820
840
|
json_value_cls = self.init_test_module()["JsonValue"]
|
|
@@ -848,11 +868,18 @@ class ModuleInitializerTestCase(unittest.TestCase):
|
|
|
848
868
|
module = self.init_test_module()
|
|
849
869
|
point_cls = module["Point"]
|
|
850
870
|
self.assertEqual(
|
|
851
|
-
repr(point_cls(x=1.5)),
|
|
852
|
-
"
|
|
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
|
+
),
|
|
853
880
|
)
|
|
854
881
|
self.assertEqual(
|
|
855
|
-
repr(point_cls(x=1.5).to_mutable()),
|
|
882
|
+
repr(point_cls.partial(x=1.5).to_mutable()),
|
|
856
883
|
"\n".join(
|
|
857
884
|
[
|
|
858
885
|
"Point.Mutable(",
|
|
@@ -874,8 +901,15 @@ class ModuleInitializerTestCase(unittest.TestCase):
|
|
|
874
901
|
),
|
|
875
902
|
)
|
|
876
903
|
self.assertEqual(
|
|
877
|
-
repr(point_cls()),
|
|
878
|
-
"
|
|
904
|
+
repr(point_cls.partial()),
|
|
905
|
+
"\n".join(
|
|
906
|
+
[
|
|
907
|
+
"Point(",
|
|
908
|
+
" x=0.0,",
|
|
909
|
+
" y=0.0,",
|
|
910
|
+
")",
|
|
911
|
+
]
|
|
912
|
+
),
|
|
879
913
|
)
|
|
880
914
|
self.assertEqual(
|
|
881
915
|
repr(point_cls.DEFAULT),
|
|
@@ -895,19 +929,22 @@ class ModuleInitializerTestCase(unittest.TestCase):
|
|
|
895
929
|
shape_cls = module["Shape"]
|
|
896
930
|
self.assertEqual(
|
|
897
931
|
repr(shape_cls(points=[])),
|
|
898
|
-
"Shape()",
|
|
932
|
+
"Shape(points=[])",
|
|
899
933
|
)
|
|
900
934
|
self.assertEqual(
|
|
901
935
|
repr(shape_cls(points=[]).to_mutable()),
|
|
902
936
|
"Shape.Mutable(points=[])",
|
|
903
937
|
)
|
|
904
938
|
self.assertEqual(
|
|
905
|
-
repr(shape_cls(points=[point_cls(x=1.5)])),
|
|
939
|
+
repr(shape_cls(points=[point_cls(x=1.5, y=0.0)])),
|
|
906
940
|
"\n".join(
|
|
907
941
|
[
|
|
908
942
|
"Shape(",
|
|
909
943
|
" points=[",
|
|
910
|
-
" Point(
|
|
944
|
+
" Point(",
|
|
945
|
+
" x=1.5,",
|
|
946
|
+
" y=0.0,",
|
|
947
|
+
" ),",
|
|
911
948
|
" ],",
|
|
912
949
|
")",
|
|
913
950
|
]
|
|
@@ -917,8 +954,8 @@ class ModuleInitializerTestCase(unittest.TestCase):
|
|
|
917
954
|
repr(
|
|
918
955
|
shape_cls(
|
|
919
956
|
points=[
|
|
920
|
-
point_cls(x=1.5),
|
|
921
|
-
point_cls(y=2.5),
|
|
957
|
+
point_cls.partial(x=1.5),
|
|
958
|
+
point_cls.partial(y=2.5),
|
|
922
959
|
],
|
|
923
960
|
)
|
|
924
961
|
),
|
|
@@ -926,8 +963,14 @@ class ModuleInitializerTestCase(unittest.TestCase):
|
|
|
926
963
|
[
|
|
927
964
|
"Shape(",
|
|
928
965
|
" points=[",
|
|
929
|
-
" Point(
|
|
930
|
-
"
|
|
966
|
+
" Point(",
|
|
967
|
+
" x=1.5,",
|
|
968
|
+
" y=0.0,",
|
|
969
|
+
" ),",
|
|
970
|
+
" Point(",
|
|
971
|
+
" x=0.0,",
|
|
972
|
+
" y=2.5,",
|
|
973
|
+
" ),",
|
|
931
974
|
" ],",
|
|
932
975
|
")",
|
|
933
976
|
]
|
|
@@ -937,8 +980,8 @@ class ModuleInitializerTestCase(unittest.TestCase):
|
|
|
937
980
|
repr(
|
|
938
981
|
shape_cls.Mutable(
|
|
939
982
|
points=[
|
|
940
|
-
point_cls(x=1.5),
|
|
941
|
-
point_cls(y=2.5).to_mutable(),
|
|
983
|
+
point_cls.partial(x=1.5),
|
|
984
|
+
point_cls.partial(y=2.5).to_mutable(),
|
|
942
985
|
]
|
|
943
986
|
)
|
|
944
987
|
),
|
|
@@ -946,7 +989,10 @@ class ModuleInitializerTestCase(unittest.TestCase):
|
|
|
946
989
|
[
|
|
947
990
|
"Shape.Mutable(",
|
|
948
991
|
" points=[",
|
|
949
|
-
" Point(
|
|
992
|
+
" Point(",
|
|
993
|
+
" x=1.5,",
|
|
994
|
+
" y=0.0,",
|
|
995
|
+
" ),",
|
|
950
996
|
" Point.Mutable(",
|
|
951
997
|
" x=0.0,",
|
|
952
998
|
" y=2.5,",
|
|
@@ -990,15 +1036,15 @@ class ModuleInitializerTestCase(unittest.TestCase):
|
|
|
990
1036
|
),
|
|
991
1037
|
)
|
|
992
1038
|
self.assertEqual(
|
|
993
|
-
repr(json_value_cls.wrap_object(json_object_cls
|
|
1039
|
+
repr(json_value_cls.wrap_object(json_object_cls.DEFAULT)),
|
|
994
1040
|
"JsonValue.wrap_object(JsonValue.Object.DEFAULT)",
|
|
995
1041
|
)
|
|
996
1042
|
self.assertEqual(
|
|
997
|
-
repr(json_value_cls.wrap_object(json_object_cls())),
|
|
1043
|
+
repr(json_value_cls.wrap_object(json_object_cls.partial())),
|
|
998
1044
|
"\n".join(
|
|
999
1045
|
[
|
|
1000
1046
|
"JsonValue.wrap_object(",
|
|
1001
|
-
" JsonValue.Object()",
|
|
1047
|
+
" JsonValue.Object(entries=[])",
|
|
1002
1048
|
")",
|
|
1003
1049
|
]
|
|
1004
1050
|
),
|
|
@@ -1081,6 +1127,7 @@ class ModuleInitializerTestCase(unittest.TestCase):
|
|
|
1081
1127
|
segment = segment_cls(
|
|
1082
1128
|
a=point_cls(x=1.0, y=2.0),
|
|
1083
1129
|
b=point_cls(x=3.0, y=4.0),
|
|
1130
|
+
c=None,
|
|
1084
1131
|
).to_mutable()
|
|
1085
1132
|
a = segment.mutable_a
|
|
1086
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
|