soia-client 1.0.29__py3-none-any.whl → 1.1.0__py3-none-any.whl

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/__init__.py CHANGED
@@ -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",
soia/_impl/keep.py ADDED
@@ -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))
soia/_impl/primitives.py CHANGED
@@ -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(in_expr, ".hex()")
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("fromhex", _BytesAdapter._fromhex_fn), "(", json_expr, ' or "")'
307
+ Expr.local("b64decode", base64.b64decode), "(", json_expr, ' or "")'
304
308
  )
305
309
 
306
310
  def get_type(self) -> reflection.Type:
soia/_impl/structs.py CHANGED
@@ -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__ = cast(Any, _make_mutable_class_init_fn(fields))
138
- frozen_class.whole = _make_whole_static_factory_method(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 _make_whole_static_factory_method(frozen_class: type) -> Callable[..., Any]:
388
- def whole(**kwargs):
389
- return frozen_class(**kwargs)
390
- return whole
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
- to_frozen_expr = field.type.to_frozen_expr(f"(self.{attribute})")
553
- is_not_default_expr = field.type.is_not_default_expr(
554
- to_frozen_expr, to_frozen_expr
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:")
soia/_impl/timestamp.py CHANGED
@@ -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
- return Timestamp.from_unix_seconds(dt.timestamp())
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
- return datetime.datetime.fromtimestamp(
45
- self.unix_seconds, tz=datetime.timezone.utc
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)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: soia-client
3
- Version: 1.0.29
3
+ Version: 1.1.0
4
4
  Author-email: Tyler Fibonacci <gepheum@gmail.com>
5
5
  License: MIT License
6
6
 
@@ -1,4 +1,4 @@
1
- soia/__init__.py,sha256=GFsFZjzbElOC3AJHoaiI_eb3SRXu0-ooE0QXusL-O1k,724
1
+ soia/__init__.py,sha256=WPhdlu7zKBSWd4DVFWXQKQoptD0gU7xGFm4tbQHrVc8,787
2
2
  soia/_module_initializer.py,sha256=1TWnc0qUO7_iljQR2ywVERrrkY5jIkT3zucc-AYZyrQ,4221
3
3
  soia/_spec.py,sha256=Y5EHHQa6qNeJc29aaqGrFPnPFXxlL7TED9_AXUGBjf0,3663
4
4
  soia/reflection.py,sha256=U5knJGmawARCdcEhNxek4dvx48WLPETLqIqKBPWwT4Q,8771
@@ -6,21 +6,22 @@ soia/_impl/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
6
  soia/_impl/arrays.py,sha256=C3j3RZdSvqmogbGAi3EprLkUYMFfO-IJhoCOte5blAw,5477
7
7
  soia/_impl/enums.py,sha256=jlg2YYGgMg6BMmSy0v0MBgZmZYzLvVS0j68TxOCiSm8,17039
8
8
  soia/_impl/function_maker.py,sha256=MvCDv1WwzKGJzZbNCnJ_8-MP3m1xTIabumXA-9Ydd9M,5639
9
+ soia/_impl/keep.py,sha256=GDeMKKUcvUFXWJVzBPB9CuLkcQb93sWKvo0PcyKUBzg,370
9
10
  soia/_impl/keyed_items.py,sha256=EoPrdeLJ8i7zCH-oW-7qOcyadyrzykB6lkrf3xRVmyk,337
10
11
  soia/_impl/method.py,sha256=C6ygh-Li4zADxFR_gfeBTuPUzLjZ1XHCp0ZI4-uiRYs,455
11
12
  soia/_impl/never.py,sha256=T7PHtIipuXlici4wa0yuJKrn9JgcT-ODC3QVCwsPprY,46
12
13
  soia/_impl/optionals.py,sha256=KTgmbfYLJLrju5v10jgnnnW98QG6NYw2We3gdgi9i2E,2414
13
- soia/_impl/primitives.py,sha256=Xk26Fv4oQG2oXd3tS_2sAnJYQdXYX9nva09713AcJvs,8940
14
+ soia/_impl/primitives.py,sha256=r-55rO12TwwWCQJgzEEgVpXMVGg0dRhGvFWX_rGeqsQ,9025
14
15
  soia/_impl/repr.py,sha256=7WX0bEAVENTjlyZIcbT8TcJylS7IRIyafGCmqaIMxFM,1413
15
16
  soia/_impl/serializer.py,sha256=28IwkjtUnLpbnPQfVNfJXkApCK4JhXHwLkC5MVhF8xo,3529
16
17
  soia/_impl/serializers.py,sha256=IL9jHHMo11pgrL1-crarOEElvTyV5YM6FTcgumjW6IU,2564
17
18
  soia/_impl/service.py,sha256=3dpcH4XEBTEShyjzxMA_zDGg9_amTFF5A49U6hivAdQ,14792
18
19
  soia/_impl/service_client.py,sha256=0WgiN6Stg548FAdZQMuezZoooe4MYw2frEQ5KUE7xag,5311
19
- soia/_impl/structs.py,sha256=KrqucOqSxXda4-3gKyCmx_Oj4osTFBLk9ORyRkV_7dk,26785
20
- soia/_impl/timestamp.py,sha256=lXBNH8mPmzflkNjSKZSBl2XS-ot9N8N92B_zGO2SMtU,4078
20
+ soia/_impl/structs.py,sha256=z9PVPlwG36z6uCQFnl7QNfVjCldgPze4rDXJhhcco24,28221
21
+ soia/_impl/timestamp.py,sha256=upnEkvW-7AaMJ9_nNqntvhJ4pFWWCAXNjEOPtszdwgE,5645
21
22
  soia/_impl/type_adapter.py,sha256=RyIyh4Fnt9rMy0HRzC-a2v2JAdZsV9FBzoGEUVygVRE,2101
22
- soia_client-1.0.29.dist-info/licenses/LICENSE,sha256=SaAftKkX6hfSOiPdENQPS70tifH3PDHgazq8eK2Pwfw,1064
23
- soia_client-1.0.29.dist-info/METADATA,sha256=Ku2iQYq_A0BOH0B4uW5H_8DoZdIXSbSwIUX3jTnP6iA,2122
24
- soia_client-1.0.29.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
25
- soia_client-1.0.29.dist-info/top_level.txt,sha256=lsYG9JrvauFe1oIV5zvnwsS9hsx3ztwfK_937op9mxc,5
26
- soia_client-1.0.29.dist-info/RECORD,,
23
+ soia_client-1.1.0.dist-info/licenses/LICENSE,sha256=SaAftKkX6hfSOiPdENQPS70tifH3PDHgazq8eK2Pwfw,1064
24
+ soia_client-1.1.0.dist-info/METADATA,sha256=6ojgF0DUendLMRkYBgyMSdTlDVR7rp1E4_XM2QtPBTQ,2121
25
+ soia_client-1.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
26
+ soia_client-1.1.0.dist-info/top_level.txt,sha256=lsYG9JrvauFe1oIV5zvnwsS9hsx3ztwfK_937op9mxc,5
27
+ soia_client-1.1.0.dist-info/RECORD,,