empyrebase 1.0.0__tar.gz

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,19 @@
1
+ Metadata-Version: 2.1
2
+ Name: empyrebase
3
+ Version: 1.0.0
4
+ Summary: A simple python wrapper for the Firebase API with current deps
5
+ Home-page: https://github.com/emrothenberg/empyrebase
6
+ Author: emrothenberg
7
+ License: MIT
8
+ Keywords: Firebase,Pyrebase,Google,Empyrebase
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3.4
13
+ Requires-Dist: requests-toolbelt>=1.0.0
14
+ Requires-Dist: requests>=2.31
15
+ Requires-Dist: urllib3<2,>=1.21.1
16
+ Requires-Dist: google-cloud-storage>=2.18.2
17
+ Requires-Dist: oauth2client>=4.1.2
18
+ Requires-Dist: pyjwt>=2.8.0
19
+ Requires-Dist: pycryptodome>=3.6.4
@@ -0,0 +1,566 @@
1
+ # Empyrebase
2
+
3
+ A simple python wrapper for the [Firebase API](https://firebase.google.com).
4
+
5
+ ## Installation
6
+
7
+ ```shell
8
+ pip install empyrebase
9
+ ```
10
+
11
+ ## Getting Started
12
+
13
+ ### Python Version
14
+
15
+ Empyrebase was written for python 3 and will not work correctly with python 2.
16
+
17
+ ### Add empyrebase to your application
18
+
19
+ For use with only user based authentication we can create the following configuration:
20
+
21
+ ```python
22
+ import empyrebase
23
+
24
+ config = {
25
+ "apiKey": "apiKey",
26
+ "authDomain": "projectId.firebaseapp.com",
27
+ "databaseURL": "https://databaseName.firebaseio.com",
28
+ "storageBucket": "projectId.appspot.com"
29
+ }
30
+
31
+ firebase = empyrebase.initialize_app(config)
32
+ ```
33
+
34
+ We can optionally add a [service account credential](https://firebase.google.com/docs/server/setup#prerequisites) to our
35
+ configuration that will allow our server to authenticate with Firebase as an admin and disregard any security rules.
36
+
37
+ ```python
38
+ import empyrebase
39
+
40
+ config = {
41
+ "apiKey": "apiKey",
42
+ "authDomain": "projectId.firebaseapp.com",
43
+ "databaseURL": "https://databaseName.firebaseio.com",
44
+ "storageBucket": "projectId.appspot.com",
45
+ "serviceAccount": "path/to/serviceAccountCredentials.json"
46
+ }
47
+
48
+ firebase = empyrebase.initialize_app(config)
49
+ ```
50
+
51
+ Adding a service account will authenticate as an admin by default for all database queries, check out the
52
+ [Authentication documentation](#authentication) for how to authenticate users.
53
+
54
+ ### Use Services
55
+
56
+ An empyrebase app can use multiple Firebase services.
57
+
58
+ ```firebase.auth()``` - [Authentication](#authentication)
59
+
60
+ ```firebase.database()``` - [Database](#database)
61
+
62
+ ```firebase.storage()``` - [Storage](#storage)
63
+
64
+ ```firebase.firestore()``` - [Firestore](#firestore)
65
+
66
+ Check out the documentation for each service for further details.
67
+
68
+ ## Authentication
69
+
70
+ The ```sign_in_with_email_and_password()``` method will return user data including a token you can use to adhere to security rules.
71
+
72
+ Each of the following methods accepts a user token: ```get()```, ```push()```, ```set()```, ```update()```, ```remove()``` and ```stream()```.
73
+
74
+ ```python
75
+ # Get a reference to the auth service
76
+ auth = firebase.auth()
77
+
78
+ # Log the user in
79
+ user = auth.sign_in_with_email_and_password(email, password)
80
+
81
+ # Log the user in anonymously
82
+ user = auth.sign_in_anonymous()
83
+
84
+ # Add user info
85
+ user = auth.update_profile(display_name, photo_url, delete_attribute)
86
+
87
+ # Get user info
88
+ user = auth.get_account_info()
89
+
90
+ # Get a reference to the database service
91
+ db = firebase.database()
92
+
93
+ # data to save
94
+ data = {
95
+ "name": "Mortimer 'Morty' Smith"
96
+ }
97
+
98
+ # Pass the user's idToken to the push method
99
+ results = db.child("users").push(data, user['idToken'])
100
+ ```
101
+
102
+ ### Token expiry
103
+
104
+ A user's idToken expires after 1 hour, so be sure to use the user's refreshToken to avoid stale tokens.
105
+ ```
106
+ user = auth.sign_in_with_email_and_password(email, password)
107
+ # before the 1 hour expiry:
108
+ user = auth.refresh(user['refreshToken'])
109
+ # now we have a fresh token
110
+ user['idToken']
111
+ ```
112
+
113
+ ### Custom tokens
114
+
115
+ You can also create users using [custom tokens](https://firebase.google.com/docs/auth/server/create-custom-tokens), for example:
116
+ ```
117
+ token = auth.create_custom_token("your_custom_id")
118
+ ```
119
+ You can also pass in additional claims.
120
+ ```
121
+ token_with_additional_claims = auth.create_custom_token("your_custom_id", {"premium_account": True})
122
+ ```
123
+ You can then send these tokens to the client to sign in, or sign in as the user on the server.
124
+ ```
125
+ user = auth.sign_in_with_custom_token(token)
126
+ ```
127
+
128
+ ### Manage Users
129
+
130
+ #### Creating users
131
+
132
+ ```python
133
+ auth.create_user_with_email_and_password(email, password)
134
+ ```
135
+ Note: Make sure you have the Email/password provider enabled in your Firebase dashboard under Auth -> Sign In Method.
136
+
137
+ #### Verifying emails
138
+
139
+ ```python
140
+ auth.send_email_verification(user['idToken'])
141
+ ```
142
+
143
+ #### Sending password reset emails
144
+
145
+ ```python
146
+ auth.send_password_reset_email("email")
147
+ ```
148
+
149
+ #### Get account information
150
+ ```python
151
+ auth.get_account_info(user['idToken'])
152
+ ```
153
+
154
+ #### Refreshing tokens
155
+ ```python
156
+ user = auth.refresh(user['refreshToken'])
157
+ ```
158
+ #### Delete account
159
+ ```python
160
+ auth.delete_user_account(user['idToken'])
161
+ ```
162
+
163
+ ## Database
164
+
165
+ You can build paths to your data by using the ```child()``` method.
166
+
167
+ ```python
168
+ db = firebase.database()
169
+ db.child("users").child("Morty")
170
+ ```
171
+
172
+ ### Save Data
173
+
174
+ #### push
175
+
176
+ To save data with a unique, auto-generated, timestamp-based key, use the ```push()``` method.
177
+
178
+ ```python
179
+ data = {"name": "Mortimer 'Morty' Smith"}
180
+ db.child("users").push(data)
181
+ ```
182
+
183
+ #### set
184
+
185
+ To create your own keys use the ```set()``` method. The key in the example below is "Morty".
186
+
187
+ ```python
188
+ data = {"name": "Mortimer 'Morty' Smith"}
189
+ db.child("users").child("Morty").set(data)
190
+ ```
191
+
192
+ #### update
193
+
194
+ To update data for an existing entry use the ```update()``` method.
195
+
196
+ ```python
197
+ db.child("users").child("Morty").update({"name": "Mortiest Morty"})
198
+ ```
199
+
200
+ #### remove
201
+
202
+ To delete data for an existing entry use the ```remove()``` method.
203
+
204
+ ```python
205
+ db.child("users").child("Morty").remove()
206
+ ```
207
+
208
+ #### multi-location updates
209
+
210
+ You can also perform [multi-location updates](https://www.firebase.com/blog/2015-09-24-atomic-writes-and-more.html) with the ```update()``` method.
211
+
212
+ ```python
213
+ data = {
214
+ "users/Morty/": {
215
+ "name": "Mortimer 'Morty' Smith"
216
+ },
217
+ "users/Rick/": {
218
+ "name": "Rick Sanchez"
219
+ }
220
+ }
221
+
222
+ db.update(data)
223
+ ```
224
+
225
+ To perform multi-location writes to new locations we can use the ```generate_key()``` method.
226
+
227
+ ```python
228
+ data = {
229
+ "users/"+ref.generate_key(): {
230
+ "name": "Mortimer 'Morty' Smith"
231
+ },
232
+ "users/"+ref.generate_key(): {
233
+ "name": "Rick Sanchez"
234
+ }
235
+ }
236
+
237
+ db.update(data)
238
+ ```
239
+ #### Conditional Requests
240
+
241
+ It's possible to do conditional sets and removes by using the `conditional_set()` and `conitional_remove()` methods respectively. You can read more about conditional requests in Firebase [here](https://firebase.google.com/docs/reference/rest/database/#section-conditional-requests).
242
+
243
+ To use these methods, you first get the ETag of a particular path by using the `get_etag()` method. You can then use that tag in your conditional request.
244
+
245
+ ```python
246
+ etag = db.child("users").child("Morty").get_etag()
247
+ data = {"name": "Mortimer 'Morty' Smith"}
248
+ db.child("users").child("Morty").conditional_set(data, etag["ETag"])
249
+ ```
250
+
251
+ If the passed ETag does not match the ETag of the path in the database, the data will not be written, and both conditional request methods will return a single key-value pair with the new ETag to use of the following form:
252
+
253
+ ```json
254
+ { "ETag": "8KnE63B6HiKp67Wf3HQrXanujSM=", "value": "<current value>" }
255
+ ```
256
+
257
+ Here's an example of checking whether or not a conditional removal was successful:
258
+
259
+ ```python
260
+ etag = db.child("users").child("Morty").get_etag()
261
+ response = db.child("users").child("Morty").conditional_remove(etag["ETag"])
262
+ if type(response) is dict and "ETag" in response:
263
+ etag = response["ETag"] # our ETag was out-of-date
264
+ else:
265
+ print("We removed the data successfully!")
266
+ ```
267
+ Here's an example of looping to increase age by 1:
268
+
269
+ ```python
270
+ etag = db.child("users").child("Morty").child("age").get_etag()
271
+ while type(etag) is dict and "ETag" in etag:
272
+ new_age = etag["value"] + 1
273
+ etag = db.child("users").child("Morty").child("age").conditional_set(new_age, etag["ETag"])
274
+ ```
275
+
276
+ ### Retrieve Data
277
+
278
+ #### val
279
+ Queries return a PyreResponse object. Calling ```val()``` on these objects returns the query data.
280
+
281
+ ```
282
+ users = db.child("users").get()
283
+ print(users.val()) # {"Morty": {"name": "Mortimer 'Morty' Smith"}, "Rick": {"name": "Rick Sanchez"}}
284
+ ```
285
+
286
+ #### key
287
+ Calling ```key()``` returns the key for the query data.
288
+
289
+ ```
290
+ user = db.child("users").get()
291
+ print(user.key()) # users
292
+ ```
293
+
294
+ #### each
295
+ Returns a list of objects on each of which you can call ```val()``` and ```key()```.
296
+
297
+ ```
298
+ all_users = db.child("users").get()
299
+ for user in all_users.each():
300
+ print(user.key()) # Morty
301
+ print(user.val()) # {"name": "Mortimer 'Morty' Smith"}
302
+ ```
303
+
304
+ #### get
305
+
306
+ To return data from a path simply call the ```get()``` method.
307
+
308
+ ```python
309
+ all_users = db.child("users").get()
310
+ ```
311
+
312
+ #### shallow
313
+
314
+ To return just the keys at a particular path use the ```shallow()``` method.
315
+
316
+ ```python
317
+ all_user_ids = db.child("users").shallow().get()
318
+ ```
319
+
320
+ Note: ```shallow()``` can not be used in conjunction with any complex queries.
321
+
322
+ #### streaming
323
+
324
+ You can listen to live changes to your data with the ```stream()``` method.
325
+
326
+ ```python
327
+ def stream_handler(message):
328
+ print(message["event"]) # put
329
+ print(message["path"]) # /-K7yGTTEp7O549EzTYtI
330
+ print(message["data"]) # {'title': 'empyrebase', "body": "etc..."}
331
+
332
+ my_stream = db.child("posts").stream(stream_handler)
333
+ ```
334
+
335
+ You should at least handle `put` and `patch` events. Refer to ["Streaming from the REST API"][streaming] for details.
336
+
337
+ [streaming]: https://firebase.google.com/docs/reference/rest/database/#section-streaming
338
+
339
+ You can also add a ```stream_id``` to help you identify a stream if you have multiple running:
340
+
341
+ ```
342
+ my_stream = db.child("posts").stream(stream_handler, stream_id="new_posts")
343
+ ```
344
+
345
+ #### close the stream
346
+
347
+ ```python
348
+ my_stream.close()
349
+ ```
350
+
351
+ ### Complex Queries
352
+
353
+ Queries can be built by chaining multiple query parameters together.
354
+
355
+ ```python
356
+ users_by_name = db.child("users").order_by_child("name").limit_to_first(3).get()
357
+ ```
358
+ This query will return the first three users ordered by name.
359
+
360
+ #### order_by_child
361
+
362
+ We begin any complex query with ```order_by_child()```.
363
+
364
+ ```python
365
+ users_by_name = db.child("users").order_by_child("name").get()
366
+ ```
367
+ This query will return users ordered by name.
368
+
369
+ #### equal_to
370
+
371
+ Return data with a specific value.
372
+
373
+ ```python
374
+ users_by_score = db.child("users").order_by_child("score").equal_to(10).get()
375
+ ```
376
+ This query will return users with a score of 10.
377
+
378
+ #### start_at and end_at
379
+
380
+ Specify a range in your data.
381
+
382
+ ```python
383
+ users_by_score = db.child("users").order_by_child("score").start_at(3).end_at(10).get()
384
+ ```
385
+ This query returns users ordered by score and with a score between 3 and 10.
386
+
387
+ #### limit_to_first and limit_to_last
388
+
389
+ Limits data returned.
390
+
391
+ ```python
392
+ users_by_score = db.child("users").order_by_child("score").limit_to_first(5).get()
393
+ ```
394
+ This query returns the first five users ordered by score.
395
+
396
+ #### order_by_key
397
+
398
+ When using ```order_by_key()``` to sort your data, data is returned in ascending order by key.
399
+
400
+ ```python
401
+ users_by_key = db.child("users").order_by_key().get()
402
+ ```
403
+
404
+ #### order_by_value
405
+
406
+ When using ```order_by_value()```, children are ordered by their value.
407
+
408
+ ```python
409
+ users_by_value = db.child("users").order_by_value().get()
410
+ ```
411
+
412
+
413
+ ## Storage
414
+
415
+ The storage service allows you to upload images to Firebase.
416
+
417
+ ### child
418
+
419
+ Just like with the Database service, you can build paths to your data with the Storage service.
420
+
421
+ ```python
422
+ storage.child("images/example.jpg")
423
+ ```
424
+
425
+ ### put
426
+
427
+ The put method takes the path to the local file and an optional user token.
428
+
429
+ ```python
430
+ storage = firebase.storage()
431
+ # as admin
432
+ storage.child("images/example.jpg").put("example2.jpg")
433
+ # as user
434
+ storage.child("images/example.jpg").put("example2.jpg", user['idToken'])
435
+ ```
436
+
437
+ ### download
438
+
439
+ The download method takes the path to the saved database file and the name you want the downloaded file to have.
440
+
441
+ ```
442
+ storage.child("images/example.jpg").download("downloaded.jpg")
443
+ ```
444
+
445
+ ### get_url
446
+
447
+ The get_url method takes the path to the saved database file and user token which returns the storage url.
448
+
449
+ ```
450
+ storage.child("images/example.jpg").get_url(user["idToken"])
451
+ # https://firebasestorage.googleapis.com/v0/b/storage-url.appspot.com/o/images%2Fexample.jpg?alt=media
452
+ ```
453
+
454
+ ### delete
455
+
456
+ The delete method takes the path to the saved database file and user token.
457
+ ```
458
+ storage.delete("images/example.jpg",user["idToken"])
459
+ ```
460
+
461
+ ### Helper Methods
462
+
463
+ #### generate_key
464
+
465
+ ```db.generate_key()``` is an implementation of Firebase's [key generation algorithm](https://www.firebase.com/blog/2015-02-11-firebase-unique-identifiers.html).
466
+
467
+ See multi-location updates for a potential use case.
468
+
469
+ #### sort
470
+
471
+ Sometimes we might want to sort our data multiple times. For example, we might want to retrieve all articles written between a
472
+ certain date then sort those articles based on the number of likes.
473
+
474
+ Currently the REST API only allows us to sort our data once, so the ```sort()``` method bridges this gap.
475
+
476
+ ```python
477
+ articles = db.child("articles").order_by_child("date").start_at(startDate).end_at(endDate).get()
478
+ articles_by_likes = db.sort(articles, "likes")
479
+ ```
480
+
481
+ ### Common Errors
482
+
483
+ #### Index not defined
484
+
485
+ +Indexing is [not enabled](https://firebase.google.com/docs/database/security/indexing-data) for the database reference.
486
+
487
+ ## Firestore
488
+
489
+ The `firestore` method in empyrebase allows interaction with Firebase Firestore.
490
+
491
+ ### Initialize Firestore
492
+
493
+ Initialize Firestore with required project and authentication parameters.
494
+
495
+ ```python
496
+ from empyrebase import Firestore
497
+
498
+ firebase_path = "your_firestore_path" # Optional. Base path for all Firestore operations. Defaults to "/"
499
+ auth_id = "your_auth_id_token" # Optional. Enables authorized transactions.
500
+ database_name = "your_database_name" # Optional. defaults to "(default)"
501
+
502
+
503
+ firestore = firebase.firestore(firebase_path=firebase_path, database_name=database_name, auth_id=auth_id)
504
+ ```
505
+
506
+ ### Authorization
507
+ Authorize Firestore using an authentication token. Firestore can be authorized at any time.
508
+ ```python
509
+ firestore.authorize("auth_id_token")
510
+ ```
511
+
512
+ ### CRUD Operations
513
+ #### Create a Document
514
+ Creates a new document.
515
+ Alias to: [`firestore.update_document("collection/document", {})`](#update-a-document)
516
+ ```python
517
+ data = {"name": "John Doe", "age": 30} # Optional
518
+ firestore.create_document("users/user_id", data)
519
+ # Alternatively, updating a non-existing document will create a new one.
520
+ firestore.create_document("users/user_id", data) # Data is required, but can be an empty dictionary
521
+ ```
522
+
523
+ #### Retrieve a Document
524
+ Fetches a document.
525
+
526
+ ```python
527
+ document = firestore.get_document("users/user_id")
528
+ print(document)
529
+ ```
530
+
531
+ #### Batch Get Documents
532
+ Fetch multiple documents in one batch.
533
+ ```python
534
+ documents = firestore.batch_get_documents(["users/user_id1", "users/user_id2"])
535
+ ```
536
+
537
+ #### Run Query
538
+ Run a structured query against a collection.
539
+ ```python
540
+ query = {"field": "name", "value": "John Doe"}
541
+ results = firestore.run_query("users", query)
542
+ ```
543
+
544
+ #### Update a Document
545
+
546
+ Update data in an existing document. If the document does not exist, it will be created with the new data.
547
+
548
+ ```python
549
+ firestore.update_document("users/user_id", {"age": 31})
550
+ ```
551
+
552
+ #### Delete a Document
553
+
554
+ Deletes a document.
555
+
556
+ ```python
557
+ firestore.delete_document("users/user_id")
558
+ ```
559
+
560
+ #### List Documents
561
+
562
+ Lists all documents in a collection.
563
+
564
+ ```python
565
+ documents = firestore.list_documents("users")
566
+ ```
@@ -0,0 +1 @@
1
+ from .pyrebase import initialize_app
@@ -0,0 +1,51 @@
1
+ import requests
2
+ from requests.adapters import HTTPAdapter
3
+
4
+ from oauth2client.service_account import ServiceAccountCredentials
5
+
6
+ from .services import Auth, Database, Firestore, Storage
7
+
8
+
9
+ def initialize_app(config):
10
+ return Firebase(config)
11
+
12
+
13
+ class Firebase:
14
+ """ Firebase Interface """
15
+ def __init__(self, config):
16
+ self.api_key = config["apiKey"]
17
+ self.auth_domain = config["authDomain"]
18
+ self.database_url = config["databaseURL"]
19
+ self.storage_bucket = config["storageBucket"]
20
+ self.project_id = config["projectId"]
21
+ self.credentials = None
22
+ self.requests = requests.Session()
23
+ if config.get("serviceAccount"):
24
+ scopes = [
25
+ 'https://www.googleapis.com/auth/firebase.database',
26
+ 'https://www.googleapis.com/auth/userinfo.email',
27
+ "https://www.googleapis.com/auth/cloud-platform",
28
+ "https://firebasestorage.googleapis.com/",
29
+ ]
30
+ service_account_type = type(config["serviceAccount"])
31
+ if service_account_type is str:
32
+ self.credentials = ServiceAccountCredentials.from_json_keyfile_name(config["serviceAccount"], scopes)
33
+ if service_account_type is dict:
34
+ self.credentials = ServiceAccountCredentials.from_json_keyfile_dict(config["serviceAccount"], scopes)
35
+
36
+ adapter = HTTPAdapter()
37
+
38
+ for scheme in ('http://', 'https://'):
39
+ self.requests.mount(scheme, adapter)
40
+
41
+ def auth(self):
42
+ return Auth(self.api_key, self.requests, self.credentials)
43
+
44
+ def database(self):
45
+ return Database(self.credentials, self.api_key, self.database_url, self.requests)
46
+
47
+ def firestore(self, firebase_path: str="/", database_name="(default)", auth_id: str | None=None):
48
+ return Firestore(self.requests, self.project_id, firebase_path, database_name, auth_id)
49
+
50
+ def storage(self):
51
+ return Storage(self.credentials, self.storage_bucket, self.requests)
@@ -0,0 +1,4 @@
1
+ from .auth import Auth
2
+ from .database import Database
3
+ from .firestore import Firestore
4
+ from .storage import Storage