-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathfirebase.py
205 lines (170 loc) · 8.33 KB
/
firebase.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
import os, sys, json, datetime, copy, uuid, re
from firebase_admin import db, storage, credentials, initialize_app
from dotenv import load_dotenv
load_dotenv()
class FireConn:
'''A class that manages the admin connection to Firebase via the firebase_admin module.
Explicit permission has to be granted by setting `FireConnEnabled` to `True` in the .env file. `serviceAccountKey.json` file must be in working directory to provide credentials for the connection. Obtain one on the Firebase Console (under Service Accounts > Firebase Admin SDK > Generate new private key).
Usage:
```
response = FireConn.connect()
if response != True:
print("Error in setting up FireConn; error: " + response)
sys.exit(1)
```
NOTE: This class is not meant to be instantiated. Other services relying on connection via firebase_admin module need to run the `connect` method in this class first. If permission is not granted, dependant services may not be able to operate.
'''
connected = False
@staticmethod
def checkPermissions():
return ("FireConnEnabled" in os.environ and os.environ["FireConnEnabled"] == "True")
@staticmethod
def connect():
'''Returns True upon successful connection.'''
if not FireConn.checkPermissions():
return "ERROR: Firebase connection permissions are not granted."
if not os.path.exists("serviceAccountKey.json"):
return "ERROR: Failed to connect to Firebase. The file serviceAccountKey.json was not found. Please re-read instructions for the Firebase addon."
else:
if 'RTDB_URL' not in os.environ:
return "ERROR: Failed to connect to Firebase. RTDB_URL environment variable not set in .env file. Please re-read instructions for the Firebase addon."
try:
## Firebase
cred_obj = credentials.Certificate(os.path.join(os.getcwd(), "serviceAccountKey.json"))
default_app = initialize_app(cred_obj, {
'databaseURL': os.environ["RTDB_URL"],
'httpTimeout': 5
})
FireConn.connected = True
except Exception as e:
return "ERROR: Error occurred in connecting to RTDB; error: {}".format(e)
return True
class FireRTDB:
'''A class to update Firebase Realtime Database (RTDB) references with data.
Explicit permission has to be granted by setting `FireRTDBEnabled` to `True` in the .env file.
Usage:
```
if FireRTDB.checkPermissions():
data = {"name": "John Appleseed"}
FireRTDB.setRef(data, refPath="/")
fetchedData = FireRTDB.getRef(refPath="/")
print(fetchedData) ## same as data defined above
```
Advanced Usage:
```
## DB translation
db = {"jobs": {}}
safeForCloudDB = FireRTDB.translateForCloud(db) ## {"jobs": 0}
# safeForLocalDB = FireRTDB.translateForLocal(safeForCloudDB) ## {"jobs": {}}
```
`FireRTDB.translateForCloud` and `FireRTDB.translateForLocal` are used to translate data structures for cloud and local storage respectively. This is because Firebase does not allow null objects to be stored in the RTDB. This method converts null objects to a value that can be stored in the RTDB. The following conversions are performed:
- Converts `{}` to `0` and `[]` to `1` (for cloud storage)
- Converts `0` to `{}` and `1` to `[]` (for local storage)
NOTE: This class is not meant to be instantiated. `FireConn.connect()` method must be executed before executing any other methods in this class.
'''
@staticmethod
def checkPermissions():
'''Returns True if permission is granted, otherwise returns False.'''
if 'FireRTDBEnabled' in os.environ and os.environ['FireRTDBEnabled'] == 'True':
return True
return False
@staticmethod
def clearRef(refPath="/"):
'''Returns True upon successful update. Providing `refPath` is optional; will be the root reference if not provided.'''
if not FireRTDB.checkPermissions():
return "ERROR: FireRTDB service operation permission denied."
try:
ref = db.reference(refPath)
ref.delete()
except Exception as e:
return "ERROR: Error occurred in clearing children at that ref; error: {}".format(e)
return True
@staticmethod
def setRef(data, refPath="/"):
'''Returns True upon successful update. Providing `refPath` is optional; will be the root reference if not provided.'''
if not FireRTDB.checkPermissions():
raise Exception("ERROR: FireRTDB service operation permission denied.")
try:
ref = db.reference(refPath)
ref.set(data)
except Exception as e:
raise Exception("ERROR: Error occurred in setting data at that ref; error: {}".format(e))
return True
@staticmethod
def getRef(refPath="/"):
'''Returns a dictionary of the data at the specified ref. Providing `refPath` is optional; will be the root reference if not provided.'''
if not FireRTDB.checkPermissions():
raise Exception("ERROR: FireRTDB service operation permission denied.")
data = None
try:
ref = db.reference(refPath)
data = ref.get()
except Exception as e:
raise Exception("ERROR: Error occurred in getting data from that ref; error: {}".format(e))
return data
@staticmethod
def recursiveReplacement(obj, purpose):
dictValue = {} if purpose == 'cloud' else 0
dictReplacementValue = 0 if purpose == 'cloud' else {}
arrayValue = [] if purpose == 'cloud' else 1
arrayReplacementValue = 1 if purpose == 'cloud' else []
data = copy.deepcopy(obj)
for key in data:
if isinstance(data, list):
# This if statement forbids the following sub-data-structure: [{}, 1, {}] (this is an example)
continue
if isinstance(data[key], dict):
if data[key] == dictValue:
data[key] = dictReplacementValue
else:
data[key] = FireRTDB.recursiveReplacement(data[key], purpose)
elif isinstance(data[key], list):
if data[key] == arrayValue:
data[key] = arrayReplacementValue
else:
data[key] = FireRTDB.recursiveReplacement(data[key], purpose)
elif isinstance(data[key], bool):
continue
elif isinstance(data[key], int) and purpose == 'local':
if data[key] == 0:
data[key] = {}
elif data[key] == 1:
data[key] = []
return data
@staticmethod
def translateForLocal(fetchedData, rootTranslatable=False):
'''Returns a translated data structure that can be stored locally.'''
if fetchedData == None:
return None
if rootTranslatable:
if fetchedData == 0:
return {}
elif fetchedData == 1:
return []
elif not (isinstance(fetchedData, dict) or isinstance(fetchedData, list)):
return fetchedData
tempData = copy.deepcopy(fetchedData)
try:
# Null object replacement
tempData = FireRTDB.recursiveReplacement(obj=tempData, purpose='local')
# Further translation here
except Exception as e:
return "ERROR: Error in translating fetched RTDB data for local system use; error: {}".format(e)
return tempData
@staticmethod
def translateForCloud(loadedData, rootTranslatable=False):
'''Returns a translated data structure that can be stored in the cloud.'''
if loadedData == None:
return None
if rootTranslatable:
if loadedData == {}:
return 0
elif loadedData == []:
return 1
elif not (isinstance(loadedData, dict) or isinstance(loadedData, list)):
return loadedData
tempData = copy.deepcopy(loadedData)
# Further translation here
# Null object replacement
tempData = FireRTDB.recursiveReplacement(obj=tempData, purpose='cloud')
return tempData