def _parse_basic_expression(self, expression): if isinstance(expression, six.string_types) and expression.startswith('$'): if expression.startswith('$$'): return helpers.get_value_by_dot(dict({ 'ROOT': self._doc_dict, 'CURRENT': self._doc_dict, }, **self._user_vars), expression[2:]) return helpers.get_value_by_dot(self._doc_dict, expression[1:], can_generate_array=True) return expression
def test__get_value_by_dot_find_key(self): """Test get_value_by_dot when key can be found""" for doc, key, expected in ( ({'a': 1}, 'a', 1), ({'a': {'b': 1}}, 'a', {'b': 1}), ({'a': {'b': 1}}, 'a.b', 1), ({'a': [{'b': 1}]}, 'a.0.b', 1)): found = get_value_by_dot(doc, key) self.assertEqual(found, expected)
def _handle_unwind_stage(in_collection, unused_database, options): if not isinstance(options, dict): options = {'path': options} path = options['path'] if not isinstance(path, six.string_types) or path[0] != '$': raise ValueError( '$unwind failed: exception: field path references must be prefixed ' "with a '$' '%s'" % path) path = path[1:] should_preserve_null_and_empty = options.get('preserveNullAndEmptyArrays') include_array_index = options.get('includeArrayIndex') unwound_collection = [] for doc in in_collection: try: array_value = helpers.get_value_by_dot(doc, path) except KeyError: if should_preserve_null_and_empty: unwound_collection.append(doc) continue if array_value is None: if should_preserve_null_and_empty: unwound_collection.append(doc) continue if array_value == []: if should_preserve_null_and_empty: new_doc = copy.deepcopy(doc) # We just ran a get_value_by_dot so we know the value exists. helpers.delete_value_by_dot(new_doc, path) unwound_collection.append(new_doc) continue if isinstance(array_value, list): iter_array = enumerate(array_value) else: iter_array = [(None, array_value)] for index, field_item in iter_array: new_doc = copy.deepcopy(doc) new_doc = helpers.set_value_by_dot(new_doc, path, field_item) if include_array_index: new_doc = helpers.set_value_by_dot(new_doc, include_array_index, index) unwound_collection.append(new_doc) return unwound_collection
def _extend_collection(out_collection, field, expression): field_exists = False for doc in out_collection: if field in doc: field_exists = True break if not field_exists: for doc in out_collection: if isinstance(expression, six.string_types) and expression.startswith('$'): try: doc[field] = helpers.get_value_by_dot( doc, expression.lstrip('$')) except KeyError: pass else: # verify expression has operator as first doc[field] = _parse_expression(expression.copy(), doc) return out_collection
def _handle_lookup_stage(in_collection, database, options): for operator in ('let', 'pipeline'): if operator in options: raise NotImplementedError( "Although '%s' is a valid lookup operator for the " 'aggregation pipeline, it is currently not ' 'implemented in Mongomock.' % operator) for operator in ('from', 'localField', 'foreignField', 'as'): if operator not in options: raise OperationFailure( "Must specify '%s' field for a $lookup" % operator) if not isinstance(options[operator], six.string_types): raise OperationFailure( 'Arguments to $lookup must be strings') if operator in ('as', 'localField', 'foreignField') and \ options[operator].startswith('$'): raise OperationFailure( "FieldPath field names may not start with '$'") if operator == 'as' and \ '.' in options[operator]: raise NotImplementedError( "Although '.' is valid in the 'as' " 'parameters for the lookup stage of the aggregation ' 'pipeline, it is currently not implemented in Mongomock.') foreign_name = options['from'] local_field = options['localField'] foreign_field = options['foreignField'] local_name = options['as'] foreign_collection = database.get_collection(foreign_name) for doc in in_collection: try: query = helpers.get_value_by_dot(doc, local_field) except KeyError: query = None if isinstance(query, list): query = {'$in': query} matches = foreign_collection.find({foreign_field: query}) doc[local_name] = [foreign_doc for foreign_doc in matches] return in_collection