tigerbeetle 0.16.55__py3-none-any.whl → 0.16.57__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.
- tigerbeetle/__init__.py +37 -1
- tigerbeetle/bindings.py +47 -36
- tigerbeetle/client.py +104 -43
- tigerbeetle/lib/aarch64-linux-gnu.2.27/libtb_client.so +0 -0
- tigerbeetle/lib/aarch64-linux-musl/libtb_client.so +0 -0
- tigerbeetle/lib/aarch64-macos/libtb_client.dylib +0 -0
- tigerbeetle/lib/x86_64-linux-gnu.2.27/libtb_client.so +0 -0
- tigerbeetle/lib/x86_64-linux-musl/libtb_client.so +0 -0
- tigerbeetle/lib/x86_64-macos/libtb_client.dylib +0 -0
- tigerbeetle/lib/x86_64-windows/tb_client.dll +0 -0
- tigerbeetle/lib.py +20 -18
- tigerbeetle/py.typed +0 -0
- {tigerbeetle-0.16.55.dist-info → tigerbeetle-0.16.57.dist-info}/METADATA +6 -1
- tigerbeetle-0.16.57.dist-info/RECORD +15 -0
- tigerbeetle-0.16.55.dist-info/RECORD +0 -14
- {tigerbeetle-0.16.55.dist-info → tigerbeetle-0.16.57.dist-info}/WHEEL +0 -0
tigerbeetle/__init__.py
CHANGED
|
@@ -1,3 +1,39 @@
|
|
|
1
1
|
from .bindings import * # noqa
|
|
2
|
-
from .client import ClientAsync, ClientSync, id, amount_max, configure_logging # noqa
|
|
2
|
+
from .client import ClientAsync, ClientSync, id, amount_max, configure_logging, PacketError # noqa
|
|
3
3
|
from .lib import IntegerOverflowError, NativeError
|
|
4
|
+
|
|
5
|
+
# Explicitly declare public exports:
|
|
6
|
+
__all__ = [
|
|
7
|
+
# from .client:
|
|
8
|
+
"ClientAsync",
|
|
9
|
+
"ClientSync",
|
|
10
|
+
"id",
|
|
11
|
+
"amount_max",
|
|
12
|
+
"configure_logging",
|
|
13
|
+
# from .lib:
|
|
14
|
+
"IntegerOverflowError",
|
|
15
|
+
"NativeError",
|
|
16
|
+
# from .bindings - everything treated as public:
|
|
17
|
+
"Operation",
|
|
18
|
+
"PacketStatus",
|
|
19
|
+
"InitStatus",
|
|
20
|
+
"ClientStatus",
|
|
21
|
+
"LogLevel",
|
|
22
|
+
"RegisterLogCallbackStatus",
|
|
23
|
+
"AccountFlags",
|
|
24
|
+
"TransferFlags",
|
|
25
|
+
"AccountFilterFlags",
|
|
26
|
+
"QueryFilterFlags",
|
|
27
|
+
"CreateAccountResult",
|
|
28
|
+
"CreateTransferResult",
|
|
29
|
+
"Account",
|
|
30
|
+
"Transfer",
|
|
31
|
+
"CreateAccountsResult",
|
|
32
|
+
"CreateTransfersResult",
|
|
33
|
+
"AccountFilter",
|
|
34
|
+
"AccountBalance",
|
|
35
|
+
"QueryFilter",
|
|
36
|
+
"InitParameters",
|
|
37
|
+
"AsyncStateMachineMixin",
|
|
38
|
+
"StateMachineMixin",
|
|
39
|
+
]
|
tigerbeetle/bindings.py
CHANGED
|
@@ -6,10 +6,21 @@ from __future__ import annotations
|
|
|
6
6
|
|
|
7
7
|
import ctypes
|
|
8
8
|
import enum
|
|
9
|
+
import sys
|
|
10
|
+
from dataclasses import dataclass
|
|
9
11
|
from collections.abc import Callable # noqa: TCH003
|
|
10
12
|
from typing import Any
|
|
13
|
+
if sys.version_info >= (3, 11):
|
|
14
|
+
from typing import Self
|
|
15
|
+
else:
|
|
16
|
+
from typing_extensions import Self
|
|
11
17
|
|
|
12
|
-
from .lib import c_uint128,
|
|
18
|
+
from .lib import c_uint128, tbclient, validate_uint
|
|
19
|
+
|
|
20
|
+
# Use slots=True if the version of Python is new enough (3.10+) to support it.
|
|
21
|
+
if sys.version_info >= (3, 10):
|
|
22
|
+
# mypy: ignore assignment (3.10+) and unused-ignore (pre 3.10)
|
|
23
|
+
dataclass = dataclass(slots=True) # type: ignore[assignment, unused-ignore]
|
|
13
24
|
|
|
14
25
|
|
|
15
26
|
class Operation(enum.IntEnum):
|
|
@@ -236,13 +247,13 @@ class Transfer:
|
|
|
236
247
|
@dataclass
|
|
237
248
|
class CreateAccountsResult:
|
|
238
249
|
index: int = 0
|
|
239
|
-
result: CreateAccountResult =
|
|
250
|
+
result: CreateAccountResult = CreateAccountResult.OK
|
|
240
251
|
|
|
241
252
|
|
|
242
253
|
@dataclass
|
|
243
254
|
class CreateTransfersResult:
|
|
244
255
|
index: int = 0
|
|
245
|
-
result: CreateTransferResult =
|
|
256
|
+
result: CreateTransferResult = CreateTransferResult.OK
|
|
246
257
|
|
|
247
258
|
|
|
248
259
|
@dataclass
|
|
@@ -282,7 +293,7 @@ class QueryFilter:
|
|
|
282
293
|
|
|
283
294
|
class CPacket(ctypes.Structure):
|
|
284
295
|
@classmethod
|
|
285
|
-
def from_param(cls, obj):
|
|
296
|
+
def from_param(cls, obj: Any) -> Self:
|
|
286
297
|
validate_uint(bits=32, name="data_size", number=obj.data_size)
|
|
287
298
|
validate_uint(bits=16, name="user_tag", number=obj.user_tag)
|
|
288
299
|
validate_uint(bits=8, name="operation", number=obj.operation)
|
|
@@ -309,7 +320,7 @@ CPacket._fields_ = [ # noqa: SLF001
|
|
|
309
320
|
|
|
310
321
|
class CClient(ctypes.Structure):
|
|
311
322
|
@classmethod
|
|
312
|
-
def from_param(cls, obj):
|
|
323
|
+
def from_param(cls, obj: Any) -> Self:
|
|
313
324
|
return cls(
|
|
314
325
|
opaque=obj.opaque,
|
|
315
326
|
)
|
|
@@ -321,7 +332,7 @@ CClient._fields_ = [ # noqa: SLF001
|
|
|
321
332
|
|
|
322
333
|
class CAccount(ctypes.Structure):
|
|
323
334
|
@classmethod
|
|
324
|
-
def from_param(cls, obj):
|
|
335
|
+
def from_param(cls, obj: Any) -> Self:
|
|
325
336
|
validate_uint(bits=128, name="id", number=obj.id)
|
|
326
337
|
validate_uint(bits=128, name="debits_pending", number=obj.debits_pending)
|
|
327
338
|
validate_uint(bits=128, name="debits_posted", number=obj.debits_posted)
|
|
@@ -349,7 +360,7 @@ class CAccount(ctypes.Structure):
|
|
|
349
360
|
)
|
|
350
361
|
|
|
351
362
|
|
|
352
|
-
def to_python(self):
|
|
363
|
+
def to_python(self) -> Account:
|
|
353
364
|
return Account(
|
|
354
365
|
id=self.id.to_python(),
|
|
355
366
|
debits_pending=self.debits_pending.to_python(),
|
|
@@ -384,7 +395,7 @@ CAccount._fields_ = [ # noqa: SLF001
|
|
|
384
395
|
|
|
385
396
|
class CTransfer(ctypes.Structure):
|
|
386
397
|
@classmethod
|
|
387
|
-
def from_param(cls, obj):
|
|
398
|
+
def from_param(cls, obj: Any) -> Self:
|
|
388
399
|
validate_uint(bits=128, name="id", number=obj.id)
|
|
389
400
|
validate_uint(bits=128, name="debit_account_id", number=obj.debit_account_id)
|
|
390
401
|
validate_uint(bits=128, name="credit_account_id", number=obj.credit_account_id)
|
|
@@ -414,7 +425,7 @@ class CTransfer(ctypes.Structure):
|
|
|
414
425
|
)
|
|
415
426
|
|
|
416
427
|
|
|
417
|
-
def to_python(self):
|
|
428
|
+
def to_python(self) -> Transfer:
|
|
418
429
|
return Transfer(
|
|
419
430
|
id=self.id.to_python(),
|
|
420
431
|
debit_account_id=self.debit_account_id.to_python(),
|
|
@@ -450,7 +461,7 @@ CTransfer._fields_ = [ # noqa: SLF001
|
|
|
450
461
|
|
|
451
462
|
class CCreateAccountsResult(ctypes.Structure):
|
|
452
463
|
@classmethod
|
|
453
|
-
def from_param(cls, obj):
|
|
464
|
+
def from_param(cls, obj: Any) -> Self:
|
|
454
465
|
validate_uint(bits=32, name="index", number=obj.index)
|
|
455
466
|
return cls(
|
|
456
467
|
index=obj.index,
|
|
@@ -458,7 +469,7 @@ class CCreateAccountsResult(ctypes.Structure):
|
|
|
458
469
|
)
|
|
459
470
|
|
|
460
471
|
|
|
461
|
-
def to_python(self):
|
|
472
|
+
def to_python(self) -> CreateAccountsResult:
|
|
462
473
|
return CreateAccountsResult(
|
|
463
474
|
index=self.index,
|
|
464
475
|
result=CreateAccountResult(self.result),
|
|
@@ -472,7 +483,7 @@ CCreateAccountsResult._fields_ = [ # noqa: SLF001
|
|
|
472
483
|
|
|
473
484
|
class CCreateTransfersResult(ctypes.Structure):
|
|
474
485
|
@classmethod
|
|
475
|
-
def from_param(cls, obj):
|
|
486
|
+
def from_param(cls, obj: Any) -> Self:
|
|
476
487
|
validate_uint(bits=32, name="index", number=obj.index)
|
|
477
488
|
return cls(
|
|
478
489
|
index=obj.index,
|
|
@@ -480,7 +491,7 @@ class CCreateTransfersResult(ctypes.Structure):
|
|
|
480
491
|
)
|
|
481
492
|
|
|
482
493
|
|
|
483
|
-
def to_python(self):
|
|
494
|
+
def to_python(self) -> CreateTransfersResult:
|
|
484
495
|
return CreateTransfersResult(
|
|
485
496
|
index=self.index,
|
|
486
497
|
result=CreateTransferResult(self.result),
|
|
@@ -494,7 +505,7 @@ CCreateTransfersResult._fields_ = [ # noqa: SLF001
|
|
|
494
505
|
|
|
495
506
|
class CAccountFilter(ctypes.Structure):
|
|
496
507
|
@classmethod
|
|
497
|
-
def from_param(cls, obj):
|
|
508
|
+
def from_param(cls, obj: Any) -> Self:
|
|
498
509
|
validate_uint(bits=128, name="account_id", number=obj.account_id)
|
|
499
510
|
validate_uint(bits=128, name="user_data_128", number=obj.user_data_128)
|
|
500
511
|
validate_uint(bits=64, name="user_data_64", number=obj.user_data_64)
|
|
@@ -516,7 +527,7 @@ class CAccountFilter(ctypes.Structure):
|
|
|
516
527
|
)
|
|
517
528
|
|
|
518
529
|
|
|
519
|
-
def to_python(self):
|
|
530
|
+
def to_python(self) -> AccountFilter:
|
|
520
531
|
return AccountFilter(
|
|
521
532
|
account_id=self.account_id.to_python(),
|
|
522
533
|
user_data_128=self.user_data_128.to_python(),
|
|
@@ -545,7 +556,7 @@ CAccountFilter._fields_ = [ # noqa: SLF001
|
|
|
545
556
|
|
|
546
557
|
class CAccountBalance(ctypes.Structure):
|
|
547
558
|
@classmethod
|
|
548
|
-
def from_param(cls, obj):
|
|
559
|
+
def from_param(cls, obj: Any) -> Self:
|
|
549
560
|
validate_uint(bits=128, name="debits_pending", number=obj.debits_pending)
|
|
550
561
|
validate_uint(bits=128, name="debits_posted", number=obj.debits_posted)
|
|
551
562
|
validate_uint(bits=128, name="credits_pending", number=obj.credits_pending)
|
|
@@ -560,7 +571,7 @@ class CAccountBalance(ctypes.Structure):
|
|
|
560
571
|
)
|
|
561
572
|
|
|
562
573
|
|
|
563
|
-
def to_python(self):
|
|
574
|
+
def to_python(self) -> AccountBalance:
|
|
564
575
|
return AccountBalance(
|
|
565
576
|
debits_pending=self.debits_pending.to_python(),
|
|
566
577
|
debits_posted=self.debits_posted.to_python(),
|
|
@@ -581,7 +592,7 @@ CAccountBalance._fields_ = [ # noqa: SLF001
|
|
|
581
592
|
|
|
582
593
|
class CQueryFilter(ctypes.Structure):
|
|
583
594
|
@classmethod
|
|
584
|
-
def from_param(cls, obj):
|
|
595
|
+
def from_param(cls, obj: Any) -> Self:
|
|
585
596
|
validate_uint(bits=128, name="user_data_128", number=obj.user_data_128)
|
|
586
597
|
validate_uint(bits=64, name="user_data_64", number=obj.user_data_64)
|
|
587
598
|
validate_uint(bits=32, name="user_data_32", number=obj.user_data_32)
|
|
@@ -603,7 +614,7 @@ class CQueryFilter(ctypes.Structure):
|
|
|
603
614
|
)
|
|
604
615
|
|
|
605
616
|
|
|
606
|
-
def to_python(self):
|
|
617
|
+
def to_python(self) -> QueryFilter:
|
|
607
618
|
return QueryFilter(
|
|
608
619
|
user_data_128=self.user_data_128.to_python(),
|
|
609
620
|
user_data_64=self.user_data_64,
|
|
@@ -678,13 +689,13 @@ tb_client_submit.argtypes = [ctypes.POINTER(CClient), ctypes.POINTER(CPacket)]
|
|
|
678
689
|
tb_client_register_log_callback = tbclient.tb_client_register_log_callback
|
|
679
690
|
tb_client_register_log_callback.restype = RegisterLogCallbackStatus
|
|
680
691
|
# Need to pass in None to clear - ctypes will error if argtypes is set.
|
|
681
|
-
#tb_client_register_log_callback.argtypes = [LogHandler, ctypes.c_bool]
|
|
692
|
+
# tb_client_register_log_callback.argtypes = [LogHandler, ctypes.c_bool]
|
|
682
693
|
|
|
683
694
|
|
|
684
695
|
class AsyncStateMachineMixin:
|
|
685
696
|
_submit: Callable[[Operation, Any, Any, Any], Any]
|
|
686
697
|
async def create_accounts(self, accounts: list[Account]) -> list[CreateAccountsResult]:
|
|
687
|
-
return await self._submit(
|
|
698
|
+
return await self._submit( # type: ignore[no-any-return]
|
|
688
699
|
Operation.CREATE_ACCOUNTS,
|
|
689
700
|
accounts,
|
|
690
701
|
CAccount,
|
|
@@ -692,7 +703,7 @@ class AsyncStateMachineMixin:
|
|
|
692
703
|
)
|
|
693
704
|
|
|
694
705
|
async def create_transfers(self, transfers: list[Transfer]) -> list[CreateTransfersResult]:
|
|
695
|
-
return await self._submit(
|
|
706
|
+
return await self._submit( # type: ignore[no-any-return]
|
|
696
707
|
Operation.CREATE_TRANSFERS,
|
|
697
708
|
transfers,
|
|
698
709
|
CTransfer,
|
|
@@ -700,7 +711,7 @@ class AsyncStateMachineMixin:
|
|
|
700
711
|
)
|
|
701
712
|
|
|
702
713
|
async def lookup_accounts(self, accounts: list[int]) -> list[Account]:
|
|
703
|
-
return await self._submit(
|
|
714
|
+
return await self._submit( # type: ignore[no-any-return]
|
|
704
715
|
Operation.LOOKUP_ACCOUNTS,
|
|
705
716
|
accounts,
|
|
706
717
|
c_uint128,
|
|
@@ -708,7 +719,7 @@ class AsyncStateMachineMixin:
|
|
|
708
719
|
)
|
|
709
720
|
|
|
710
721
|
async def lookup_transfers(self, transfers: list[int]) -> list[Transfer]:
|
|
711
|
-
return await self._submit(
|
|
722
|
+
return await self._submit( # type: ignore[no-any-return]
|
|
712
723
|
Operation.LOOKUP_TRANSFERS,
|
|
713
724
|
transfers,
|
|
714
725
|
c_uint128,
|
|
@@ -716,7 +727,7 @@ class AsyncStateMachineMixin:
|
|
|
716
727
|
)
|
|
717
728
|
|
|
718
729
|
async def get_account_transfers(self, filter: AccountFilter) -> list[Transfer]:
|
|
719
|
-
return await self._submit(
|
|
730
|
+
return await self._submit( # type: ignore[no-any-return]
|
|
720
731
|
Operation.GET_ACCOUNT_TRANSFERS,
|
|
721
732
|
[filter],
|
|
722
733
|
CAccountFilter,
|
|
@@ -724,7 +735,7 @@ class AsyncStateMachineMixin:
|
|
|
724
735
|
)
|
|
725
736
|
|
|
726
737
|
async def get_account_balances(self, filter: AccountFilter) -> list[AccountBalance]:
|
|
727
|
-
return await self._submit(
|
|
738
|
+
return await self._submit( # type: ignore[no-any-return]
|
|
728
739
|
Operation.GET_ACCOUNT_BALANCES,
|
|
729
740
|
[filter],
|
|
730
741
|
CAccountFilter,
|
|
@@ -732,7 +743,7 @@ class AsyncStateMachineMixin:
|
|
|
732
743
|
)
|
|
733
744
|
|
|
734
745
|
async def query_accounts(self, query_filter: QueryFilter) -> list[Account]:
|
|
735
|
-
return await self._submit(
|
|
746
|
+
return await self._submit( # type: ignore[no-any-return]
|
|
736
747
|
Operation.QUERY_ACCOUNTS,
|
|
737
748
|
[query_filter],
|
|
738
749
|
CQueryFilter,
|
|
@@ -740,7 +751,7 @@ class AsyncStateMachineMixin:
|
|
|
740
751
|
)
|
|
741
752
|
|
|
742
753
|
async def query_transfers(self, query_filter: QueryFilter) -> list[Transfer]:
|
|
743
|
-
return await self._submit(
|
|
754
|
+
return await self._submit( # type: ignore[no-any-return]
|
|
744
755
|
Operation.QUERY_TRANSFERS,
|
|
745
756
|
[query_filter],
|
|
746
757
|
CQueryFilter,
|
|
@@ -752,7 +763,7 @@ class AsyncStateMachineMixin:
|
|
|
752
763
|
class StateMachineMixin:
|
|
753
764
|
_submit: Callable[[Operation, Any, Any, Any], Any]
|
|
754
765
|
def create_accounts(self, accounts: list[Account]) -> list[CreateAccountsResult]:
|
|
755
|
-
return self._submit(
|
|
766
|
+
return self._submit( # type: ignore[no-any-return]
|
|
756
767
|
Operation.CREATE_ACCOUNTS,
|
|
757
768
|
accounts,
|
|
758
769
|
CAccount,
|
|
@@ -760,7 +771,7 @@ class StateMachineMixin:
|
|
|
760
771
|
)
|
|
761
772
|
|
|
762
773
|
def create_transfers(self, transfers: list[Transfer]) -> list[CreateTransfersResult]:
|
|
763
|
-
return self._submit(
|
|
774
|
+
return self._submit( # type: ignore[no-any-return]
|
|
764
775
|
Operation.CREATE_TRANSFERS,
|
|
765
776
|
transfers,
|
|
766
777
|
CTransfer,
|
|
@@ -768,7 +779,7 @@ class StateMachineMixin:
|
|
|
768
779
|
)
|
|
769
780
|
|
|
770
781
|
def lookup_accounts(self, accounts: list[int]) -> list[Account]:
|
|
771
|
-
return self._submit(
|
|
782
|
+
return self._submit( # type: ignore[no-any-return]
|
|
772
783
|
Operation.LOOKUP_ACCOUNTS,
|
|
773
784
|
accounts,
|
|
774
785
|
c_uint128,
|
|
@@ -776,7 +787,7 @@ class StateMachineMixin:
|
|
|
776
787
|
)
|
|
777
788
|
|
|
778
789
|
def lookup_transfers(self, transfers: list[int]) -> list[Transfer]:
|
|
779
|
-
return self._submit(
|
|
790
|
+
return self._submit( # type: ignore[no-any-return]
|
|
780
791
|
Operation.LOOKUP_TRANSFERS,
|
|
781
792
|
transfers,
|
|
782
793
|
c_uint128,
|
|
@@ -784,7 +795,7 @@ class StateMachineMixin:
|
|
|
784
795
|
)
|
|
785
796
|
|
|
786
797
|
def get_account_transfers(self, filter: AccountFilter) -> list[Transfer]:
|
|
787
|
-
return self._submit(
|
|
798
|
+
return self._submit( # type: ignore[no-any-return]
|
|
788
799
|
Operation.GET_ACCOUNT_TRANSFERS,
|
|
789
800
|
[filter],
|
|
790
801
|
CAccountFilter,
|
|
@@ -792,7 +803,7 @@ class StateMachineMixin:
|
|
|
792
803
|
)
|
|
793
804
|
|
|
794
805
|
def get_account_balances(self, filter: AccountFilter) -> list[AccountBalance]:
|
|
795
|
-
return self._submit(
|
|
806
|
+
return self._submit( # type: ignore[no-any-return]
|
|
796
807
|
Operation.GET_ACCOUNT_BALANCES,
|
|
797
808
|
[filter],
|
|
798
809
|
CAccountFilter,
|
|
@@ -800,7 +811,7 @@ class StateMachineMixin:
|
|
|
800
811
|
)
|
|
801
812
|
|
|
802
813
|
def query_accounts(self, query_filter: QueryFilter) -> list[Account]:
|
|
803
|
-
return self._submit(
|
|
814
|
+
return self._submit( # type: ignore[no-any-return]
|
|
804
815
|
Operation.QUERY_ACCOUNTS,
|
|
805
816
|
[query_filter],
|
|
806
817
|
CQueryFilter,
|
|
@@ -808,7 +819,7 @@ class StateMachineMixin:
|
|
|
808
819
|
)
|
|
809
820
|
|
|
810
821
|
def query_transfers(self, query_filter: QueryFilter) -> list[Transfer]:
|
|
811
|
-
return self._submit(
|
|
822
|
+
return self._submit( # type: ignore[no-any-return]
|
|
812
823
|
Operation.QUERY_TRANSFERS,
|
|
813
824
|
[query_filter],
|
|
814
825
|
CQueryFilter,
|
tigerbeetle/client.py
CHANGED
|
@@ -4,11 +4,16 @@ import asyncio
|
|
|
4
4
|
import ctypes
|
|
5
5
|
import logging
|
|
6
6
|
import os
|
|
7
|
+
import sys
|
|
7
8
|
import threading
|
|
8
9
|
import time
|
|
9
10
|
from collections.abc import Callable # noqa: TCH003
|
|
10
11
|
from dataclasses import dataclass
|
|
11
12
|
from typing import Any
|
|
13
|
+
if sys.version_info >= (3, 11):
|
|
14
|
+
from typing import Self
|
|
15
|
+
else:
|
|
16
|
+
from typing_extensions import Self
|
|
12
17
|
|
|
13
18
|
from . import bindings
|
|
14
19
|
from .lib import tb_assert, c_uint128
|
|
@@ -17,11 +22,11 @@ logger = logging.getLogger("tigerbeetle")
|
|
|
17
22
|
|
|
18
23
|
|
|
19
24
|
class AtomicInteger:
|
|
20
|
-
def __init__(self, value=0):
|
|
25
|
+
def __init__(self, value: int = 0) -> None:
|
|
21
26
|
self._value = value
|
|
22
27
|
self._lock = threading.Lock()
|
|
23
28
|
|
|
24
|
-
def increment(self):
|
|
29
|
+
def increment(self) -> int:
|
|
25
30
|
with self._lock:
|
|
26
31
|
self._value += 1
|
|
27
32
|
return self._value
|
|
@@ -45,30 +50,43 @@ class InflightPacket:
|
|
|
45
50
|
operation: bindings.Operation
|
|
46
51
|
c_event_type: Any
|
|
47
52
|
c_result_type: Any
|
|
48
|
-
on_completion: Callable | None
|
|
53
|
+
on_completion: Callable[[Self], None] | None
|
|
49
54
|
on_completion_context: CompletionContextSync | CompletionContextAsync | None
|
|
50
55
|
|
|
56
|
+
class _IDGenerator:
|
|
57
|
+
"""
|
|
58
|
+
Generator for Universally Unique and Sortable Identifiers as a 128-bit integers, based on ULIDs.
|
|
59
|
+
|
|
60
|
+
Keeps a monotonically increasing millisecond timestamp between calls to `.generate()`.
|
|
61
|
+
"""
|
|
62
|
+
def __init__(self) -> None:
|
|
63
|
+
self._time_ms_last = time.time_ns() // (1000 * 1000)
|
|
64
|
+
|
|
65
|
+
def generate(self) -> int:
|
|
66
|
+
time_ms = time.time_ns() // (1000 * 1000)
|
|
67
|
+
|
|
68
|
+
# Ensure time_ms monotonically increases.
|
|
69
|
+
if time_ms <= self._time_ms_last:
|
|
70
|
+
time_ms = self._time_ms_last
|
|
71
|
+
else:
|
|
72
|
+
self._time_ms_last = time_ms
|
|
73
|
+
|
|
74
|
+
randomness = os.urandom(10)
|
|
75
|
+
|
|
76
|
+
return int.from_bytes(
|
|
77
|
+
time_ms.to_bytes(6, "big") + randomness,
|
|
78
|
+
"big",
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
# Module-level singleton instance.
|
|
82
|
+
_id_generator = _IDGenerator()
|
|
51
83
|
|
|
52
84
|
|
|
53
85
|
def id() -> int:
|
|
54
86
|
"""
|
|
55
87
|
Generates a Universally Unique and Sortable Identifier as a 128-bit integer. Based on ULIDs.
|
|
56
88
|
"""
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
# Ensure time_ms monotonically increases.
|
|
60
|
-
time_ms_last = getattr(id, "_time_ms_last", 0)
|
|
61
|
-
if time_ms <= time_ms_last:
|
|
62
|
-
time_ms = time_ms_last
|
|
63
|
-
else:
|
|
64
|
-
id._time_ms_last = time_ms
|
|
65
|
-
|
|
66
|
-
randomness = os.urandom(10)
|
|
67
|
-
|
|
68
|
-
return int.from_bytes(
|
|
69
|
-
time_ms.to_bytes(6, "big") + randomness,
|
|
70
|
-
"big",
|
|
71
|
-
)
|
|
89
|
+
return _id_generator.generate()
|
|
72
90
|
|
|
73
91
|
|
|
74
92
|
amount_max = (2 ** 128) - 1
|
|
@@ -137,15 +155,9 @@ class Client:
|
|
|
137
155
|
c_event_type=c_event_type,
|
|
138
156
|
c_result_type=c_result_type)
|
|
139
157
|
|
|
140
|
-
def close(self):
|
|
141
|
-
bindings.tb_client_deinit(ctypes.byref(self._client))
|
|
142
|
-
tb_assert(self._client is not None)
|
|
143
|
-
tb_assert(len(self._inflight_packets) == 0)
|
|
144
|
-
del Client._clients[self._client_key]
|
|
145
|
-
|
|
146
158
|
@staticmethod
|
|
147
|
-
@bindings.OnCompletion
|
|
148
|
-
def _c_on_completion(completion_ctx, packet, timestamp, bytes_ptr, len_):
|
|
159
|
+
@bindings.OnCompletion # type: ignore[misc]
|
|
160
|
+
def _c_on_completion(completion_ctx: int, packet: Any, timestamp: int, bytes_ptr: Any, len_: int) -> None:
|
|
149
161
|
"""
|
|
150
162
|
Invoked in a separate thread
|
|
151
163
|
"""
|
|
@@ -156,7 +168,9 @@ class Client:
|
|
|
156
168
|
|
|
157
169
|
if packet[0].status != bindings.PacketStatus.OK.value:
|
|
158
170
|
inflight_packet.response = PacketError(repr(bindings.PacketStatus(packet[0].status)))
|
|
159
|
-
|
|
171
|
+
if inflight_packet.on_completion is None:
|
|
172
|
+
# Can't use tb_assert here, as mypy complains later that it might be None.
|
|
173
|
+
raise TypeError("inflight_packet.on_completion not set")
|
|
160
174
|
inflight_packet.on_completion(inflight_packet)
|
|
161
175
|
return
|
|
162
176
|
|
|
@@ -174,22 +188,21 @@ class Client:
|
|
|
174
188
|
|
|
175
189
|
inflight_packet.response = results
|
|
176
190
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
def __enter__(self):
|
|
181
|
-
return self
|
|
191
|
+
if inflight_packet.on_completion is None:
|
|
192
|
+
# Can't use tb_assert here, as mypy complains later that it might be None.
|
|
193
|
+
raise TypeError("inflight_packet.on_completion not set")
|
|
182
194
|
|
|
183
|
-
|
|
184
|
-
self.close()
|
|
195
|
+
inflight_packet.on_completion(inflight_packet)
|
|
185
196
|
|
|
186
197
|
|
|
187
198
|
class ClientSync(Client, bindings.StateMachineMixin):
|
|
188
|
-
def _on_completion(self, inflight_packet):
|
|
199
|
+
def _on_completion(self, inflight_packet: InflightPacket) -> None:
|
|
200
|
+
if not isinstance(inflight_packet.on_completion_context, CompletionContextSync):
|
|
201
|
+
raise TypeError(repr(inflight_packet.on_completion_context))
|
|
189
202
|
inflight_packet.on_completion_context.event.set()
|
|
190
203
|
|
|
191
204
|
def _submit(self, operation: bindings.Operation, operations: list[Any],
|
|
192
|
-
c_event_type: Any, c_result_type: Any):
|
|
205
|
+
c_event_type: Any, c_result_type: Any) -> Any:
|
|
193
206
|
inflight_packet = self._acquire_packet(operation, operations, c_event_type, c_result_type)
|
|
194
207
|
self._inflight_packets[inflight_packet.packet.user_data] = inflight_packet
|
|
195
208
|
|
|
@@ -210,24 +223,41 @@ class ClientSync(Client, bindings.StateMachineMixin):
|
|
|
210
223
|
|
|
211
224
|
return inflight_packet.response
|
|
212
225
|
|
|
226
|
+
def close(self) -> None:
|
|
227
|
+
tb_assert(self._client is not None)
|
|
228
|
+
bindings.tb_client_deinit(ctypes.byref(self._client))
|
|
229
|
+
|
|
230
|
+
tb_assert(len(self._inflight_packets) == 0)
|
|
231
|
+
del Client._clients[self._client_key]
|
|
232
|
+
|
|
233
|
+
def __enter__(self) -> Self:
|
|
234
|
+
return self
|
|
235
|
+
|
|
236
|
+
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
|
|
237
|
+
self.close()
|
|
238
|
+
|
|
213
239
|
|
|
214
240
|
class ClientAsync(Client, bindings.AsyncStateMachineMixin):
|
|
215
|
-
def _on_completion(self, inflight_packet):
|
|
241
|
+
def _on_completion(self, inflight_packet: InflightPacket) -> None:
|
|
216
242
|
"""
|
|
217
243
|
Called by Client._c_on_completion, which itself is called from a different thread. Use
|
|
218
244
|
`call_soon_threadsafe` to return to the thread of the event loop the request was invoked
|
|
219
245
|
from, so _trigger_event() can trigger the async event and allow the client to progress.
|
|
220
246
|
"""
|
|
247
|
+
if not isinstance(inflight_packet.on_completion_context, CompletionContextAsync):
|
|
248
|
+
raise TypeError(repr(inflight_packet.on_completion_context))
|
|
221
249
|
inflight_packet.on_completion_context.loop.call_soon_threadsafe(
|
|
222
250
|
self._trigger_event,
|
|
223
251
|
inflight_packet
|
|
224
252
|
)
|
|
225
253
|
|
|
226
|
-
def _trigger_event(self, inflight_packet):
|
|
254
|
+
def _trigger_event(self, inflight_packet:InflightPacket) -> None:
|
|
255
|
+
if not isinstance(inflight_packet.on_completion_context, CompletionContextAsync):
|
|
256
|
+
raise TypeError(repr(inflight_packet.on_completion_context))
|
|
227
257
|
inflight_packet.on_completion_context.event.set()
|
|
228
258
|
|
|
229
259
|
async def _submit(self, operation: bindings.Operation, operations: Any,
|
|
230
|
-
c_event_type: Any, c_result_type: Any):
|
|
260
|
+
c_event_type: Any, c_result_type: Any) -> Any:
|
|
231
261
|
inflight_packet = self._acquire_packet(operation, operations, c_event_type, c_result_type)
|
|
232
262
|
self._inflight_packets[inflight_packet.packet.user_data] = inflight_packet
|
|
233
263
|
|
|
@@ -251,9 +281,35 @@ class ClientAsync(Client, bindings.AsyncStateMachineMixin):
|
|
|
251
281
|
|
|
252
282
|
return inflight_packet.response
|
|
253
283
|
|
|
284
|
+
async def close(self) -> None:
|
|
285
|
+
tb_assert(self._client is not None)
|
|
286
|
+
bindings.tb_client_deinit(ctypes.byref(self._client))
|
|
287
|
+
|
|
288
|
+
# tb_client_deinit internally clears any inflight requests, and calls their callbacks, so
|
|
289
|
+
# the client needs to stick around until that's done.
|
|
290
|
+
#
|
|
291
|
+
# This isn't a problem for ClientSync, but ClientAsync invokes the callbacks using
|
|
292
|
+
# call_soon_threadsafe(), so wait for the event to fire on all pending packets.
|
|
293
|
+
all_events = []
|
|
294
|
+
for packet in self._inflight_packets.values():
|
|
295
|
+
if not isinstance(packet.on_completion_context, CompletionContextAsync):
|
|
296
|
+
raise AssertionError()
|
|
297
|
+
all_events.append(packet.on_completion_context.event.wait())
|
|
298
|
+
|
|
299
|
+
await asyncio.gather(*all_events)
|
|
300
|
+
|
|
301
|
+
tb_assert(len(self._inflight_packets) == 0)
|
|
302
|
+
del Client._clients[self._client_key]
|
|
254
303
|
|
|
255
|
-
|
|
256
|
-
|
|
304
|
+
async def __aenter__(self) -> Self:
|
|
305
|
+
return self
|
|
306
|
+
|
|
307
|
+
async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
|
|
308
|
+
await self.close()
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
@bindings.LogHandler # type: ignore[misc]
|
|
312
|
+
def log_handler(level_zig: bindings.LogLevel, message_ptr: Any, message_len: int) -> None:
|
|
257
313
|
level_python = {
|
|
258
314
|
bindings.LogLevel.ERR: logging.ERROR,
|
|
259
315
|
bindings.LogLevel.WARN: logging.WARNING,
|
|
@@ -265,10 +321,15 @@ def log_handler(level_zig, message_ptr, message_len):
|
|
|
265
321
|
tb_assert(bindings.tb_client_register_log_callback(log_handler, True) ==
|
|
266
322
|
bindings.RegisterLogCallbackStatus.SUCCESS)
|
|
267
323
|
|
|
268
|
-
|
|
324
|
+
|
|
325
|
+
def configure_logging(
|
|
326
|
+
*,
|
|
327
|
+
debug: bool,
|
|
328
|
+
handler: Callable[[bindings.LogLevel, Any, int], None] = log_handler,
|
|
329
|
+
) -> None:
|
|
269
330
|
# First disable the existing log handler, before enabling the new one.
|
|
270
331
|
tb_assert(bindings.tb_client_register_log_callback(None, debug) ==
|
|
271
332
|
bindings.RegisterLogCallbackStatus.SUCCESS)
|
|
272
333
|
|
|
273
|
-
tb_assert(bindings.tb_client_register_log_callback(
|
|
334
|
+
tb_assert(bindings.tb_client_register_log_callback(handler, debug) ==
|
|
274
335
|
bindings.RegisterLogCallbackStatus.SUCCESS)
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
tigerbeetle/lib.py
CHANGED
|
@@ -1,17 +1,23 @@
|
|
|
1
1
|
import ctypes
|
|
2
|
-
import dataclasses
|
|
3
2
|
import platform
|
|
3
|
+
import sys
|
|
4
4
|
from pathlib import Path
|
|
5
|
+
from typing import Any
|
|
6
|
+
if sys.version_info >= (3, 11):
|
|
7
|
+
from typing import Self
|
|
8
|
+
else:
|
|
9
|
+
from typing_extensions import Self
|
|
5
10
|
|
|
6
11
|
|
|
7
12
|
class NativeError(Exception):
|
|
8
13
|
pass
|
|
9
14
|
|
|
15
|
+
|
|
10
16
|
class IntegerOverflowError(ValueError):
|
|
11
17
|
pass
|
|
12
18
|
|
|
13
19
|
|
|
14
|
-
def _load_tbclient():
|
|
20
|
+
def _load_tbclient() -> ctypes.CDLL:
|
|
15
21
|
prefix = ""
|
|
16
22
|
arch = ""
|
|
17
23
|
system = ""
|
|
@@ -50,39 +56,35 @@ def _load_tbclient():
|
|
|
50
56
|
|
|
51
57
|
source_path = Path(__file__)
|
|
52
58
|
source_dir = source_path.parent
|
|
53
|
-
library_path =
|
|
59
|
+
library_path = (
|
|
60
|
+
source_dir / "lib" / f"{arch}-{system}{linux_libc}" / f"{prefix}tb_client{suffix}"
|
|
61
|
+
)
|
|
54
62
|
return ctypes.CDLL(str(library_path))
|
|
55
63
|
|
|
56
64
|
|
|
57
|
-
def validate_uint(*, bits: int, name: str, number: int):
|
|
65
|
+
def validate_uint(*, bits: int, name: str, number: int) -> None:
|
|
58
66
|
if number > 2**bits - 1:
|
|
59
67
|
raise IntegerOverflowError(f"{name}=={number} is too large to fit in {bits} bits")
|
|
60
68
|
if number < 0:
|
|
61
69
|
raise IntegerOverflowError(f"{name}=={number} cannot be negative")
|
|
62
70
|
|
|
63
71
|
|
|
64
|
-
class c_uint128(ctypes.Structure):
|
|
65
|
-
_fields_ = [("_low", ctypes.c_uint64), ("_high", ctypes.c_uint64)]
|
|
72
|
+
class c_uint128(ctypes.Structure): # noqa: N801
|
|
73
|
+
_fields_ = [("_low", ctypes.c_uint64), ("_high", ctypes.c_uint64)] # noqa: RUF012
|
|
66
74
|
|
|
67
75
|
@classmethod
|
|
68
|
-
def from_param(cls, obj):
|
|
69
|
-
return cls(_high=obj >> 64, _low=obj &
|
|
70
|
-
|
|
71
|
-
def to_python(self):
|
|
72
|
-
return self._high << 64 | self._low
|
|
76
|
+
def from_param(cls, obj: int) -> Self:
|
|
77
|
+
return cls(_high=obj >> 64, _low=obj & 0xFFFFFFFFFFFFFFFF)
|
|
73
78
|
|
|
79
|
+
def to_python(self) -> int:
|
|
80
|
+
return int(self._high << 64 | self._low)
|
|
74
81
|
|
|
75
|
-
|
|
76
|
-
try:
|
|
77
|
-
dataclass = dataclasses.dataclass(slots=True)
|
|
78
|
-
except TypeError:
|
|
79
|
-
dataclass = dataclasses.dataclass()
|
|
80
|
-
|
|
81
|
-
def tb_assert(value):
|
|
82
|
+
def tb_assert(value: Any) -> None:
|
|
82
83
|
"""
|
|
83
84
|
Python's built-in assert can be silently disabled if Python is run with -O.
|
|
84
85
|
"""
|
|
85
86
|
if not value:
|
|
86
87
|
raise AssertionError()
|
|
87
88
|
|
|
89
|
+
|
|
88
90
|
tbclient = _load_tbclient()
|
tigerbeetle/py.typed
ADDED
|
File without changes
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tigerbeetle
|
|
3
|
-
Version: 0.16.
|
|
3
|
+
Version: 0.16.57
|
|
4
4
|
Summary: The TigerBeetle client for Python.
|
|
5
5
|
Project-URL: Homepage, https://github.com/tigerbeetle/tigerbeetle
|
|
6
6
|
Project-URL: Issues, https://github.com/tigerbeetle/tigerbeetle/issues
|
|
@@ -91,6 +91,11 @@ environment variable and defaults to port `3000`.
|
|
|
91
91
|
with tb.ClientSync(cluster_id=0, replica_addresses=os.getenv("TB_ADDRESS", "3000")) as client:
|
|
92
92
|
# Use the client.
|
|
93
93
|
pass
|
|
94
|
+
|
|
95
|
+
# Alternatively:
|
|
96
|
+
async with tb.ClientAsync(cluster_id=0, replica_addresses=os.getenv("TB_ADDRESS", "3000")) as client:
|
|
97
|
+
# Use the client, async!
|
|
98
|
+
pass
|
|
94
99
|
```
|
|
95
100
|
|
|
96
101
|
The following are valid addresses:
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
tigerbeetle/__init__.py,sha256=vrZ9rqFxHpXtACa9qDhwKwSkhv3CGhAvg7vF3Ges8c0,957
|
|
2
|
+
tigerbeetle/bindings.py,sha256=NSND7UuFpnd4TRBDA9aDJLRS115QNi3m0dVksekVlZg,28155
|
|
3
|
+
tigerbeetle/client.py,sha256=iLvRvUk-_YNip2ZIzPV3C5BbqjvlYa4vHDXbQpAoDnU,12048
|
|
4
|
+
tigerbeetle/lib.py,sha256=1Qln-Z4ZoCHJJ43sXlJJEs7-ZY5aZJjNNu7eISooAK8,2432
|
|
5
|
+
tigerbeetle/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
tigerbeetle/lib/aarch64-linux-gnu.2.27/libtb_client.so,sha256=9Ni-kr77uva3dIxwq6g89rxdbC0LuiDPbXYu7d8ZDrQ,693336
|
|
7
|
+
tigerbeetle/lib/aarch64-linux-musl/libtb_client.so,sha256=o3VknLRJjkZpBWlPO33KBclshNtrX7teHgvixKUiD1A,691616
|
|
8
|
+
tigerbeetle/lib/aarch64-macos/libtb_client.dylib,sha256=zqXLlUX1V2yJdG_8YbdVux7LsIFykZuuf2hjdgrsMBE,700224
|
|
9
|
+
tigerbeetle/lib/x86_64-linux-gnu.2.27/libtb_client.so,sha256=9WKWUV7QqmntjceqIu8-iD90zONv6Y2HW8riHYwyFyE,816808
|
|
10
|
+
tigerbeetle/lib/x86_64-linux-musl/libtb_client.so,sha256=6d7__mj4Vfc_WrHG6HuCBeCrE8g__awoQnlQvenG3b4,815256
|
|
11
|
+
tigerbeetle/lib/x86_64-macos/libtb_client.dylib,sha256=3MX0n9AdFfq3pjXgahavPiob6DEfEGA8-xz_pOoOYB4,792590
|
|
12
|
+
tigerbeetle/lib/x86_64-windows/tb_client.dll,sha256=R53xX0nn3hTgVoeR8jQLL8w7M5d00jbZf5IyaVoD_ek,664064
|
|
13
|
+
tigerbeetle-0.16.57.dist-info/METADATA,sha256=1EUIAa2ekf9x41mGf9dLHGaRIsu5ZW0b3ILqnK4Ac-E,23426
|
|
14
|
+
tigerbeetle-0.16.57.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
15
|
+
tigerbeetle-0.16.57.dist-info/RECORD,,
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
tigerbeetle/__init__.py,sha256=3cmov0HPkByJw1znLKoTqIuZUEeEQMEg1fflWY3ty9A,168
|
|
2
|
-
tigerbeetle/bindings.py,sha256=syIRjAQQfoE15iGK6kxmJLToRdU2KWDY0nnu6u-t6xk,26978
|
|
3
|
-
tigerbeetle/client.py,sha256=oGg1vAFqI8UZ5awI3sshZfxUdBsroUKwgp2rUa75lFc,9255
|
|
4
|
-
tigerbeetle/lib.py,sha256=JKreDxdw_YejtH7F135_m6I6mNBSwjokaI6QicKm7cc,2425
|
|
5
|
-
tigerbeetle/lib/aarch64-linux-gnu.2.27/libtb_client.so,sha256=bEgYJ674Nu-naxUASpdvpTVKAetinNZ9j4jdIVIAs4U,692104
|
|
6
|
-
tigerbeetle/lib/aarch64-linux-musl/libtb_client.so,sha256=g64c-V-NJ1WQA_gfDpBlaSvJ0IUOsAemvp-SVhZ_cCk,690384
|
|
7
|
-
tigerbeetle/lib/aarch64-macos/libtb_client.dylib,sha256=AMGOyDarLDXbIfKq5hQu6tCrY-tWBLBSQ3f21lc2P74,700224
|
|
8
|
-
tigerbeetle/lib/x86_64-linux-gnu.2.27/libtb_client.so,sha256=ZecvJ8l-cn6-ud9nAQa9r4ATOfFQ7slKYPOw7MwpvnE,815368
|
|
9
|
-
tigerbeetle/lib/x86_64-linux-musl/libtb_client.so,sha256=K1XGJ5n6uw2aW8RIcdhxQcNGcic1zTbkbAwPJiUs6qw,813816
|
|
10
|
-
tigerbeetle/lib/x86_64-macos/libtb_client.dylib,sha256=mFQ3CxmCYfQuLcnHkHmg6DxlewPM5G_TGpRU4fo5Ldg,792558
|
|
11
|
-
tigerbeetle/lib/x86_64-windows/tb_client.dll,sha256=KFrZ8XK2gTlzoCFOMfcRoGk8i3LA3MBiyQLuz2PSBYc,663552
|
|
12
|
-
tigerbeetle-0.16.55.dist-info/METADATA,sha256=kuhGPnre8bPfIbC9EQbWz6OLSU_3pOYwwIarE3ccMbY,23268
|
|
13
|
-
tigerbeetle-0.16.55.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
14
|
-
tigerbeetle-0.16.55.dist-info/RECORD,,
|
|
File without changes
|