tigerbeetle 0.16.16__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.
@@ -0,0 +1,798 @@
1
+ Metadata-Version: 2.3
2
+ Name: tigerbeetle
3
+ Version: 0.16.16
4
+ Summary: The TigerBeetle client for Python.
5
+ Project-URL: Homepage, https://github.com/tigerbeetle/tigerbeetle
6
+ Project-URL: Issues, https://github.com/tigerbeetle/tigerbeetle/issues
7
+ Classifier: Development Status :: 5 - Production/Stable
8
+ Classifier: License :: OSI Approved :: Apache Software License
9
+ Classifier: Operating System :: MacOS :: MacOS X
10
+ Classifier: Operating System :: Microsoft :: Windows
11
+ Classifier: Operating System :: POSIX :: Linux
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Topic :: Database :: Front-Ends
14
+ Requires-Python: >=3.7
15
+ Description-Content-Type: text/markdown
16
+
17
+ ---
18
+ title: Python
19
+ ---
20
+
21
+ <!-- This file is generated by [/src/scripts/client_readmes.zig](/src/scripts/client_readmes.zig). -->
22
+ # tigerbeetle
23
+
24
+ The TigerBeetle client for Python.
25
+
26
+ ## Prerequisites
27
+
28
+ Linux >= 5.6 is the only production environment we
29
+ support. But for ease of development we also support macOS and Windows.
30
+ * Python (or PyPy, etc) >= `3.7`
31
+
32
+ ## Setup
33
+
34
+ First, create a directory for your project and `cd` into the directory.
35
+
36
+ Then, install the TigerBeetle client:
37
+
38
+ ```console
39
+ pip install tigerbeetle
40
+ ```
41
+
42
+ Now, create `main.py` and copy this into it:
43
+
44
+ ```python
45
+ import os
46
+
47
+ import tigerbeetle as tb
48
+
49
+ print("Import OK!")
50
+ ```
51
+
52
+ Finally, build and run:
53
+
54
+ ```console
55
+ python3 main.py
56
+ ```
57
+
58
+ Now that all prerequisites and dependencies are correctly set
59
+ up, let's dig into using TigerBeetle.
60
+
61
+ ## Sample projects
62
+
63
+ This document is primarily a reference guide to
64
+ the client. Below are various sample projects demonstrating
65
+ features of TigerBeetle.
66
+
67
+ * [Basic](/src/clients/python/samples/basic/): Create two accounts and transfer an amount between them.
68
+ * [Two-Phase Transfer](/src/clients/python/samples/two-phase/): Create two accounts and start a pending transfer between
69
+ them, then post the transfer.
70
+ * [Many Two-Phase Transfers](/src/clients/python/samples/two-phase-many/): Create two accounts and start a number of pending transfer
71
+ between them, posting and voiding alternating transfers.
72
+ ## Creating a Client
73
+
74
+ A client is created with a cluster ID and replica
75
+ addresses for all replicas in the cluster. The cluster
76
+ ID and replica addresses are both chosen by the system that
77
+ starts the TigerBeetle cluster.
78
+
79
+ Clients are thread-safe and a single instance should be shared
80
+ between multiple concurrent tasks.
81
+
82
+ Multiple clients are useful when connecting to more than
83
+ one TigerBeetle cluster.
84
+
85
+ In this example the cluster ID is `0` and there is one
86
+ replica. The address is read from the `TB_ADDRESS`
87
+ environment variable and defaults to port `3000`.
88
+
89
+ ```python
90
+ with tb.ClientSync(cluster_id=0, replica_addresses=os.getenv("TB_ADDRESS", "3000")) as client:
91
+ # Use the client.
92
+ pass
93
+ ```
94
+
95
+ The following are valid addresses:
96
+ * `3000` (interpreted as `127.0.0.1:3000`)
97
+ * `127.0.0.1:3000` (interpreted as `127.0.0.1:3000`)
98
+ * `127.0.0.1` (interpreted as `127.0.0.1:3001`, `3001` is the default port)
99
+
100
+ ## Creating Accounts
101
+
102
+ See details for account fields in the [Accounts
103
+ reference](https://docs.tigerbeetle.com/reference/account).
104
+
105
+ ```python
106
+ account = tb.Account(
107
+ id=tb.id(), # TigerBeetle time-based ID.
108
+ debits_pending=0,
109
+ debits_posted=0,
110
+ credits_pending=0,
111
+ credits_posted=0,
112
+ user_data_128=0,
113
+ user_data_64=0,
114
+ user_data_32=0,
115
+ ledger=1,
116
+ code=718,
117
+ flags=0,
118
+ timestamp=0,
119
+ )
120
+
121
+ account_errors = client.create_accounts([account])
122
+ # Error handling omitted.
123
+ ```
124
+
125
+ See details for the recommended ID scheme in
126
+ [time-based identifiers](https://docs.tigerbeetle.com/coding/data-modeling#tigerbeetle-time-based-identifiers-recommended).
127
+
128
+ ### Account Flags
129
+
130
+ The account flags value is a bitfield. See details for
131
+ these flags in the [Accounts
132
+ reference](https://docs.tigerbeetle.com/reference/account#flags).
133
+
134
+ To toggle behavior for an account, combine enum values stored in the
135
+ `AccountFlags` object (it's an `enum.IntFlag`) with bitwise-or:
136
+
137
+ * `AccountFlags.linked`
138
+ * `AccountFlags.debits_must_not_exceed_credits`
139
+ * `AccountFlags.credits_must_not_exceed_credits`
140
+ * `AccountFlags.history`
141
+
142
+
143
+ For example, to link two accounts where the first account
144
+ additionally has the `debits_must_not_exceed_credits` constraint:
145
+
146
+ ```python
147
+ account0 = tb.Account(
148
+ id=100,
149
+ debits_pending=0,
150
+ debits_posted=0,
151
+ credits_pending=0,
152
+ credits_posted=0,
153
+ user_data_128=0,
154
+ user_data_64=0,
155
+ user_data_32=0,
156
+ ledger=1,
157
+ code=1,
158
+ timestamp=0,
159
+ flags=tb.AccountFlags.LINKED | tb.AccountFlags.DEBITS_MUST_NOT_EXCEED_CREDITS,
160
+ )
161
+ account1 = tb.Account(
162
+ id=101,
163
+ debits_pending=0,
164
+ debits_posted=0,
165
+ credits_pending=0,
166
+ credits_posted=0,
167
+ user_data_128=0,
168
+ user_data_64=0,
169
+ user_data_32=0,
170
+ ledger=1,
171
+ code=1,
172
+ timestamp=0,
173
+ flags=tb.AccountFlags.HISTORY,
174
+ )
175
+
176
+ account_errors = client.create_accounts([account0, account1])
177
+ # Error handling omitted.
178
+ ```
179
+
180
+ ### Response and Errors
181
+
182
+ The response is an empty array if all accounts were
183
+ created successfully. If the response is non-empty, each
184
+ object in the response array contains error information
185
+ for an account that failed. The error object contains an
186
+ error code and the index of the account in the request
187
+ batch.
188
+
189
+ See all error conditions in the [create_accounts
190
+ reference](https://docs.tigerbeetle.com/reference/requests/create_accounts).
191
+
192
+ ```python
193
+ account0 = tb.Account(
194
+ id=102,
195
+ debits_pending=0,
196
+ debits_posted=0,
197
+ credits_pending=0,
198
+ credits_posted=0,
199
+ user_data_128=0,
200
+ user_data_64=0,
201
+ user_data_32=0,
202
+ ledger=1,
203
+ code=1,
204
+ timestamp=0,
205
+ flags=0,
206
+ )
207
+ account1 = tb.Account(
208
+ id=103,
209
+ debits_pending=0,
210
+ debits_posted=0,
211
+ credits_pending=0,
212
+ credits_posted=0,
213
+ user_data_128=0,
214
+ user_data_64=0,
215
+ user_data_32=0,
216
+ ledger=1,
217
+ code=1,
218
+ timestamp=0,
219
+ flags=0,
220
+ )
221
+ account2 = tb.Account(
222
+ id=104,
223
+ debits_pending=0,
224
+ debits_posted=0,
225
+ credits_pending=0,
226
+ credits_posted=0,
227
+ user_data_128=0,
228
+ user_data_64=0,
229
+ user_data_32=0,
230
+ ledger=1,
231
+ code=1,
232
+ timestamp=0,
233
+ flags=0,
234
+ )
235
+
236
+ account_errors = client.create_accounts([account0, account1, account2])
237
+ for error in account_errors:
238
+ if error.result == tb.CreateAccountResult.EXISTS:
239
+ print(f"Batch account at {error.index} already exists.")
240
+ else:
241
+ print(f"Batch account at ${error.index} failed to create: {error.result}.")
242
+ ```
243
+
244
+ To handle errors you can compare the result code returned
245
+ from `client.create_accounts` with enum values in the
246
+ `CreateAccountResult` object.
247
+
248
+ ## Account Lookup
249
+
250
+ Account lookup is batched, like account creation. Pass
251
+ in all IDs to fetch. The account for each matched ID is returned.
252
+
253
+ If no account matches an ID, no object is returned for
254
+ that account. So the order of accounts in the response is
255
+ not necessarily the same as the order of IDs in the
256
+ request. You can refer to the ID field in the response to
257
+ distinguish accounts.
258
+
259
+ ```python
260
+ accounts = client.lookup_accounts([100, 101])
261
+ ```
262
+
263
+ ## Create Transfers
264
+
265
+ This creates a journal entry between two accounts.
266
+
267
+ See details for transfer fields in the [Transfers
268
+ reference](https://docs.tigerbeetle.com/reference/transfer).
269
+
270
+ ```python
271
+ transfers = [tb.Transfer(
272
+ id=tb.id(), # TigerBeetle time-based ID.
273
+ debit_account_id=102,
274
+ credit_account_id=103,
275
+ amount=10,
276
+ pending_id=0,
277
+ user_data_128=0,
278
+ user_data_64=0,
279
+ user_data_32=0,
280
+ timeout=0,
281
+ ledger=1,
282
+ code=720,
283
+ flags=0,
284
+ timestamp=0,
285
+ )]
286
+
287
+ transfer_errors = client.create_transfers(transfers)
288
+ # Error handling omitted.
289
+ ```
290
+
291
+ See details for the recommended ID scheme in
292
+ [time-based identifiers](https://docs.tigerbeetle.com/coding/data-modeling#tigerbeetle-time-based-identifiers-recommended).
293
+
294
+ ### Response and Errors
295
+
296
+ The response is an empty array if all transfers were created
297
+ successfully. If the response is non-empty, each object in the
298
+ response array contains error information for a transfer that
299
+ failed. The error object contains an error code and the index of the
300
+ transfer in the request batch.
301
+
302
+ See all error conditions in the [create_transfers
303
+ reference](https://docs.tigerbeetle.com/reference/requests/create_transfers).
304
+
305
+ ```python
306
+ batch = [tb.Transfer(
307
+ id=1,
308
+ debit_account_id=102,
309
+ credit_account_id=103,
310
+ amount=10,
311
+ pending_id=0,
312
+ user_data_128=0,
313
+ user_data_64=0,
314
+ user_data_32=0,
315
+ timeout=0,
316
+ ledger=1,
317
+ code=720,
318
+ flags=0,
319
+ timestamp=0,
320
+ ),
321
+ tb.Transfer(
322
+ id=2,
323
+ debit_account_id=102,
324
+ credit_account_id=103,
325
+ amount=10,
326
+ pending_id=0,
327
+ user_data_128=0,
328
+ user_data_64=0,
329
+ user_data_32=0,
330
+ timeout=0,
331
+ ledger=1,
332
+ code=720,
333
+ flags=0,
334
+ timestamp=0,
335
+ ),
336
+ tb.Transfer(
337
+ id=3,
338
+ debit_account_id=102,
339
+ credit_account_id=103,
340
+ amount=10,
341
+ pending_id=0,
342
+ user_data_128=0,
343
+ user_data_64=0,
344
+ user_data_32=0,
345
+ timeout=0,
346
+ ledger=1,
347
+ code=720,
348
+ flags=0,
349
+ timestamp=0,
350
+ )]
351
+
352
+ transfer_errors = client.create_transfers(batch)
353
+ for error in transfer_errors:
354
+ if error.result == tb.CreateTransferResult.EXISTS:
355
+ print(f"Batch transfer at {error.index} already exists.")
356
+ else:
357
+ print(f"Batch transfer at {error.index} failed to create: {error.result}.")
358
+ ```
359
+
360
+ To handle errors you can compare the result code returned
361
+ from `client.create_transfers` with enum values in the
362
+ `CreateTransferResult` object.
363
+
364
+ ## Batching
365
+
366
+ TigerBeetle performance is maximized when you batch
367
+ API requests. The client does not do this automatically for
368
+ you. So, for example, you *can* insert 1 million transfers
369
+ one at a time like so:
370
+
371
+ ```python
372
+ batch = [] # Array of transfer to create.
373
+ for transfer in batch:
374
+ transfer_errors = client.create_transfers([transfer])
375
+ # Error handling omitted.
376
+ ```
377
+
378
+ But the insert rate will be a *fraction* of
379
+ potential. Instead, **always batch what you can**.
380
+
381
+ The maximum batch size is set in the TigerBeetle server. The default
382
+ is 8190.
383
+
384
+ ```python
385
+ batch = [] # Array of transfer to create.
386
+ BATCH_SIZE = 8190 #FIXME
387
+ for i in range(0, len(batch), BATCH_SIZE):
388
+ transfer_errors = client.create_transfers(
389
+ batch[i:min(len(batch), BATCH_SIZE)],
390
+ )
391
+ # Error handling omitted.
392
+ ```
393
+
394
+ ### Queues and Workers
395
+
396
+ If you are making requests to TigerBeetle from workers
397
+ pulling jobs from a queue, you can batch requests to
398
+ TigerBeetle by having the worker act on multiple jobs from
399
+ the queue at once rather than one at a time. i.e. pulling
400
+ multiple jobs from the queue rather than just one.
401
+
402
+ ## Transfer Flags
403
+
404
+ The transfer `flags` value is a bitfield. See details for these flags in
405
+ the [Transfers
406
+ reference](https://docs.tigerbeetle.com/reference/transfer#flags).
407
+
408
+ To toggle behavior for a transfer, combine enum values stored in the
409
+ `TransferFlags` object (it's an `enum.IntFlag`) with bitwise-or:
410
+
411
+ * `TransferFlags.linked`
412
+ * `TransferFlags.pending`
413
+ * `TransferFlags.post_pending_transfer`
414
+ * `TransferFlags.void_pending_transfer`
415
+
416
+ For example, to link `transfer0` and `transfer1`:
417
+
418
+ ```python
419
+ transfer0 = tb.Transfer(
420
+ id=4,
421
+ debit_account_id=102,
422
+ credit_account_id=103,
423
+ amount=10,
424
+ pending_id=0,
425
+ user_data_128=0,
426
+ user_data_64=0,
427
+ user_data_32=0,
428
+ timeout=0,
429
+ ledger=1,
430
+ code=720,
431
+ flags=tb.TransferFlags.LINKED,
432
+ timestamp=0,
433
+ )
434
+ transfer1 = tb.Transfer(
435
+ id=5,
436
+ debit_account_id=102,
437
+ credit_account_id=103,
438
+ amount=10,
439
+ pending_id=0,
440
+ user_data_128=0,
441
+ user_data_64=0,
442
+ user_data_32=0,
443
+ timeout=0,
444
+ ledger=1,
445
+ code=720,
446
+ flags=0,
447
+ timestamp=0,
448
+ )
449
+
450
+ # Create the transfer
451
+ transfer_errors = client.create_transfers([transfer0, transfer1])
452
+ # Error handling omitted.
453
+ ```
454
+
455
+ ### Two-Phase Transfers
456
+
457
+ Two-phase transfers are supported natively by toggling the appropriate
458
+ flag. TigerBeetle will then adjust the `credits_pending` and
459
+ `debits_pending` fields of the appropriate accounts. A corresponding
460
+ post pending transfer then needs to be sent to post or void the
461
+ transfer.
462
+
463
+ #### Post a Pending Transfer
464
+
465
+ With `flags` set to `post_pending_transfer`,
466
+ TigerBeetle will post the transfer. TigerBeetle will atomically roll
467
+ back the changes to `debits_pending` and `credits_pending` of the
468
+ appropriate accounts and apply them to the `debits_posted` and
469
+ `credits_posted` balances.
470
+
471
+ ```python
472
+ transfer0 = tb.Transfer(
473
+ id=6,
474
+ debit_account_id=102,
475
+ credit_account_id=103,
476
+ amount=10,
477
+ pending_id=0,
478
+ user_data_128=0,
479
+ user_data_64=0,
480
+ user_data_32=0,
481
+ timeout=0,
482
+ ledger=1,
483
+ code=720,
484
+ flags=tb.TransferFlags.PENDING,
485
+ timestamp=0,
486
+ )
487
+
488
+ transfer_errors = client.create_transfers([transfer0])
489
+ # Error handling omitted.
490
+
491
+ transfer1 = tb.Transfer(
492
+ id=7,
493
+ debit_account_id=102,
494
+ credit_account_id=103,
495
+ # Post the entire pending amount.
496
+ amount=tb.amount_max,
497
+ pending_id=6,
498
+ user_data_128=0,
499
+ user_data_64=0,
500
+ user_data_32=0,
501
+ timeout=0,
502
+ ledger=1,
503
+ code=720,
504
+ flags=tb.TransferFlags.POST_PENDING_TRANSFER,
505
+ timestamp=0,
506
+ )
507
+
508
+ transfer_errors = client.create_transfers([transfer1])
509
+ # Error handling omitted.
510
+ ```
511
+
512
+ #### Void a Pending Transfer
513
+
514
+ In contrast, with `flags` set to `void_pending_transfer`,
515
+ TigerBeetle will void the transfer. TigerBeetle will roll
516
+ back the changes to `debits_pending` and `credits_pending` of the
517
+ appropriate accounts and **not** apply them to the `debits_posted` and
518
+ `credits_posted` balances.
519
+
520
+ ```python
521
+ transfer0 = tb.Transfer(
522
+ id=8,
523
+ debit_account_id=102,
524
+ credit_account_id=103,
525
+ amount=10,
526
+ pending_id=0,
527
+ user_data_128=0,
528
+ user_data_64=0,
529
+ user_data_32=0,
530
+ timeout=0,
531
+ ledger=1,
532
+ code=720,
533
+ flags=tb.TransferFlags.PENDING,
534
+ timestamp=0,
535
+ )
536
+
537
+ transfer_errors = client.create_transfers([transfer0])
538
+ # Error handling omitted.
539
+
540
+ transfer1 = tb.Transfer(
541
+ id=9,
542
+ debit_account_id=102,
543
+ credit_account_id=103,
544
+ amount=10,
545
+ pending_id=8,
546
+ user_data_128=0,
547
+ user_data_64=0,
548
+ user_data_32=0,
549
+ timeout=0,
550
+ ledger=1,
551
+ code=720,
552
+ flags=tb.TransferFlags.VOID_PENDING_TRANSFER,
553
+ timestamp=0,
554
+ )
555
+
556
+ transfer_errors = client.create_transfers([transfer1])
557
+ # Error handling omitted.
558
+ ```
559
+
560
+ ## Transfer Lookup
561
+
562
+ NOTE: While transfer lookup exists, it is not a flexible query API. We
563
+ are developing query APIs and there will be new methods for querying
564
+ transfers in the future.
565
+
566
+ Transfer lookup is batched, like transfer creation. Pass in all `id`s to
567
+ fetch, and matched transfers are returned.
568
+
569
+ If no transfer matches an `id`, no object is returned for that
570
+ transfer. So the order of transfers in the response is not necessarily
571
+ the same as the order of `id`s in the request. You can refer to the
572
+ `id` field in the response to distinguish transfers.
573
+
574
+ ```python
575
+ transfers = client.lookup_transfers([1, 2])
576
+ ```
577
+
578
+ ## Get Account Transfers
579
+
580
+ NOTE: This is a preview API that is subject to breaking changes once we have
581
+ a stable querying API.
582
+
583
+ Fetches the transfers involving a given account, allowing basic filter and pagination
584
+ capabilities.
585
+
586
+ The transfers in the response are sorted by `timestamp` in chronological or
587
+ reverse-chronological order.
588
+
589
+ ```python
590
+ filter = tb.AccountFilter(
591
+ account_id=2,
592
+ user_data_128=0, # No filter by UserData.
593
+ user_data_64=0,
594
+ user_data_32=0,
595
+ code=0, # No filter by Code.
596
+ timestamp_min=0, # No filter by Timestamp.
597
+ timestamp_max=0, # No filter by Timestamp.
598
+ limit=10, # Limit to ten balances at most.
599
+ flags=tb.AccountFilterFlags.DEBITS | # Include transfer from the debit side.
600
+ tb.AccountFilterFlags.CREDITS | # Include transfer from the credit side.
601
+ tb.AccountFilterFlags.REVERSED, # Sort by timestamp in reverse-chronological order.
602
+ )
603
+
604
+ account_transfers = client.get_account_transfers(filter)
605
+ ```
606
+
607
+ ## Get Account Balances
608
+
609
+ NOTE: This is a preview API that is subject to breaking changes once we have
610
+ a stable querying API.
611
+
612
+ Fetches the point-in-time balances of a given account, allowing basic filter and
613
+ pagination capabilities.
614
+
615
+ Only accounts created with the flag
616
+ [`history`](https://docs.tigerbeetle.com/reference/account#flagshistory) set retain
617
+ [historical balances](https://docs.tigerbeetle.com/reference/requests/get_account_balances).
618
+
619
+ The balances in the response are sorted by `timestamp` in chronological or
620
+ reverse-chronological order.
621
+
622
+ ```python
623
+ filter = tb.AccountFilter(
624
+ account_id=2,
625
+ user_data_128=0, # No filter by UserData.
626
+ user_data_64=0,
627
+ user_data_32=0,
628
+ code=0, # No filter by Code.
629
+ timestamp_min=0, # No filter by Timestamp.
630
+ timestamp_max=0, # No filter by Timestamp.
631
+ limit=10, # Limit to ten balances at most.
632
+ flags=tb.AccountFilterFlags.DEBITS | # Include transfer from the debit side.
633
+ tb.AccountFilterFlags.CREDITS | # Include transfer from the credit side.
634
+ tb.AccountFilterFlags.REVERSED, # Sort by timestamp in reverse-chronological order.
635
+ )
636
+
637
+ account_balances = client.get_account_balances(filter)
638
+ ```
639
+
640
+ ## Query Accounts
641
+
642
+ NOTE: This is a preview API that is subject to breaking changes once we have
643
+ a stable querying API.
644
+
645
+ Query accounts by the intersection of some fields and by timestamp range.
646
+
647
+ The accounts in the response are sorted by `timestamp` in chronological or
648
+ reverse-chronological order.
649
+
650
+ ```python
651
+ query_filter = tb.QueryFilter(
652
+ user_data_128=1000, # Filter by UserData.
653
+ user_data_64=100,
654
+ user_data_32=10,
655
+ code=1, # Filter by Code.
656
+ ledger=0, # No filter by Ledger.
657
+ timestamp_min=0, # No filter by Timestamp.
658
+ timestamp_max=0, # No filter by Timestamp.
659
+ limit=10, # Limit to ten balances at most.
660
+ flags=tb.QueryFilterFlags.REVERSED, # Sort by timestamp in reverse-chronological order.
661
+ )
662
+
663
+ query_accounts = client.query_accounts(query_filter)
664
+ ```
665
+
666
+ ## Query Transfers
667
+
668
+ NOTE: This is a preview API that is subject to breaking changes once we have
669
+ a stable querying API.
670
+
671
+ Query transfers by the intersection of some fields and by timestamp range.
672
+
673
+ The transfers in the response are sorted by `timestamp` in chronological or
674
+ reverse-chronological order.
675
+
676
+ ```python
677
+ query_filter = tb.QueryFilter(
678
+ user_data_128=1000, # Filter by UserData.
679
+ user_data_64=100,
680
+ user_data_32=10,
681
+ code=1, # Filter by Code.
682
+ ledger=0, # No filter by Ledger.
683
+ timestamp_min=0, # No filter by Timestamp.
684
+ timestamp_max=0, # No filter by Timestamp.
685
+ limit=10, # Limit to ten balances at most.
686
+ flags=tb.QueryFilterFlags.REVERSED, # Sort by timestamp in reverse-chronological order.
687
+ )
688
+
689
+ query_transfers = client.query_transfers(query_filter)
690
+ ```
691
+
692
+ ## Linked Events
693
+
694
+ When the `linked` flag is specified for an account when creating accounts or
695
+ a transfer when creating transfers, it links that event with the next event in the
696
+ batch, to create a chain of events, of arbitrary length, which all
697
+ succeed or fail together. The tail of a chain is denoted by the first
698
+ event without this flag. The last event in a batch may therefore never
699
+ have the `linked` flag set as this would leave a chain
700
+ open-ended. Multiple chains or individual events may coexist within a
701
+ batch to succeed or fail independently.
702
+
703
+ Events within a chain are executed within order, or are rolled back on
704
+ error, so that the effect of each event in the chain is visible to the
705
+ next, and so that the chain is either visible or invisible as a unit
706
+ to subsequent events after the chain. The event that was the first to
707
+ break the chain will have a unique error result. Other events in the
708
+ chain will have their error result set to `linked_event_failed`.
709
+
710
+ ```python
711
+ batch = [] # List of tb.Transfers to create.
712
+ linkedFlag = 0
713
+ linkedFlag |= tb.TransferFlags.LINKED
714
+
715
+ # An individual transfer (successful):
716
+ batch.append(tb.Transfer(id=1))
717
+
718
+ # A chain of 4 transfers (the last transfer in the chain closes the chain with linked=false):
719
+ batch.append(tb.Transfer(id=2, flags=linkedFlag)) # Commit/rollback.
720
+ batch.append(tb.Transfer(id=3, flags=linkedFlag)) # Commit/rollback.
721
+ batch.append(tb.Transfer(id=2, flags=linkedFlag)) # Fail with exists
722
+ batch.append(tb.Transfer(id=4, flags=0)) # Fail without committing.
723
+
724
+ # An individual transfer (successful):
725
+ # This should not see any effect from the failed chain above.
726
+ batch.append(tb.Transfer(id=2, flags=0 ))
727
+
728
+ # A chain of 2 transfers (the first transfer fails the chain):
729
+ batch.append(tb.Transfer(id=2, flags=linkedFlag))
730
+ batch.append(tb.Transfer(id=3, flags=0))
731
+
732
+ # A chain of 2 transfers (successful):
733
+ batch.append(tb.Transfer(id=3, flags=linkedFlag))
734
+ batch.append(tb.Transfer(id=4, flags=0))
735
+
736
+ transfer_errors = client.create_transfers(batch)
737
+ # Error handling omitted.
738
+ ```
739
+
740
+ ## Imported Events
741
+
742
+ When the `imported` flag is specified for an account when creating accounts or
743
+ a transfer when creating transfers, it allows importing historical events with
744
+ a user-defined timestamp.
745
+
746
+ The entire batch of events must be set with the flag `imported`.
747
+
748
+ It's recommended to submit the whole batch as a `linked` chain of events, ensuring that
749
+ if any event fails, none of them are committed, preserving the last timestamp unchanged.
750
+ This approach gives the application a chance to correct failed imported events, re-submitting
751
+ the batch again with the same user-defined timestamps.
752
+
753
+ ```python
754
+ # External source of time.
755
+ historical_timestamp = 0
756
+ # Events loaded from an external source.
757
+ historical_accounts = [] # Loaded from an external source.
758
+ historical_transfers = [] # Loaded from an external source.
759
+
760
+ # First, load and import all accounts with their timestamps from the historical source.
761
+ accounts = []
762
+ for index, account in enumerate(historical_accounts):
763
+ # Set a unique and strictly increasing timestamp.
764
+ historical_timestamp += 1
765
+ account.timestamp = historical_timestamp
766
+ # Set the account as `imported`.
767
+ account.flags = tb.AccountFlags.IMPORTED
768
+ # To ensure atomicity, the entire batch (except the last event in the chain)
769
+ # must be `linked`.
770
+ if index < len(historical_accounts) - 1:
771
+ account.flags |= tb.AccountFlags.LINKED
772
+
773
+ accounts.append(account)
774
+
775
+ account_errors = client.create_accounts(accounts)
776
+ # Error handling omitted.
777
+
778
+ # The, load and import all transfers with their timestamps from the historical source.
779
+ transfers = []
780
+ for index, transfer in enumerate(historical_transfers):
781
+ # Set a unique and strictly increasing timestamp.
782
+ historical_timestamp += 1
783
+ transfer.timestamp = historical_timestamp
784
+ # Set the account as `imported`.
785
+ transfer.flags = tb.TransferFlags.IMPORTED
786
+ # To ensure atomicity, the entire batch (except the last event in the chain)
787
+ # must be `linked`.
788
+ if index < len(historical_transfers) - 1:
789
+ transfer.flags |= tb.AccountFlags.LINKED
790
+
791
+ transfers.append(transfer)
792
+
793
+ transfer_errors = client.create_transfers(transfers)
794
+ # Error handling omitted.
795
+
796
+ # Since it is a linked chain, in case of any error the entire batch is rolled back and can be retried
797
+ # with the same historical timestamps without regressing the cluster timestamp.
798
+ ```