13
13
# limitations under the License.
14
14
15
15
"""Firebase Realtime Database module.
16
+
16
17
This module contains functions and classes that facilitate interacting with the Firebase Realtime
17
18
Database. It supports basic data manipulation operations, as well as complex queries such as
18
19
limit queries and range queries. However, it does not support realtime update notifications. This
42
43
43
44
def reference (path = '/' , app = None ):
44
45
"""Returns a database Reference representing the node at the specified path.
46
+
45
47
If no path is specified, this function returns a Reference that represents the database root.
48
+
46
49
Args:
47
50
path: Path to a node in the Firebase realtime database (optional).
48
51
app: An App instance (optional).
52
+
49
53
Returns:
50
54
Reference: A newly initialized Reference.
55
+
51
56
Raises:
52
57
ValueError: If the specified path or app is invalid.
53
58
"""
@@ -69,6 +74,7 @@ class Reference(object):
69
74
70
75
def __init__ (self , ** kwargs ):
71
76
"""Creates a new Reference using the provided parameters.
77
+
72
78
This method is for internal use only. Use db.reference() to obtain an instance of
73
79
Reference.
74
80
"""
@@ -97,12 +103,16 @@ def parent(self):
97
103
98
104
def child (self , path ):
99
105
"""Returns a Reference to the specified child node.
106
+
100
107
The path may point to an immediate child of the current Reference, or a deeply nested
101
108
child. Child paths must not begin with '/'.
109
+
102
110
Args:
103
111
path: Path to the child node.
112
+
104
113
Returns:
105
114
Reference: A database Reference representing the specified child node.
115
+
106
116
Raises:
107
117
ValueError: If the child path is not a string, not well-formed or begins with '/'.
108
118
"""
@@ -117,28 +127,32 @@ def child(self, path):
117
127
118
128
def get (self ):
119
129
"""Returns the value at the current location of the database.
130
+
120
131
Returns:
121
132
object: Decoded JSON value of the current database Reference.
133
+
122
134
Raises:
123
135
ApiCallError: If an error occurs while communicating with the remote database server.
124
136
"""
125
137
return self ._client .request ('get' , self ._add_suffix ())
126
138
127
- def get_with_etag (self ):
139
+ def _get_with_etag (self ):
128
140
"""Returns the value at the current location of the database, along with its ETag.
129
- Returns:
130
- object: Tuple of the ETag value corresponding to the Reference, and the
131
- Decoded JSON value of the current database Reference.
132
- Raises:
133
- ApiCallError: If an error occurs while communicating with the remote database server.
134
141
"""
135
- return self ._client .request ('get' , self ._add_suffix (), headers = {'X-Firebase-ETag' : 'true' })
142
+ data , headers = self ._client .request ('get' , self ._add_suffix (),
143
+ headers = {'X-Firebase-ETag' : 'true' },
144
+ resp_headers = True )
145
+ etag = headers .get ('ETag' )
146
+ return etag , data
136
147
137
148
def set (self , value ):
138
149
"""Sets the data at this location to the given value.
150
+
139
151
The value must be JSON-serializable and not None.
152
+
140
153
Args:
141
154
value: JSON-serialable value to be set at this location.
155
+
142
156
Raises:
143
157
ValueError: If the value is None.
144
158
TypeError: If the value is not JSON-serializable.
@@ -150,12 +164,16 @@ def set(self, value):
150
164
151
165
def push (self , value = '' ):
152
166
"""Creates a new child node.
167
+
153
168
The optional value argument can be used to provide an initial value for the child node. If
154
169
no value is provided, child node will have empty string as the default value.
170
+
155
171
Args:
156
172
value: JSON-serializable initial value for the child node (optional).
173
+
157
174
Returns:
158
175
Reference: A Reference representing the newly created child node.
176
+
159
177
Raises:
160
178
ValueError: If the value is None.
161
179
TypeError: If the value is not JSON-serializable.
@@ -169,8 +187,10 @@ def push(self, value=''):
169
187
170
188
def update (self , value ):
171
189
"""Updates the specified child keys of this Reference to the provided values.
190
+
172
191
Args:
173
192
value: A dictionary containing the child keys to update, and their new values.
193
+
174
194
Raises:
175
195
ValueError: If value is empty or not a dictionary.
176
196
ApiCallError: If an error occurs while communicating with the remote database server.
@@ -181,17 +201,8 @@ def update(self, value):
181
201
raise ValueError ('Dictionary must not contain None keys or values.' )
182
202
self ._client .request_oneway ('patch' , self ._add_suffix (), json = value , params = 'print=silent' )
183
203
184
- def update_with_etag (self , value , etag ):
185
- """Updates the specified child keys of this Reference to the provided values
186
- and uses ETag to make sure data is up to date.
187
- Args:
188
- value: A dictionary containing the child keys to update, and their new values.
189
- etag: ETag value for the Reference.
190
- Returns:
191
- value: None if the update is successful, otherwise the current ETag of the reference
192
- and a snapshot of the data in the database.
193
- Raises:
194
- ValueError: If value is empty or not a dictionary, or if etag is not a string.
204
+ def _update_with_etag (self , value , etag ):
205
+ """Sets the data at this location to the specified value, if the etag matches.
195
206
"""
196
207
if not value or not isinstance (value , dict ):
197
208
raise ValueError ('Value argument must be a non-empty dictionary.' )
@@ -200,26 +211,33 @@ def update_with_etag(self, value, etag):
200
211
if not isinstance (etag , str ):
201
212
raise ValueError ('ETag must be a string.' )
202
213
214
+ success = True
215
+ snapshot = value
203
216
try :
204
217
self ._client .request_oneway ('put' , self ._add_suffix (), json = value ,
205
218
headers = {'if-match' : etag })
206
219
except ApiCallError as error :
207
220
detail = error .detail
208
- snapshot = detail . response . json ()
221
+ success = False
209
222
etag = detail .response .headers ['ETag' ]
210
- return etag , snapshot
223
+ snapshot = detail .response .json ()
224
+
225
+ return success , etag , snapshot
211
226
212
227
def delete (self ):
213
228
"""Deleted this node from the database.
229
+
214
230
Raises:
215
231
ApiCallError: If an error occurs while communicating with the remote database server.
216
232
"""
217
233
self ._client .request_oneway ('delete' , self ._add_suffix ())
218
234
219
235
def transaction (self , transaction_update ):
220
236
"""Write to database using a transaction.
237
+
221
238
Args:
222
239
transaction_update: function that takes in current database data as a parameter.
240
+
223
241
Raises:
224
242
ValueError: If transaction_update is not a function.
225
243
@@ -228,25 +246,28 @@ def transaction(self, transaction_update):
228
246
raise ValueError ('transaction_update must be a function.' )
229
247
230
248
tries = 0
231
- etag , data = self .get_with_etag ()
249
+ etag , data = self ._get_with_etag ()
232
250
val = transaction_update (data )
233
251
while tries < _TRANSACTION_MAX_RETRIES :
234
- resp = self .update_with_etag (val , etag )
235
- if resp is None :
252
+ success , etag , snapshot = self ._update_with_etag (val , etag )
253
+ if success :
236
254
break
237
255
else :
238
- etag , data = resp
239
- val = transaction_update (data )
256
+ val = transaction_update (snapshot )
240
257
tries += 1
241
258
242
259
def order_by_child (self , path ):
243
260
"""Returns a Query that orders data by child values.
261
+
244
262
Returned Query can be used to set additional parameters, and execute complex database
245
263
queries (e.g. limit queries, range queries).
264
+
246
265
Args:
247
266
path: Path to a valid child of the current Reference.
267
+
248
268
Returns:
249
269
Query: A database Query instance.
270
+
250
271
Raises:
251
272
ValueError: If the child path is not a string, not well-formed or None.
252
273
"""
@@ -256,17 +277,21 @@ def order_by_child(self, path):
256
277
257
278
def order_by_key (self ):
258
279
"""Creates a Query that orderes data by key.
280
+
259
281
Returned Query can be used to set additional parameters, and execute complex database
260
282
queries (e.g. limit queries, range queries).
283
+
261
284
Returns:
262
285
Query: A database Query instance.
263
286
"""
264
287
return Query (order_by = '$key' , client = self ._client , pathurl = self ._add_suffix ())
265
288
266
289
def order_by_value (self ):
267
290
"""Creates a Query that orderes data by value.
291
+
268
292
Returned Query can be used to set additional parameters, and execute complex database
269
293
queries (e.g. limit queries, range queries).
294
+
270
295
Returns:
271
296
Query: A database Query instance.
272
297
"""
@@ -287,6 +312,7 @@ def _check_priority(cls, priority):
287
312
288
313
class Query (object ):
289
314
"""Represents a complex query that can be executed on a Reference.
315
+
290
316
Complex queries can consist of up to 2 components: a required ordering constraint, and an
291
317
optional filtering constraint. At the server, data is first sorted according to the given
292
318
ordering constraint (e.g. order by child). Then the filtering constraint (e.g. limit, range)
@@ -316,10 +342,13 @@ def __init__(self, **kwargs):
316
342
317
343
def limit_to_first (self , limit ):
318
344
"""Creates a query with limit, and anchors it to the start of the window.
345
+
319
346
Args:
320
347
limit: The maximum number of child nodes to return.
348
+
321
349
Returns:
322
350
Query: The updated Query instance.
351
+
323
352
Raises:
324
353
ValueError: If the value is not an integer, or set_limit_last() was called previously.
325
354
"""
@@ -332,10 +361,13 @@ def limit_to_first(self, limit):
332
361
333
362
def limit_to_last (self , limit ):
334
363
"""Creates a query with limit, and anchors it to the end of the window.
364
+
335
365
Args:
336
366
limit: The maximum number of child nodes to return.
367
+
337
368
Returns:
338
369
Query: The updated Query instance.
370
+
339
371
Raises:
340
372
ValueError: If the value is not an integer, or set_limit_first() was called previously.
341
373
"""
@@ -348,12 +380,16 @@ def limit_to_last(self, limit):
348
380
349
381
def start_at (self , start ):
350
382
"""Sets the lower bound for a range query.
383
+
351
384
The Query will only return child nodes with a value greater than or equal to the specified
352
385
value.
386
+
353
387
Args:
354
388
start: JSON-serializable value to start at, inclusive.
389
+
355
390
Returns:
356
391
Query: The updated Query instance.
392
+
357
393
Raises:
358
394
ValueError: If the value is empty or None.
359
395
"""
@@ -364,12 +400,16 @@ def start_at(self, start):
364
400
365
401
def end_at (self , end ):
366
402
"""Sets the upper bound for a range query.
403
+
367
404
The Query will only return child nodes with a value less than or equal to the specified
368
405
value.
406
+
369
407
Args:
370
408
end: JSON-serializable value to end at, inclusive.
409
+
371
410
Returns:
372
411
Query: The updated Query instance.
412
+
373
413
Raises:
374
414
ValueError: If the value is empty or None.
375
415
"""
@@ -380,11 +420,15 @@ def end_at(self, end):
380
420
381
421
def equal_to (self , value ):
382
422
"""Sets an equals constraint on the Query.
423
+
383
424
The Query will only return child nodes whose value is equal to the specified value.
425
+
384
426
Args:
385
427
value: JSON-serializable value to query for.
428
+
386
429
Returns:
387
430
Query: The updated Query instance.
431
+
388
432
Raises:
389
433
ValueError: If the value is empty or None.
390
434
"""
@@ -402,9 +446,12 @@ def _querystr(self):
402
446
403
447
def get (self ):
404
448
"""Executes this Query and returns the results.
449
+
405
450
The results will be returned as a sorted list or an OrderedDict.
451
+
406
452
Returns:
407
453
object: Decoded JSON result of the Query.
454
+
408
455
Raises:
409
456
ApiCallError: If an error occurs while communicating with the remote database server.
410
457
"""
@@ -483,6 +530,7 @@ def value(self):
483
530
@classmethod
484
531
def _get_index_type (cls , index ):
485
532
"""Assigns an integer code to the type of the index.
533
+
486
534
The index type determines how differently typed values are sorted. This ordering is based
487
535
on https://firebase.google.com/docs/database/rest/retrieve-data#section-rest-ordered-data
488
536
"""
@@ -512,6 +560,7 @@ def _extract_child(cls, value, path):
512
560
513
561
def _compare (self , other ):
514
562
"""Compares two _SortEntry instances.
563
+
515
564
If the indices have the same numeric or string type, compare them directly. Ties are
516
565
broken by comparing the keys. If the indices have the same type, but are neither numeric
517
566
nor string, compare the keys. In all other cases compare based on the ordering provided
@@ -549,14 +598,17 @@ def __eq__(self, other):
549
598
550
599
class _Client (object ):
551
600
"""HTTP client used to make REST calls.
601
+
552
602
_Client maintains an HTTP session, and handles authenticating HTTP requests along with
553
603
marshalling and unmarshalling of JSON data.
554
604
"""
555
605
556
606
def __init__ (self , ** kwargs ):
557
607
"""Creates a new _Client from the given parameters.
608
+
558
609
This exists primarily to enable testing. For regular use, obtain _Client instances by
559
610
calling the from_app() class method.
611
+
560
612
Keyword Args:
561
613
url: Firebase Realtime Database URL.
562
614
session: An HTTP session created using the requests module.
@@ -602,9 +654,10 @@ def from_app(cls, app):
602
654
session = session , auth_override = auth_override )
603
655
604
656
def request (self , method , urlpath , ** kwargs ):
657
+ resp_headers = kwargs .pop ('resp_headers' , False )
605
658
resp = self ._do_request (method , urlpath , ** kwargs )
606
- if 'headers' in kwargs and kwargs [ 'headers' ]. get ( 'X-Firebase-ETag' ) == 'true' :
607
- return resp .headers [ 'ETag' ] , resp .json ()
659
+ if resp_headers :
660
+ return resp .json () , resp .headers
608
661
else :
609
662
return resp .json ()
610
663
@@ -619,10 +672,9 @@ def _do_request(self, method, urlpath, **kwargs):
619
672
620
673
Args:
621
674
method: HTTP method name as a string (e.g. get, post).
622
- urlpath: URL path of the remote endpoint. This will be appended to the server's
623
- base URL.
624
- kwargs: An additional set of keyword arguments to be passed into requests
625
- (e.g. json, params).
675
+ urlpath: URL path of the remote endpoint. This will be appended to the server's base URL.
676
+ kwargs: An additional set of keyword arguments to be passed into requests API
677
+ (e.g. json, params).
626
678
627
679
Returns:
628
680
Response: An HTTP response object.
0 commit comments