0
# Python API Reference
1
2
The FoundationDB Python API provides a high-level, Pythonic interface to the database with automatic transaction retry, iterator support for ranges, and comprehensive error handling. The API is built on the C client library and offers both functional and object-oriented patterns.
3
4
## Installation
5
6
```bash
7
pip install foundationdb
8
```
9
10
## Core Imports
11
12
```python
13
import fdb
14
import fdb.tuple
15
import fdb.directory
16
import fdb.subspace
17
```
18
19
## Capabilities
20
21
### Initialization and Database Connection
22
23
API version selection and database connection management.
24
25
```python { .api }
26
def api_version(version: int) -> None:
27
"""
28
Set the API version (must be called before any other operations).
29
30
Args:
31
version: API version to use (typically 740 for v7.4.0+)
32
"""
33
34
def open(cluster_file: Optional[str] = None) -> Database:
35
"""
36
Open database connection.
37
38
Args:
39
cluster_file: Path to cluster file (None for default location)
40
41
Returns:
42
Database handle for creating transactions
43
"""
44
```
45
46
**Usage Example:**
47
48
```python
49
import fdb
50
51
# Initialize FoundationDB (required first step)
52
fdb.api_version(740)
53
54
# Open database connection
55
db = fdb.open() # Uses default cluster file
56
# or specify cluster file path
57
db = fdb.open('/etc/foundationdb/fdb.cluster')
58
```
59
60
### Database Operations
61
62
Database handle for creating transactions and managing tenants.
63
64
```python { .api }
65
class Database:
66
"""Database handle for transaction creation and tenant management."""
67
68
def create_transaction(self) -> Transaction:
69
"""
70
Create a new transaction.
71
72
Returns:
73
Transaction handle for database operations
74
"""
75
76
def open_tenant(self, tenant_name: bytes) -> Tenant:
77
"""
78
Open tenant handle for multi-tenancy.
79
80
Args:
81
tenant_name: Name of the tenant to open
82
83
Returns:
84
Tenant handle with isolated keyspace
85
"""
86
```
87
88
### Transaction Decorator and Functional Interface
89
90
High-level transaction handling with automatic retry logic.
91
92
```python { .api }
93
@transactional
94
def transaction_function(tr: Transaction, *args, **kwargs) -> Any:
95
"""
96
Decorator for automatic transaction retry.
97
Functions decorated with @fdb.transactional automatically retry
98
on retryable errors and conflicts.
99
100
Args:
101
tr: Transaction handle (automatically provided)
102
*args, **kwargs: Function arguments
103
104
Returns:
105
Function return value
106
"""
107
108
def transactional(func: Callable) -> Callable:
109
"""
110
Decorator that automatically retries functions on database conflicts.
111
112
Args:
113
func: Function to wrap with retry logic
114
115
Returns:
116
Wrapped function with automatic retry
117
"""
118
```
119
120
**Usage Example:**
121
122
```python
123
import fdb
124
125
fdb.api_version(740)
126
db = fdb.open()
127
128
@fdb.transactional
129
def update_counter(tr, counter_key, increment=1):
130
"""Atomically increment a counter."""
131
current_value = tr.get(counter_key).wait()
132
if current_value is None:
133
new_value = increment
134
else:
135
new_value = int(current_value) + increment
136
137
tr.set(counter_key, str(new_value).encode())
138
return new_value
139
140
# Function automatically retries on conflicts
141
result = update_counter(db, b'my_counter', 5)
142
print(f"New counter value: {result}")
143
```
144
145
### Transaction Operations
146
147
Core transaction operations for reading and writing data.
148
149
```python { .api }
150
class Transaction:
151
"""Transaction context for ACID database operations."""
152
153
def get(self, key: KeyConvertible, snapshot: bool = False) -> Future:
154
"""
155
Read a single key.
156
157
Args:
158
key: Key to read (bytes, str, or tuple)
159
snapshot: Whether to use snapshot read (no conflicts)
160
161
Returns:
162
Future containing the value (None if key doesn't exist)
163
"""
164
165
def get_range(self, begin: KeyConvertible, end: KeyConvertible,
166
limit: int = 0, reverse: bool = False,
167
streaming_mode: int = fdb.StreamingMode.ITERATOR) -> Iterable[KeyValue]:
168
"""
169
Read a range of key-value pairs.
170
171
Args:
172
begin: Start key (inclusive)
173
end: End key (exclusive)
174
limit: Maximum number of pairs to return (0 for no limit)
175
reverse: Whether to read in reverse order
176
streaming_mode: How to stream results
177
178
Returns:
179
Iterable of KeyValue objects
180
"""
181
182
def set(self, key: KeyConvertible, value: ValueConvertible) -> None:
183
"""
184
Write a key-value pair.
185
186
Args:
187
key: Key to write (bytes, str, or tuple)
188
value: Value to write (bytes or str)
189
"""
190
191
def clear(self, key: KeyConvertible) -> None:
192
"""
193
Delete a single key.
194
195
Args:
196
key: Key to delete (bytes, str, or tuple)
197
"""
198
199
def clear_range(self, begin: KeyConvertible, end: KeyConvertible) -> None:
200
"""
201
Delete a range of keys.
202
203
Args:
204
begin: Start key (inclusive)
205
end: End key (exclusive)
206
"""
207
208
def commit(self) -> Future:
209
"""
210
Commit the transaction.
211
212
Returns:
213
Future that completes when commit finishes
214
"""
215
216
def on_error(self, error: Exception) -> Future:
217
"""
218
Handle transaction errors and determine retry logic.
219
220
Args:
221
error: Exception that occurred
222
223
Returns:
224
Future that completes when ready to retry
225
"""
226
227
def get_read_version(self) -> Future:
228
"""
229
Get the read version for this transaction.
230
231
Returns:
232
Future containing the read version
233
"""
234
235
def set_read_version(self, version: int) -> None:
236
"""
237
Set the read version for this transaction.
238
239
Args:
240
version: Read version to use
241
"""
242
243
def get_committed_version(self) -> int:
244
"""
245
Get the version at which this transaction was committed.
246
Only valid after successful commit.
247
248
Returns:
249
Committed version number
250
"""
251
252
def get_versionstamp(self) -> Future:
253
"""
254
Get the versionstamp for this transaction.
255
Only valid after successful commit.
256
257
Returns:
258
Future containing the 12-byte versionstamp
259
"""
260
261
def add_read_conflict_range(self, begin: KeyConvertible, end: KeyConvertible) -> None:
262
"""
263
Add a range to the transaction's read conflict ranges.
264
265
Args:
266
begin: Start key (inclusive)
267
end: End key (exclusive)
268
"""
269
270
def add_write_conflict_range(self, begin: KeyConvertible, end: KeyConvertible) -> None:
271
"""
272
Add a range to the transaction's write conflict ranges.
273
274
Args:
275
begin: Start key (inclusive)
276
end: End key (exclusive)
277
"""
278
```
279
280
### Future Operations
281
282
Asynchronous result handling and synchronization.
283
284
```python { .api }
285
class Future:
286
"""Asynchronous result wrapper for database operations."""
287
288
def wait(self) -> Any:
289
"""
290
Block until the result is available and return it.
291
292
Returns:
293
The result value
294
295
Raises:
296
FDBError: If the operation failed
297
"""
298
299
def is_ready(self) -> bool:
300
"""
301
Check if the result is available without blocking.
302
303
Returns:
304
True if result is ready, False otherwise
305
"""
306
307
def block_until_ready(self) -> None:
308
"""
309
Block until the result is available (without returning it).
310
"""
311
312
def on_ready(self, callback: Callable[[Future], None]) -> None:
313
"""
314
Set a callback to be called when the result is ready.
315
316
Args:
317
callback: Function to call with this Future as argument
318
"""
319
```
320
321
### Key Selectors and Range Operations
322
323
Advanced key selection and range query operations.
324
325
```python { .api }
326
class KeySelector:
327
"""Key selector for advanced range operations."""
328
329
def __init__(self, key: KeyConvertible, or_equal: bool, offset: int):
330
"""
331
Create a key selector.
332
333
Args:
334
key: Reference key
335
or_equal: Whether to include the reference key
336
offset: Offset from the reference key
337
"""
338
339
@staticmethod
340
def last_less_than(key: KeyConvertible) -> KeySelector:
341
"""Key selector for last key less than given key."""
342
343
@staticmethod
344
def last_less_or_equal(key: KeyConvertible) -> KeySelector:
345
"""Key selector for last key less than or equal to given key."""
346
347
@staticmethod
348
def first_greater_than(key: KeyConvertible) -> KeySelector:
349
"""Key selector for first key greater than given key."""
350
351
@staticmethod
352
def first_greater_or_equal(key: KeyConvertible) -> KeySelector:
353
"""Key selector for first key greater than or equal to given key."""
354
355
class KeyValue:
356
"""Key-value pair from range operations."""
357
key: bytes
358
value: bytes
359
```
360
361
### Tenant Operations
362
363
Multi-tenancy support for isolated keyspaces.
364
365
```python { .api }
366
class Tenant:
367
"""Tenant handle for multi-tenancy operations."""
368
369
def create_transaction(self) -> Transaction:
370
"""
371
Create a new transaction within this tenant's keyspace.
372
373
Returns:
374
Transaction handle scoped to this tenant
375
"""
376
377
def __enter__(self) -> Tenant:
378
"""Context manager entry."""
379
380
def __exit__(self, exc_type, exc_val, exc_tb) -> None:
381
"""Context manager exit."""
382
```
383
384
### Error Handling
385
386
Exception classes and error predicate functions.
387
388
```python { .api }
389
class FDBError(Exception):
390
"""Base exception class for FoundationDB errors."""
391
392
def __init__(self, code: int):
393
"""
394
Create FDBError with error code.
395
396
Args:
397
code: FoundationDB error code
398
"""
399
self.code = code
400
super().__init__(fdb.strerror(code))
401
402
# Error predicate functions
403
def predicates.retryable(error: Exception) -> bool:
404
"""
405
Check if error indicates transaction should be retried.
406
407
Args:
408
error: Exception to check
409
410
Returns:
411
True if error is retryable
412
"""
413
414
def predicates.maybe_committed(error: Exception) -> bool:
415
"""
416
Check if transaction may have been committed despite error.
417
418
Args:
419
error: Exception to check
420
421
Returns:
422
True if transaction may have committed
423
"""
424
425
def predicates.retryable_not_committed(error: Exception) -> bool:
426
"""
427
Check if error is retryable and transaction was not committed.
428
429
Args:
430
error: Exception to check
431
432
Returns:
433
True if safe to retry (not committed)
434
"""
435
```
436
437
### Tuple Encoding
438
439
Structured key encoding for hierarchical data organization.
440
441
```python { .api }
442
# fdb.tuple module
443
def pack(items: Tuple[Any, ...]) -> bytes:
444
"""
445
Encode tuple to bytes for use as key.
446
447
Args:
448
items: Tuple of values to encode
449
450
Returns:
451
Encoded bytes suitable for use as FoundationDB key
452
"""
453
454
def unpack(data: bytes) -> Tuple[Any, ...]:
455
"""
456
Decode bytes back to tuple.
457
458
Args:
459
data: Encoded tuple bytes
460
461
Returns:
462
Decoded tuple values
463
"""
464
465
def range(prefix: Tuple[Any, ...]) -> Tuple[bytes, bytes]:
466
"""
467
Create key range for all tuples with given prefix.
468
469
Args:
470
prefix: Tuple prefix
471
472
Returns:
473
(begin_key, end_key) tuple for range operations
474
"""
475
```
476
477
**Usage Example:**
478
479
```python
480
import fdb.tuple as tuple
481
482
# Encode structured keys
483
user_key = tuple.pack(("users", user_id, "profile"))
484
score_key = tuple.pack(("scores", game_id, timestamp, player_id))
485
486
# Create ranges for prefix queries
487
user_range = tuple.range(("users", user_id)) # All keys for this user
488
score_range = tuple.range(("scores", game_id)) # All scores for this game
489
490
# Use in transactions
491
@fdb.transactional
492
def get_user_data(tr, user_id):
493
begin, end = tuple.range(("users", user_id))
494
return list(tr.get_range(begin, end))
495
```
496
497
### Locality Operations
498
499
Functions for querying database topology and key distribution.
500
501
```python { .api }
502
# fdb.locality module
503
def get_boundary_keys(db_or_tr, begin: KeyConvertible, end: KeyConvertible) -> Iterator[bytes]:
504
"""
505
Get boundary keys for shards in the specified range.
506
507
Args:
508
db_or_tr: Database or Transaction handle
509
begin: Start of key range
510
end: End of key range
511
512
Returns:
513
Iterator of boundary keys between shards
514
"""
515
516
@transactional
517
def get_addresses_for_key(tr: Transaction, key: KeyConvertible) -> Future:
518
"""
519
Get network addresses of servers storing the specified key.
520
521
Args:
522
tr: Transaction handle
523
key: Key to query addresses for
524
525
Returns:
526
Future containing list of server addresses
527
"""
528
```
529
530
**Usage Example:**
531
532
```python
533
import fdb.locality
534
535
# Get shard boundaries for a key range
536
boundaries = list(fdb.locality.get_boundary_keys(db, b"user/", b"user0"))
537
print(f"Found {len(boundaries)} shard boundaries")
538
539
# Get addresses for a specific key
540
@fdb.transactional
541
def find_key_location(tr, key):
542
addresses = fdb.locality.get_addresses_for_key(tr, key).wait()
543
return addresses
544
545
servers = find_key_location(db, b"user:12345")
546
```
547
548
### Administrative Tenant Management
549
550
Functions for creating, deleting, and listing tenants in the cluster.
551
552
```python { .api }
553
# fdb.tenant_management module (available in API version 630+)
554
def create_tenant(db_or_tr, tenant_name: bytes) -> None:
555
"""
556
Create a new tenant in the cluster.
557
558
Args:
559
db_or_tr: Database or Transaction handle
560
tenant_name: Unique name for the tenant
561
562
Raises:
563
FDBError(2132): tenant_already_exists if tenant exists
564
"""
565
566
def delete_tenant(db_or_tr, tenant_name: bytes) -> None:
567
"""
568
Delete a tenant from the cluster.
569
570
Args:
571
db_or_tr: Database or Transaction handle
572
tenant_name: Name of tenant to delete
573
574
Raises:
575
FDBError(2131): tenant_not_found if tenant doesn't exist
576
"""
577
578
def list_tenants(db_or_tr, begin: bytes, end: bytes, limit: int) -> FDBTenantList:
579
"""
580
List tenants in the cluster within the specified range.
581
582
Args:
583
db_or_tr: Database or Transaction handle
584
begin: Start of tenant name range (inclusive)
585
end: End of tenant name range (exclusive)
586
limit: Maximum number of tenants to return
587
588
Returns:
589
Iterable of KeyValue objects with tenant names and metadata
590
"""
591
592
class FDBTenantList:
593
"""Iterator for tenant listing results."""
594
def to_list(self) -> List[KeyValue]: ...
595
def __iter__(self) -> Iterator[KeyValue]: ...
596
```
597
598
**Usage Example:**
599
600
```python
601
import fdb.tenant_management
602
603
# Create tenants for different applications
604
fdb.tenant_management.create_tenant(db, b"app1")
605
fdb.tenant_management.create_tenant(db, b"app2")
606
607
# List all tenants
608
tenants = fdb.tenant_management.list_tenants(db, b"", b"\xFF", 100)
609
for tenant in tenants:
610
tenant_name = tenant.key.decode()
611
print(f"Tenant: {tenant_name}")
612
613
# Delete a tenant
614
fdb.tenant_management.delete_tenant(db, b"old_app")
615
```
616
617
### Subspace Operations
618
619
Key prefix management for organizing data hierarchies.
620
621
```python { .api }
622
class Subspace:
623
"""Key prefix management for hierarchical data organization."""
624
625
def __init__(self, prefixTuple: Tuple[Any, ...] = tuple(), rawPrefix: bytes = b''):
626
"""
627
Create subspace with tuple prefix.
628
629
Args:
630
prefixTuple: Tuple to use as prefix
631
rawPrefix: Raw bytes to use as prefix
632
"""
633
634
def pack(self, t: Tuple[Any, ...] = tuple()) -> bytes:
635
"""
636
Pack tuple with subspace prefix.
637
638
Args:
639
t: Tuple to pack with prefix
640
641
Returns:
642
Packed key with subspace prefix
643
"""
644
645
def unpack(self, key: bytes) -> Tuple[Any, ...]:
646
"""
647
Unpack key and remove subspace prefix.
648
649
Args:
650
key: Key to unpack
651
652
Returns:
653
Tuple without subspace prefix
654
"""
655
656
def range(self, t: Tuple[Any, ...] = tuple()) -> Tuple[bytes, bytes]:
657
"""
658
Create range within this subspace.
659
660
Args:
661
t: Tuple suffix for range
662
663
Returns:
664
(begin_key, end_key) for range operations
665
"""
666
667
def contains(self, key: bytes) -> bool:
668
"""
669
Check if key belongs to this subspace.
670
671
Args:
672
key: Key to check
673
674
Returns:
675
True if key is in this subspace
676
"""
677
678
def subspace(self, t: Tuple[Any, ...]) -> Subspace:
679
"""
680
Create child subspace.
681
682
Args:
683
t: Additional tuple elements for child
684
685
Returns:
686
New subspace with extended prefix
687
"""
688
```
689
690
### Directory Layer
691
692
Hierarchical directory management system built on subspaces.
693
694
```python { .api }
695
# fdb.directory module
696
class DirectoryLayer:
697
"""Hierarchical directory management for organizing keyspaces."""
698
699
def create_or_open(self, tr: Transaction, path: List[str],
700
layer: bytes = b'') -> Directory:
701
"""
702
Create or open directory at path.
703
704
Args:
705
tr: Transaction handle
706
path: Directory path as list of strings
707
layer: Layer identifier for directory type
708
709
Returns:
710
Directory handle
711
"""
712
713
def open(self, tr: Transaction, path: List[str],
714
layer: bytes = b'') -> Directory:
715
"""
716
Open existing directory at path.
717
718
Args:
719
tr: Transaction handle
720
path: Directory path as list of strings
721
layer: Expected layer identifier
722
723
Returns:
724
Directory handle
725
726
Raises:
727
DirectoryError: If directory doesn't exist
728
"""
729
730
def create(self, tr: Transaction, path: List[str],
731
layer: bytes = b'', prefix: bytes = None) -> Directory:
732
"""
733
Create new directory at path.
734
735
Args:
736
tr: Transaction handle
737
path: Directory path as list of strings
738
layer: Layer identifier for directory type
739
prefix: Specific prefix to use (None for automatic)
740
741
Returns:
742
Directory handle
743
744
Raises:
745
DirectoryError: If directory already exists
746
"""
747
748
def list(self, tr: Transaction, path: List[str] = []) -> List[str]:
749
"""
750
List subdirectories at path.
751
752
Args:
753
tr: Transaction handle
754
path: Directory path to list
755
756
Returns:
757
List of subdirectory names
758
"""
759
760
def remove(self, tr: Transaction, path: List[str]) -> bool:
761
"""
762
Remove directory and all its data.
763
764
Args:
765
tr: Transaction handle
766
path: Directory path to remove
767
768
Returns:
769
True if directory was removed
770
"""
771
772
def move(self, tr: Transaction, old_path: List[str],
773
new_path: List[str]) -> Directory:
774
"""
775
Move directory to new path.
776
777
Args:
778
tr: Transaction handle
779
old_path: Current directory path
780
new_path: New directory path
781
782
Returns:
783
Directory handle at new location
784
"""
785
786
def exists(self, tr: Transaction, path: List[str]) -> bool:
787
"""
788
Check if directory exists at path.
789
790
Args:
791
tr: Transaction handle
792
path: Directory path to check
793
794
Returns:
795
True if directory exists
796
"""
797
798
class Directory(Subspace):
799
"""Directory handle extending Subspace functionality."""
800
801
def get_layer(self) -> bytes:
802
"""Get the layer identifier for this directory."""
803
804
def get_path(self) -> List[str]:
805
"""Get the path for this directory."""
806
807
# Default directory layer instance
808
directory = DirectoryLayer()
809
```
810
811
## Configuration Options
812
813
### Transaction Options
814
815
```python { .api }
816
# Transaction option constants
817
class TransactionOptions:
818
CAUSAL_READ_RISKY = 0
819
CAUSAL_WRITE_RISKY = 1
820
READ_YOUR_WRITES_DISABLE = 2
821
READ_AHEAD_DISABLE = 3
822
DURABILITY_DATACENTER = 4
823
DURABILITY_RISKY = 5
824
PRIORITY_SYSTEM_IMMEDIATE = 6
825
PRIORITY_BATCH = 7
826
INITIALIZE_NEW_DATABASE = 8
827
ACCESS_SYSTEM_KEYS = 9
828
READ_SYSTEM_KEYS = 10
829
TIMEOUT = 11
830
RETRY_LIMIT = 12
831
MAX_RETRY_DELAY = 13
832
SIZE_LIMIT = 14
833
SNAPSHOT_RYW_ENABLE = 15
834
SNAPSHOT_RYW_DISABLE = 16
835
LOCK_AWARE = 17
836
USED_DURING_COMMIT_PROTECTION_DISABLE = 18
837
READ_LOCK_AWARE = 19
838
839
# Usage
840
tr.options.set_timeout(5000) # 5 second timeout
841
tr.options.set_retry_limit(100)
842
tr.options.set_size_limit(10000000) # 10MB limit
843
```
844
845
### Streaming Modes
846
847
```python { .api }
848
class StreamingMode:
849
"""Streaming modes for range operations."""
850
WANT_ALL = -2
851
ITERATOR = -1 # Default for iteration
852
EXACT = 0
853
SMALL = 1
854
MEDIUM = 2
855
LARGE = 3
856
SERIAL = 4
857
```
858
859
## Complete Usage Example
860
861
```python
862
import fdb
863
import fdb.tuple as tuple
864
import fdb.directory
865
866
# Initialize FoundationDB
867
fdb.api_version(740)
868
db = fdb.open()
869
870
# Directory-based organization
871
@fdb.transactional
872
def setup_application(tr):
873
# Create application directories
874
root = fdb.directory.create_or_open(tr, ['myapp'])
875
users = root.create_or_open(tr, ['users'])
876
sessions = root.create_or_open(tr, ['sessions'])
877
return users, sessions
878
879
users_dir, sessions_dir = setup_application(db)
880
881
@fdb.transactional
882
def create_user(tr, user_id, email, name):
883
"""Create a new user with profile data."""
884
user_space = users_dir.subspace((user_id,))
885
886
# Store user profile
887
tr.set(user_space.pack(('email',)), email.encode())
888
tr.set(user_space.pack(('name',)), name.encode())
889
tr.set(user_space.pack(('created',)), str(time.time()).encode())
890
891
# Add to email index
892
email_key = tuple.pack(('email_index', email, user_id))
893
tr.set(email_key, b'')
894
895
return user_id
896
897
@fdb.transactional
898
def get_user_profile(tr, user_id):
899
"""Get complete user profile."""
900
user_space = users_dir.subspace((user_id,))
901
902
profile = {}
903
for kv in tr.get_range(user_space.range()):
904
field = user_space.unpack(kv.key)[0]
905
profile[field] = kv.value.decode()
906
907
return profile
908
909
@fdb.transactional
910
def find_user_by_email(tr, email):
911
"""Find user ID by email address."""
912
begin, end = tuple.range(('email_index', email))
913
914
for kv in tr.get_range(begin, end, limit=1):
915
# Extract user_id from key
916
_, _, user_id = tuple.unpack(kv.key)
917
return user_id
918
919
return None
920
921
# Usage
922
user_id = create_user(db, 'user123', 'alice@example.com', 'Alice Smith')
923
profile = get_user_profile(db, user_id)
924
found_user = find_user_by_email(db, 'alice@example.com')
925
926
print(f"Created user: {user_id}")
927
print(f"Profile: {profile}")
928
print(f"Found user by email: {found_user}")
929
```