Skip to content

Commit f2df96d

Browse files
committed
Move transformation helpers to separate file
1 parent 90828da commit f2df96d

File tree

2 files changed

+89
-85
lines changed

2 files changed

+89
-85
lines changed

mockfirestore/_transformations.py

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
from typing import Dict, Any, List
2+
3+
from mockfirestore._helpers import get_document_iterator, get_by_path, set_by_path, delete_by_path
4+
5+
6+
def apply_transformations(document: Dict[str, Any], data: Dict[str, Any]):
7+
"""Handles special fields like INCREMENT."""
8+
increments = {}
9+
arr_unions = {}
10+
arr_deletes = {}
11+
deletes = []
12+
13+
for key, value in list(get_document_iterator(data)):
14+
if not value.__class__.__module__.startswith('google.cloud.firestore'):
15+
# Unfortunately, we can't use `isinstance` here because that would require
16+
# us to declare google-cloud-firestore as a dependency for this library.
17+
# However, it's somewhat strange that the mocked version of the library
18+
# requires the library itself, so we'll just leverage this heuristic as a
19+
# means of identifying it.
20+
#
21+
# Furthermore, we don't hardcode the full module name, since the original
22+
# library seems to use a thin shim to perform versioning. e.g. at the time
23+
# of writing, the full module name is `google.cloud.firestore_v1.transforms`,
24+
# and it can evolve to `firestore_v2` in the future.
25+
continue
26+
27+
transformer = value.__class__.__name__
28+
if transformer == 'Increment':
29+
increments[key] = value.value
30+
elif transformer == 'ArrayUnion':
31+
arr_unions[key] = value.values
32+
elif transformer == 'ArrayRemove':
33+
arr_deletes[key] = value.values
34+
del data[key]
35+
elif transformer == 'Sentinel':
36+
if value.description == "Value used to delete a field in a document.":
37+
deletes.append(key)
38+
del data[key]
39+
40+
# All other transformations can be applied as needed.
41+
# See #29 for tracking.
42+
43+
def _update_data(new_values: dict, default: Any):
44+
for key, value in new_values.items():
45+
path = key.split('.')
46+
47+
try:
48+
item = get_by_path(document, path)
49+
except (TypeError, KeyError):
50+
item = default
51+
52+
set_by_path(data, path, item + value, create_nested=True)
53+
54+
_update_data(increments, 0)
55+
_update_data(arr_unions, [])
56+
57+
_apply_updates(document, data)
58+
_apply_deletes(document, deletes)
59+
_apply_arr_deletes(document, arr_deletes)
60+
61+
62+
def _apply_updates(document: Dict[str, Any], data: Dict[str, Any]):
63+
for key, value in data.items():
64+
path = key.split(".")
65+
set_by_path(document, path, value, create_nested=True)
66+
67+
68+
def _apply_deletes(document: Dict[str, Any], data: List[str]):
69+
for key in data:
70+
path = key.split(".")
71+
delete_by_path(document, path)
72+
73+
74+
def _apply_arr_deletes(document: Dict[str, Any], data: Dict[str, Any]):
75+
for key, values_to_delete in data.items():
76+
path = key.split(".")
77+
try:
78+
value = get_by_path(document, path)
79+
except KeyError:
80+
continue
81+
for value_to_delete in values_to_delete:
82+
try:
83+
value.remove(value_to_delete)
84+
except ValueError:
85+
pass
86+
set_by_path(document, path, value)

mockfirestore/document.py

Lines changed: 3 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44
from typing import List, Dict, Any
55
from mockfirestore import NotFound
66
from mockfirestore._helpers import (
7-
Timestamp, Document, Store, get_by_path, set_by_path, delete_by_path, get_document_iterator
7+
Timestamp, Document, Store, get_by_path, set_by_path, delete_by_path
88
)
9+
from mockfirestore._transformations import apply_transformations
910

1011

1112
class DocumentSnapshot:
@@ -82,7 +83,7 @@ def update(self, data: Dict[str, Any]):
8283
if document == {}:
8384
raise NotFound('No document to update: {}'.format(self._path))
8485

85-
_apply_transformations(document, deepcopy(data))
86+
apply_transformations(document, deepcopy(data))
8687

8788
def collection(self, name) -> 'CollectionReference':
8889
from mockfirestore.collection import CollectionReference
@@ -91,86 +92,3 @@ def collection(self, name) -> 'CollectionReference':
9192
if name not in document:
9293
set_by_path(self._data, new_path, {})
9394
return CollectionReference(self._data, new_path, parent=self)
94-
95-
96-
def _apply_transformations(document: Dict[str, Any], data: Dict[str, Any]):
97-
"""Handles special fields like INCREMENT."""
98-
increments = {}
99-
arr_unions = {}
100-
arr_deletes = {}
101-
deletes = []
102-
103-
for key, value in list(get_document_iterator(data)):
104-
if not value.__class__.__module__.startswith('google.cloud.firestore'):
105-
# Unfortunately, we can't use `isinstance` here because that would require
106-
# us to declare google-cloud-firestore as a dependency for this library.
107-
# However, it's somewhat strange that the mocked version of the library
108-
# requires the library itself, so we'll just leverage this heuristic as a
109-
# means of identifying it.
110-
#
111-
# Furthermore, we don't hardcode the full module name, since the original
112-
# library seems to use a thin shim to perform versioning. e.g. at the time
113-
# of writing, the full module name is `google.cloud.firestore_v1.transforms`,
114-
# and it can evolve to `firestore_v2` in the future.
115-
continue
116-
117-
transformer = value.__class__.__name__
118-
if transformer == 'Increment':
119-
increments[key] = value.value
120-
elif transformer == 'ArrayUnion':
121-
arr_unions[key] = value.values
122-
elif transformer == 'ArrayRemove':
123-
arr_deletes[key] = value.values
124-
del data[key]
125-
elif transformer == 'Sentinel':
126-
if value.description == "Value used to delete a field in a document.":
127-
deletes.append(key)
128-
del data[key]
129-
130-
# All other transformations can be applied as needed.
131-
# See #29 for tracking.
132-
133-
def _update_data(new_values: dict, default: Any):
134-
for key, value in new_values.items():
135-
path = key.split('.')
136-
137-
try:
138-
item = get_by_path(document, path)
139-
except (TypeError, KeyError):
140-
item = default
141-
142-
set_by_path(data, path, item + value, create_nested=True)
143-
144-
_update_data(increments, 0)
145-
_update_data(arr_unions, [])
146-
147-
_apply_updates(document, data)
148-
_apply_deletes(document, deletes)
149-
_apply_arr_deletes(document, arr_deletes)
150-
151-
152-
def _apply_updates(document: Dict[str, Any], data: Dict[str, Any]):
153-
for key, value in data.items():
154-
path = key.split(".")
155-
set_by_path(document, path, value, create_nested=True)
156-
157-
158-
def _apply_deletes(document: Dict[str, Any], data: List[str]):
159-
for key in data:
160-
path = key.split(".")
161-
delete_by_path(document, path)
162-
163-
164-
def _apply_arr_deletes(document: Dict[str, Any], data: Dict[str, Any]):
165-
for key, values_to_delete in data.items():
166-
path = key.split(".")
167-
try:
168-
value = get_by_path(document, path)
169-
except KeyError:
170-
continue
171-
for value_to_delete in values_to_delete:
172-
try:
173-
value.remove(value_to_delete)
174-
except ValueError:
175-
pass
176-
set_by_path(document, path, value)

0 commit comments

Comments
 (0)