Skip to content

Commit ec710ab

Browse files
feat(firestore): add add() method
1 parent a823618 commit ec710ab

File tree

2 files changed

+141
-2
lines changed

2 files changed

+141
-2
lines changed

firebase/firestore/__init__.py

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
from google.cloud.firestore import Client
1818
from google.cloud.firestore_v1._helpers import *
1919

20-
from ._utils import _from_datastore
20+
from ._utils import _from_datastore, _to_datastore
2121
from firebase._exception import raise_detailed_error
2222

2323

@@ -96,6 +96,50 @@ def __init__(self, collection_path, api_key, credentials, project_id, requests):
9696
if self._credentials:
9797
self.__datastore = Client(credentials=self._credentials, project=self._project_id)
9898

99+
def add(self, data, token=None):
100+
""" Create a document in the Firestore database with the
101+
provided data using an auto generated ID for the document.
102+
103+
104+
:type data: dict
105+
:param data: Data to be stored in firestore.
106+
107+
:type token: str
108+
:param token: (Optional) Firebase Auth User ID Token, defaults
109+
to :data:`None`.
110+
111+
112+
:return: returns the auto generated document ID, used to store
113+
the data.
114+
:rtype: str
115+
"""
116+
117+
path = self._path.copy()
118+
self._path.clear()
119+
120+
if self._credentials:
121+
db_ref = _build_db(self.__datastore, path)
122+
123+
response = db_ref.add(data)
124+
125+
return response[1].id
126+
127+
else:
128+
req_ref = f"{self._base_url}/{'/'.join(path)}?key={self._api_key}"
129+
130+
if token:
131+
headers = {"Authorization": "Firebase " + token}
132+
response = self._requests.post(req_ref, headers=headers, json=_to_datastore(data))
133+
134+
else:
135+
response = self._requests.post(req_ref, json=_to_datastore(data))
136+
137+
raise_detailed_error(response)
138+
139+
doc_id = response.json()['name'].split('/')
140+
141+
return doc_id.pop()
142+
99143
def document(self, document_id):
100144
""" A reference to a document in a collection.
101145

firebase/firestore/_utils.py

Lines changed: 96 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
# --------------------------------------------------------------------------------------
66

77

8-
from base64 import b64decode
8+
from datetime import datetime
9+
from base64 import b64encode, b64decode
10+
from google.protobuf.json_format import MessageToDict
911
from google.cloud.firestore_v1._helpers import GeoPoint
1012
from google.api_core.datetime_helpers import DatetimeWithNanoseconds
1113

@@ -92,3 +94,96 @@ def _decode_datastore(value):
9294

9395
else:
9496
raise TypeError("Cannot convert to a Python Value", value, "Invalid type", type(value))
97+
98+
99+
def _to_datastore(data):
100+
""" Converts a Python dictionary ``data``-s to map of Firestore.
101+
102+
103+
:type data: dict
104+
:param data: A Python dictionary containing data.
105+
106+
107+
:return: A map of Firebase values converted from the ``data``.
108+
:rtype: dict
109+
110+
:raises ValueError: Raised when a key in the python dictionary is
111+
Non-alphanum char without *`* (ticks) at start and end.
112+
"""
113+
114+
restructured_data = {}
115+
116+
for key, val in data.items():
117+
118+
if ' ' in key and (not key.startswith('`') or not key.endswith('`')):
119+
raise ValueError(f'Non-alphanum char in element with leading alpha: {key}')
120+
121+
key = str(key)
122+
123+
if isinstance(val, dict):
124+
restructured_data[key] = {'mapValue': _to_datastore(val)}
125+
126+
elif isinstance(val, list):
127+
arr = []
128+
129+
for x in val:
130+
arr.append(_encode_datastore_value(x))
131+
132+
restructured_data[key] = {'arrayValue': {'values': arr}}
133+
134+
else:
135+
restructured_data[key] = _encode_datastore_value(val)
136+
137+
return {'fields': restructured_data}
138+
139+
140+
def _encode_datastore_value(value):
141+
""" Converts a Python ``value`` to a Firebase value.
142+
143+
144+
:type value: :data:`None` or :class:`bool` or :class:`bytes`
145+
or :class:`int` or :class:`float` or :class:`str` or
146+
:class:`dict` or :class:`~datetime.datetime` or
147+
:class:`~google.api_core.datetime_helpers.DatetimeWithNanoseconds`
148+
or :class:`~google.cloud.firestore_v1._helpers.GeoPoint`.
149+
:param value: A Python data to be encoded/converted to Firebase.
150+
151+
152+
:return: A Firebase value converted from ``value``.
153+
:rtype: dict
154+
155+
:raises TypeError: Raised when unsupported data type given.
156+
"""
157+
158+
if value is None:
159+
return {'nullValue': value}
160+
161+
elif isinstance(value, bytes):
162+
return {'bytesValue': b64encode(value).decode('utf-8')}
163+
164+
elif isinstance(value, bool):
165+
return {'booleanValue': value}
166+
167+
elif isinstance(value, int):
168+
return {'integerValue': value}
169+
170+
elif isinstance(value, float):
171+
return {'doubleValue': value}
172+
173+
elif isinstance(value, str):
174+
return {'stringValue': value}
175+
176+
elif isinstance(value, dict):
177+
return {'mapValue': _to_datastore(value)}
178+
179+
elif isinstance(value, datetime):
180+
return {'timestampValue': value.strftime("%Y-%m-%dT%H:%M:%S.%fZ")}
181+
182+
elif isinstance(value, DatetimeWithNanoseconds):
183+
return {'timestampValue': value.rfc3339()}
184+
185+
elif isinstance(value, GeoPoint):
186+
return {'geoPointValue': MessageToDict(value.to_protobuf())}
187+
else:
188+
189+
raise TypeError("Cannot convert to a Firestore Value", value, "Invalid type", type(value))

0 commit comments

Comments
 (0)