3
3
4
4
import json
5
5
import queue
6
+ import random
6
7
7
8
from spacetimedb_sdk .spacetime_websocket_client import WebSocketClient
8
9
from spacetimedb_sdk .client_cache import ClientCache
@@ -56,6 +57,69 @@ def __eq__(self, other):
56
57
def __hash__ (self ):
57
58
return hash (self .data )
58
59
60
+ class Address :
61
+ """
62
+ Represents a user address. This is a wrapper around the Uint8Array that is recieved from SpacetimeDB.
63
+
64
+ Attributes:
65
+ data (bytes): The address data.
66
+ """
67
+
68
+ def __init__ (self , data ):
69
+ self .data = bytes (data ) # Ensure data is always bytes
70
+
71
+ @staticmethod
72
+ def from_string (string ):
73
+ """
74
+ Returns an Address object with the data attribute set to the byte representation of the input string.
75
+ Returns None if the string is all zeros.
76
+
77
+ Args:
78
+ string (str): The input string.
79
+
80
+ Returns:
81
+ Address: The Address object.
82
+ """
83
+ address_bytes = bytes .fromhex (string )
84
+ if all (byte == 0 for byte in address_bytes ):
85
+ return None
86
+ else :
87
+ return Address (address_bytes )
88
+
89
+ @staticmethod
90
+ def from_bytes (data ):
91
+ """
92
+ Returns an Address object with the data attribute set to the input bytes.
93
+
94
+ Args:
95
+ data (bytes): The input bytes.
96
+
97
+ Returns:
98
+ Address: The Address object.
99
+ """
100
+ if all (byte == 0 for byte in address_bytes ):
101
+ return None
102
+ else :
103
+ return Address (data )
104
+
105
+ @staticmethod
106
+ def random ():
107
+ """
108
+ Returns a random Address.
109
+ """
110
+ return Address (bytes (random .getrandbits (8 ) for _ in range (16 )))
111
+
112
+ # override to_string
113
+ def __str__ (self ):
114
+ return self .data .hex ()
115
+
116
+ # override = operator
117
+ def __eq__ (self , other ):
118
+ return isinstance (other , Address ) and self .data == other .data
119
+
120
+ def __hash__ (self ):
121
+ return hash (self .data )
122
+
59
123
60
124
class DbEvent :
61
125
"""
@@ -93,11 +157,12 @@ class _IdentityReceivedMessage(_ClientApiMessage):
93
157
This class is intended for internal use only and should not be used externally.
94
158
"""
95
159
96
- def __init__ (self , auth_token , identity ):
160
+ def __init__ (self , auth_token , identity , address ):
97
161
super ().__init__ ("IdentityReceived" )
98
162
99
163
self .auth_token = auth_token
100
164
self .identity = identity
165
+ self .address = address
101
166
102
167
103
168
class _SubscriptionUpdateMessage (_ClientApiMessage ):
@@ -114,15 +179,17 @@ class ReducerEvent:
114
179
This class contains the information about a reducer event to be passed to row update callbacks.
115
180
"""
116
181
117
- def __init__ (self , caller_identity , reducer_name , status , message , args ):
182
+ def __init__ (self , caller_identity , caller_address , reducer_name , status , message , args ):
118
183
self .caller_identity = caller_identity
184
+ self .caller_address = caller_address
119
185
self .reducer_name = reducer_name
120
186
self .status = status
121
187
self .message = message
122
188
self .args = args
123
189
124
190
125
191
class TransactionUpdateMessage (_ClientApiMessage ):
192
+ # This docstring appears incorrect
126
193
"""
127
194
Represents a transaction update message. Used in on_event callbacks.
128
195
@@ -140,14 +207,15 @@ class TransactionUpdateMessage(_ClientApiMessage):
140
207
def __init__ (
141
208
self ,
142
209
caller_identity : Identity ,
210
+ caller_address : Address ,
143
211
status : str ,
144
212
message : str ,
145
213
reducer_name : str ,
146
214
args : Dict ,
147
215
):
148
216
super ().__init__ ("TransactionUpdate" )
149
217
self .reducer_event = ReducerEvent (
150
- caller_identity , reducer_name , status , message , args
218
+ caller_identity , caller_address , reducer_name , status , message , args
151
219
)
152
220
153
221
@@ -169,7 +237,7 @@ def init(
169
237
autogen_package : ModuleType ,
170
238
on_connect : Callable [[], None ] = None ,
171
239
on_disconnect : Callable [[str ], None ] = None ,
172
- on_identity : Callable [[str , Identity ], None ] = None ,
240
+ on_identity : Callable [[str , Identity , Address ], None ] = None ,
173
241
on_error : Callable [[str ], None ] = None ,
174
242
):
175
243
"""
@@ -182,7 +250,7 @@ def init(
182
250
autogen_package (ModuleType): Python package where SpacetimeDB module generated files are located.
183
251
on_connect (Callable[[], None], optional): Optional callback called when a connection is made to the SpacetimeDB module.
184
252
on_disconnect (Callable[[str], None], optional): Optional callback called when the Python client is disconnected from the SpacetimeDB module. The argument is the close message.
185
- on_identity (Callable[[str, bytes ], None], optional): Called when the user identity is recieved from SpacetimeDB. First argument is the auth token used to login in future sessions.
253
+ on_identity (Callable[[str, Identity, Address ], None], optional): Called when the user identity is recieved from SpacetimeDB. First argument is the auth token used to login in future sessions.
186
254
on_error (Callable[[str], None], optional): Optional callback called when the Python client connection encounters an error. The argument is the error message.
187
255
188
256
Example:
@@ -210,6 +278,7 @@ def __init__(self, autogen_package):
210
278
self ._on_event = []
211
279
212
280
self .identity = None
281
+ self .address = Address .random ()
213
282
214
283
self .client_cache = ClientCache (autogen_package )
215
284
self .message_queue = queue .Queue ()
@@ -238,6 +307,7 @@ def connect(
238
307
on_error = on_error ,
239
308
on_close = on_disconnect ,
240
309
on_message = self ._on_message ,
310
+ client_address = self .address ,
241
311
)
242
312
# print("CONNECTING " + host + " " + address_or_name)
243
313
self .wsc .connect (
@@ -420,7 +490,8 @@ def _on_message(self, data):
420
490
# is this safe to do in the message thread?
421
491
token = message ["IdentityToken" ]["token" ]
422
492
identity = Identity .from_string (message ["IdentityToken" ]["identity" ])
423
- self .message_queue .put (_IdentityReceivedMessage (token , identity ))
493
+ address = Address .from_string (message ["IdentityToken" ]["address" ])
494
+ self .message_queue .put (_IdentityReceivedMessage (token , identity , address ))
424
495
elif "SubscriptionUpdate" in message or "TransactionUpdate" in message :
425
496
clientapi_message = None
426
497
table_updates = None
@@ -432,6 +503,7 @@ def _on_message(self, data):
432
503
# DAB Todo: We need reducer codegen to parse the args
433
504
clientapi_message = TransactionUpdateMessage (
434
505
Identity .from_string (spacetime_message ["event" ]["caller_identity" ]),
506
+ Address .from_string (spacetime_message ["event" ]["caller_address" ]),
435
507
spacetime_message ["event" ]["status" ],
436
508
spacetime_message ["event" ]["message" ],
437
509
spacetime_message ["event" ]["function_call" ]["reducer" ],
@@ -473,8 +545,9 @@ def _do_update(self):
473
545
474
546
if next_message .transaction_type == "IdentityReceived" :
475
547
self .identity = next_message .identity
548
+ self .address = next_message .address
476
549
if self ._on_identity :
477
- self ._on_identity (next_message .auth_token , self .identity )
550
+ self ._on_identity (next_message .auth_token , self .identity , self . address )
478
551
else :
479
552
# print(f"next_message: {next_message.transaction_type}")
480
553
# apply all the event state before calling callbacks
@@ -596,14 +669,15 @@ def _do_update(self):
596
669
decode_func = self .client_cache .reducer_cache [
597
670
reducer_event .reducer_name
598
671
]
599
- if reducer_event . status == "committed" :
600
- args = decode_func (reducer_event .args )
672
+
673
+ args = decode_func (reducer_event .args )
601
674
602
675
for reducer_callback in self ._reducer_callbacks [
603
676
reducer_event .reducer_name
604
677
]:
605
678
reducer_callback (
606
679
reducer_event .caller_identity ,
680
+ reducer_event .caller_address ,
607
681
reducer_event .status ,
608
682
reducer_event .message ,
609
683
* args ,
0 commit comments