44from typing import List , Dict , Any
55from mockfirestore import NotFound
66from 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
1112class 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