def test_transform_embedded_document_list_fields(self): """ Test added to check filtering EmbeddedDocumentListField which is inside a EmbeddedDocumentField """ class Drink(EmbeddedDocument): id = StringField() meta = {"strict": False} class Shop(Document): drinks = EmbeddedDocumentListField(Drink) Shop.drop_collection() drinks = [Drink(id="drink_1"), Drink(id="drink_2")] Shop.objects.create(drinks=drinks) q_obj = transform.query(Shop, drinks__all=[{ "$elemMatch": { "_id": x.id } } for x in drinks]) assert q_obj == { "drinks": { "$all": [{ "$elemMatch": { "_id": x.id } } for x in drinks] } } Shop.drop_collection()
def _save_update(self, doc, save_condition, write_concern): """Update an existing document. Helper method, should only be used inside save(). """ doc_alias = None if doc.get('properties', {}).get('db_alias', None): doc_alias = doc['properties'].pop('db_alias') if doc.get('properties', {}).get('conn_settings', {}): connection_properties = doc['properties'].pop('conn_settings') self.verify_connection_setting(doc_alias, connection_properties) if doc_alias: alias_db = get_db(doc_alias) collection = alias_db.get_collection(self._meta['collection']) else: collection = self._get_collection() object_id = doc["_id"] created = False select_dict = {} if save_condition is not None: select_dict = transform.query(self.__class__, **save_condition) select_dict["_id"] = object_id # Need to add shard key to query, or you get an error shard_key = self._meta.get("shard_key", tuple()) for k in shard_key: path = self._lookup_field(k.split(".")) actual_key = [p.db_field for p in path] val = doc for ak in actual_key: val = val[ak] select_dict[".".join(actual_key)] = val update_doc = self._get_update_doc() if update_doc: upsert = save_condition is None with set_write_concern(collection, write_concern) as wc_collection: last_error = wc_collection.update_one(select_dict, update_doc, upsert=upsert).raw_result if not upsert and last_error["n"] == 0: raise SaveConditionError( "Race condition preventing document update detected") if last_error is not None: updated_existing = last_error.get("updatedExisting") if updated_existing is False: created = True # !!! This is bad, means we accidentally created a new, # potentially corrupted document. See # https://github.com/MongoEngine/mongoengine/issues/564 return object_id, created
def test_transform_query(self): """Ensure that the _transform_query function operates correctly. """ assert transform.query(name="test", age=30) == { "name": "test", "age": 30 } assert transform.query(age__lt=30) == {"age": {"$lt": 30}} assert transform.query(age__gt=20, age__lt=50) == { "age": { "$gt": 20, "$lt": 50 } } assert transform.query(age=20, age__gt=50) == { "$and": [{ "age": { "$gt": 50 } }, { "age": 20 }] } assert transform.query(friend__age__gte=30) == { "friend.age": { "$gte": 30 } } assert transform.query(name__exists=True) == { "name": { "$exists": True } } assert transform.query(name=["Mark"], __raw__={"name": { "$in": "Tom" }}) == { "$and": [{ "name": ["Mark"] }, { "name": { "$in": "Tom" } }] } assert transform.query(name__in=["Tom"], __raw__={"name": "Mark"}) == { "$and": [{ "name": { "$in": ["Tom"] } }, { "name": "Mark" }] }
def test_transform_query(self): """Ensure that the _transform_query function operates correctly. """ self.assertEqual(transform.query(name='test', age=30), { 'name': 'test', 'age': 30 }) self.assertEqual(transform.query(age__lt=30), {'age': {'$lt': 30}}) self.assertEqual(transform.query(age__gt=20, age__lt=50), {'age': { '$gt': 20, '$lt': 50 }}) self.assertEqual(transform.query(age=20, age__gt=50), {'$and': [{ 'age': { '$gt': 50 } }, { 'age': 20 }]}) self.assertEqual(transform.query(friend__age__gte=30), {'friend.age': { '$gte': 30 }}) self.assertEqual(transform.query(name__exists=True), {'name': { '$exists': True }})
def _save_update(self, doc, save_condition, write_concern): """Update an existing document. Helper method, should only be used inside save(). """ collection = self._get_collection() object_id = doc['_id'] created = False select_dict = {} if save_condition is not None: select_dict = transform.query(self.__class__, **save_condition) select_dict['_id'] = object_id # Need to add shard key to query, or you get an error shard_key = self._meta.get('shard_key', tuple()) for k in shard_key: path = self._lookup_field(k.split('.')) actual_key = [p.db_field for p in path] val = doc for ak in actual_key: val = val[ak] select_dict['.'.join(actual_key)] = val updates, removals = self._delta() update_query = {} if updates: update_query['$set'] = updates if removals: update_query['$unset'] = removals if updates or removals: upsert = save_condition is None last_error = collection.update(select_dict, update_query, upsert=upsert, **write_concern) if not upsert and last_error['n'] == 0: raise SaveConditionError('Race condition preventing' ' document update detected') if last_error is not None: updated_existing = last_error.get('updatedExisting') if updated_existing is False: created = True # !!! This is bad, means we accidentally created a new, # potentially corrupted document. See # https://github.com/MongoEngine/mongoengine/issues/564 return object_id, created
def _save_update(self, doc, save_condition, write_concern): """Update an existing document. Helper method, should only be used inside save(). """ collection = self._get_collection() object_id = doc['_id'] created = False select_dict = {} if save_condition is not None: select_dict = transform.query(self.__class__, **save_condition) select_dict['_id'] = object_id # Need to add shard key to query, or you get an error shard_key = self._meta.get('shard_key', tuple()) for k in shard_key: path = self._lookup_field(k.split('.')) actual_key = [p.db_field for p in path] val = doc for ak in actual_key: val = val[ak] select_dict['.'.join(actual_key)] = val update_doc = self._get_update_doc() if update_doc: upsert = save_condition is None last_error = collection.update(select_dict, update_doc, upsert=upsert, **write_concern) if not upsert and last_error['n'] == 0: raise SaveConditionError('Race condition preventing' ' document update detected') if last_error is not None: updated_existing = last_error.get('updatedExisting') if updated_existing is False: created = True # !!! This is bad, means we accidentally created a new, # potentially corrupted document. See # https://github.com/MongoEngine/mongoengine/issues/564 return object_id, created
def test_transform_query(self): """Ensure that the _transform_query function operates correctly. """ self.assertEqual(transform.query(name='test', age=30), {'name': 'test', 'age': 30}) self.assertEqual(transform.query(age__lt=30), {'age': {'$lt': 30}}) self.assertEqual(transform.query(age__gt=20, age__lt=50), {'age': {'$gt': 20, '$lt': 50}}) self.assertEqual(transform.query(age=20, age__gt=50), {'$and': [{'age': {'$gt': 50}}, {'age': 20}]}) self.assertEqual(transform.query(friend__age__gte=30), {'friend.age': {'$gte': 30}}) self.assertEqual(transform.query(name__exists=True), {'name': {'$exists': True}})
def _save_update(self, doc, save_condition, write_concern): """Update an existing document. Helper method, should only be used inside save(). """ collection = self._get_collection() object_id = doc["_id"] created = False select_dict = {} if save_condition is not None: select_dict = transform.query(self.__class__, **save_condition) select_dict["_id"] = object_id select_dict = self._integrate_shard_key(doc, select_dict) update_doc = self._get_update_doc() if update_doc: upsert = save_condition is None with set_write_concern(collection, write_concern) as wc_collection: last_error = wc_collection.update_one( select_dict, update_doc, upsert=upsert, session=self._get_local_session(), ).raw_result if not upsert and last_error["n"] == 0: raise SaveConditionError( "Race condition preventing document update detected") if last_error is not None: updated_existing = last_error.get("updatedExisting") if updated_existing is False: created = True # !!! This is bad, means we accidentally created a new, # potentially corrupted document. See # https://github.com/MongoEngine/mongoengine/issues/564 return object_id, created
def test_transform_query(self): """Ensure that the _transform_query function operates correctly. """ self.assertEqual( transform.query(name="test", age=30), {"name": "test", "age": 30} ) self.assertEqual(transform.query(age__lt=30), {"age": {"$lt": 30}}) self.assertEqual( transform.query(age__gt=20, age__lt=50), {"age": {"$gt": 20, "$lt": 50}} ) self.assertEqual( transform.query(age=20, age__gt=50), {"$and": [{"age": {"$gt": 50}}, {"age": 20}]}, ) self.assertEqual( transform.query(friend__age__gte=30), {"friend.age": {"$gte": 30}} ) self.assertEqual( transform.query(name__exists=True), {"name": {"$exists": True}} )
def save(self, force_insert=False, validate=True, clean=True, write_concern=None, cascade=None, cascade_kwargs=None, _refs=None, save_condition=None, **kwargs): """Save the :class:`~mongoengine.Document` to the database. If the document already exists, it will be updated, otherwise it will be created. :param force_insert: only try to create a new document, don't allow updates of existing documents :param validate: validates the document; set to ``False`` to skip. :param clean: call the document clean method, requires `validate` to be True. :param write_concern: Extra keyword arguments are passed down to :meth:`~pymongo.collection.Collection.save` OR :meth:`~pymongo.collection.Collection.insert` which will be used as options for the resultant ``getLastError`` command. For example, ``save(..., write_concern={w: 2, fsync: True}, ...)`` will wait until at least two servers have recorded the write and will force an fsync on the primary server. :param cascade: Sets the flag for cascading saves. You can set a default by setting "cascade" in the document __meta__ :param cascade_kwargs: (optional) kwargs dictionary to be passed throw to cascading saves. Implies ``cascade=True``. :param _refs: A list of processed references used in cascading saves :param save_condition: only perform save if matching record in db satisfies condition(s) (e.g. version number). Raises :class:`OperationError` if the conditions are not satisfied .. versionchanged:: 0.5 In existing documents it only saves changed fields using set / unset. Saves are cascaded and any :class:`~bson.dbref.DBRef` objects that have changes are saved as well. .. versionchanged:: 0.6 Added cascading saves .. versionchanged:: 0.8 Cascade saves are optional and default to False. If you want fine grain control then you can turn off using document meta['cascade'] = True. Also you can pass different kwargs to the cascade save using cascade_kwargs which overwrites the existing kwargs with custom values. .. versionchanged:: 0.8.5 Optional save_condition that only overwrites existing documents if the condition is satisfied in the current db record. .. versionchanged:: 0.10 :class:`OperationError` exception raised if save_condition fails. .. versionchanged:: 0.10.1 :class: save_condition failure now raises a `SaveConditionError` """ signals.pre_save.send(self.__class__, document=self) if validate: self.validate(clean=clean) if write_concern is None: write_concern = {"w": 1} doc = self.to_mongo() created = ('_id' not in doc or self._created or force_insert) signals.pre_save_post_validation.send(self.__class__, document=self, created=created) try: collection = self._get_collection() if self._meta.get('auto_create_index', True): self.ensure_indexes() if created: if force_insert: object_id = collection.insert(doc, **write_concern) else: object_id = collection.save(doc, **write_concern) # In PyMongo 3.0, the save() call calls internally the _update() call # but they forget to return the _id value passed back, therefore getting it back here # Correct behaviour in 2.X and in 3.0.1+ versions if not object_id and pymongo.version_tuple == (3, 0): pk_as_mongo_obj = self._fields.get(self._meta['id_field']).to_mongo(self.pk) object_id = self._qs.filter(pk=pk_as_mongo_obj).first() and \ self._qs.filter(pk=pk_as_mongo_obj).first().pk else: object_id = doc['_id'] updates, removals = self._delta() # Need to add shard key to query, or you get an error if save_condition is not None: select_dict = transform.query(self.__class__, **save_condition) else: select_dict = {} select_dict['_id'] = object_id shard_key = self.__class__._meta.get('shard_key', tuple()) for k in shard_key: actual_key = self._db_field_map.get(k, k) select_dict[actual_key] = doc[actual_key] def is_new_object(last_error): if last_error is not None: updated = last_error.get("updatedExisting") if updated is not None: return not updated return created update_query = {} if updates: update_query["$set"] = updates if removals: update_query["$unset"] = removals if updates or removals: upsert = save_condition is None last_error = collection.update(select_dict, update_query, upsert=upsert, **write_concern) if not upsert and last_error["n"] == 0: raise SaveConditionError('Race condition preventing' ' document update detected') created = is_new_object(last_error) if cascade is None: cascade = self._meta.get( 'cascade', False) or cascade_kwargs is not None if cascade: kwargs = { "force_insert": force_insert, "validate": validate, "write_concern": write_concern, "cascade": cascade } if cascade_kwargs: # Allow granular control over cascades kwargs.update(cascade_kwargs) kwargs['_refs'] = _refs self.cascade_save(**kwargs) except pymongo.errors.DuplicateKeyError as err: message = 'Tried to save duplicate unique keys (%s)' raise NotUniqueError(message % str(err)) except pymongo.errors.OperationFailure as err: message = 'Could not save document (%s)' if re.match('^E1100[01] duplicate key', str(err)): # E11000 - duplicate key error index # E11001 - duplicate key on update message = 'Tried to save duplicate unique keys (%s)' raise NotUniqueError(message % str(err)) raise OperationError(message % str(err)) id_field = self._meta['id_field'] if created or id_field not in self._meta.get('shard_key', []): self[id_field] = self._fields[id_field].to_python(object_id) signals.post_save.send(self.__class__, document=self, created=created) self._clear_changed_fields() self._created = False return self
def save(self, force_insert=False, validate=True, clean=True, write_concern=None, cascade=None, cascade_kwargs=None, _refs=None, save_condition=None, signal_kwargs=None, **kwargs): """Save the :class:`~mongoengine.Document` to the database. If the document already exists, it will be updated, otherwise it will be created. :param force_insert: only try to create a new document, don't allow updates of existing documents :param validate: validates the document; set to ``False`` to skip. :param clean: call the document clean method, requires `validate` to be True. :param write_concern: Extra keyword arguments are passed down to :meth:`~pymongo.collection.Collection.save` OR :meth:`~pymongo.collection.Collection.insert` which will be used as options for the resultant ``getLastError`` command. For example, ``save(..., write_concern={w: 2, fsync: True}, ...)`` will wait until at least two servers have recorded the write and will force an fsync on the primary server. :param cascade: Sets the flag for cascading saves. You can set a default by setting "cascade" in the document __meta__ :param cascade_kwargs: (optional) kwargs dictionary to be passed throw to cascading saves. Implies ``cascade=True``. :param _refs: A list of processed references used in cascading saves :param save_condition: only perform save if matching record in db satisfies condition(s) (e.g. version number). Raises :class:`OperationError` if the conditions are not satisfied :parm signal_kwargs: (optional) kwargs dictionary to be passed to the signal calls. .. versionchanged:: 0.5 In existing documents it only saves changed fields using set / unset. Saves are cascaded and any :class:`~bson.dbref.DBRef` objects that have changes are saved as well. .. versionchanged:: 0.6 Added cascading saves .. versionchanged:: 0.8 Cascade saves are optional and default to False. If you want fine grain control then you can turn off using document meta['cascade'] = True. Also you can pass different kwargs to the cascade save using cascade_kwargs which overwrites the existing kwargs with custom values. .. versionchanged:: 0.8.5 Optional save_condition that only overwrites existing documents if the condition is satisfied in the current db record. .. versionchanged:: 0.10 :class:`OperationError` exception raised if save_condition fails. .. versionchanged:: 0.10.1 :class: save_condition failure now raises a `SaveConditionError` .. versionchanged:: 0.10.7 Add signal_kwargs argument """ signal_kwargs = signal_kwargs or {} signals.pre_save.send(self.__class__, document=self, **signal_kwargs) if validate: self.validate(clean=clean) if write_concern is None: write_concern = {'w': 1} doc = self.to_mongo() created = ('_id' not in doc or self._created or force_insert) signals.pre_save_post_validation.send(self.__class__, document=self, created=created, **signal_kwargs) try: collection = self._get_collection() if self._meta.get('auto_create_index', True): self.ensure_indexes() if created: if force_insert: object_id = collection.insert(doc, **write_concern) else: object_id = collection.save(doc, **write_concern) # In PyMongo 3.0, the save() call calls internally the _update() call # but they forget to return the _id value passed back, therefore getting it back here # Correct behaviour in 2.X and in 3.0.1+ versions if not object_id and pymongo.version_tuple == (3, 0): pk_as_mongo_obj = self._fields.get(self._meta['id_field']).to_mongo(self.pk) object_id = ( self._qs.filter(pk=pk_as_mongo_obj).first() and self._qs.filter(pk=pk_as_mongo_obj).first().pk ) # TODO doesn't this make 2 queries? else: object_id = doc['_id'] updates, removals = self._delta() # Need to add shard key to query, or you get an error if save_condition is not None: select_dict = transform.query(self.__class__, **save_condition) else: select_dict = {} select_dict['_id'] = object_id shard_key = self._meta.get('shard_key', tuple()) for k in shard_key: path = self._lookup_field(k.split('.')) actual_key = [p.db_field for p in path] val = doc for ak in actual_key: val = val[ak] select_dict['.'.join(actual_key)] = val def is_new_object(last_error): if last_error is not None: updated = last_error.get('updatedExisting') if updated is not None: return not updated return created update_query = {} if updates: update_query['$set'] = updates if removals: update_query['$unset'] = removals if updates or removals: upsert = save_condition is None last_error = collection.update(select_dict, update_query, upsert=upsert, **write_concern) if not upsert and last_error['n'] == 0: raise SaveConditionError('Race condition preventing' ' document update detected') created = is_new_object(last_error) if cascade is None: cascade = self._meta.get( 'cascade', False) or cascade_kwargs is not None if cascade: kwargs = { 'force_insert': force_insert, 'validate': validate, 'write_concern': write_concern, 'cascade': cascade } if cascade_kwargs: # Allow granular control over cascades kwargs.update(cascade_kwargs) kwargs['_refs'] = _refs self.cascade_save(**kwargs) except pymongo.errors.DuplicateKeyError as err: message = 'Tried to save duplicate unique keys (%s)' raise NotUniqueError(message % six.text_type(err)) except pymongo.errors.OperationFailure as err: message = 'Could not save document (%s)' if re.match('^E1100[01] duplicate key', six.text_type(err)): # E11000 - duplicate key error index # E11001 - duplicate key on update message = 'Tried to save duplicate unique keys (%s)' raise NotUniqueError(message % six.text_type(err)) raise OperationError(message % six.text_type(err)) id_field = self._meta['id_field'] if created or id_field not in self._meta.get('shard_key', []): self[id_field] = self._fields[id_field].to_python(object_id) signals.post_save.send(self.__class__, document=self, created=created, **signal_kwargs) self._clear_changed_fields() self._created = False return self
def visit_query(self, query): return transform.query(self.document, **query.query)
def save(self, force_insert=False, validate=True, clean=True, write_concern=None, cascade=None, cascade_kwargs=None, _refs=None, save_condition=None, **kwargs): """Save the :class:`~mongoengine.Document` to the database. If the document already exists, it will be updated, otherwise it will be created. :param force_insert: only try to create a new document, don't allow updates of existing documents :param validate: validates the document; set to ``False`` to skip. :param clean: call the document clean method, requires `validate` to be True. :param write_concern: Extra keyword arguments are passed down to :meth:`~pymongo.collection.Collection.save` OR :meth:`~pymongo.collection.Collection.insert` which will be used as options for the resultant ``getLastError`` command. For example, ``save(..., write_concern={w: 2, fsync: True}, ...)`` will wait until at least two servers have recorded the write and will force an fsync on the primary server. :param cascade: Sets the flag for cascading saves. You can set a default by setting "cascade" in the document __meta__ :param cascade_kwargs: (optional) kwargs dictionary to be passed throw to cascading saves. Implies ``cascade=True``. :param _refs: A list of processed references used in cascading saves :param save_condition: only perform save if matching record in db satisfies condition(s) (e.g., version number) .. versionchanged:: 0.5 In existing documents it only saves changed fields using set / unset. Saves are cascaded and any :class:`~bson.dbref.DBRef` objects that have changes are saved as well. .. versionchanged:: 0.6 Added cascading saves .. versionchanged:: 0.8 Cascade saves are optional and default to False. If you want fine grain control then you can turn off using document meta['cascade'] = True. Also you can pass different kwargs to the cascade save using cascade_kwargs which overwrites the existing kwargs with custom values. .. versionchanged:: 0.8.5 Optional save_condition that only overwrites existing documents if the condition is satisfied in the current db record. """ signals.pre_save.send(self.__class__, document=self) if validate: self.validate(clean=clean) if write_concern is None: write_concern = {"w": 1} doc = self.to_mongo() created = ('_id' not in doc or self._created or force_insert) signals.pre_save_post_validation.send(self.__class__, document=self, created=created) try: collection = self._get_collection() if self._meta.get('auto_create_index', True): self.ensure_indexes() if created: if force_insert: object_id = collection.insert(doc, **write_concern) else: object_id = collection.save(doc, **write_concern) else: object_id = doc['_id'] updates, removals = self._delta() # Need to add shard key to query, or you get an error if save_condition is not None: select_dict = transform.query(self.__class__, **save_condition) else: select_dict = {} select_dict['_id'] = object_id shard_key = self.__class__._meta.get('shard_key', tuple()) for k in shard_key: actual_key = self._db_field_map.get(k, k) select_dict[actual_key] = doc[actual_key] def is_new_object(last_error): if last_error is not None: updated = last_error.get("updatedExisting") if updated is not None: return not updated return created update_query = {} if updates: update_query["$set"] = updates if removals: update_query["$unset"] = removals if updates or removals: upsert = save_condition is None last_error = collection.update(select_dict, update_query, upsert=upsert, **write_concern) created = is_new_object(last_error) if cascade is None: cascade = self._meta.get('cascade', False) or cascade_kwargs is not None if cascade: kwargs = { "force_insert": force_insert, "validate": validate, "write_concern": write_concern, "cascade": cascade } if cascade_kwargs: # Allow granular control over cascades kwargs.update(cascade_kwargs) kwargs['_refs'] = _refs self.cascade_save(**kwargs) except pymongo.errors.DuplicateKeyError as err: message = 'Tried to save duplicate unique keys (%s)' raise NotUniqueError(message % str(err)) except pymongo.errors.OperationFailure as err: message = 'Could not save document (%s)' if re.match('^E1100[01] duplicate key', str(err)): # E11000 - duplicate key error index # E11001 - duplicate key on update message = 'Tried to save duplicate unique keys (%s)' raise NotUniqueError(message % str(err)) raise OperationError(message % str(err)) id_field = self._meta['id_field'] if created or id_field not in self._meta.get('shard_key', []): self[id_field] = self._fields[id_field].to_python(object_id) signals.post_save.send(self.__class__, document=self, created=created) self._clear_changed_fields() self._created = False return self