Exemple #1
0
	async def _(
		*, module, handler, skip_events=[], env=None, query=[], doc={}, payload={}
	):
		if not env:
			env = mock_env()
		query = Query(query)
		return await getattr(MockRegistry.modules[module], handler)(
			skip_events=skip_events, env=env, query=query, doc=doc, payload=payload
		)
Exemple #2
0
def test_compile_query_geo_near():
    skip, limit, sort, group, aggregate_query = _query._compile_query(
        collection_name='collection_name',
        attrs={},
        query=Query([{
            '$geo_near': {
                'val': [21.422507, 39.826181],
                'attr': 'str',
                'dist': 1000
            }
        }]),
        watch_mode=False,
    )
    assert skip == None
    assert limit == None
    assert sort == {'_id': -1}
    assert group == None
    assert aggregate_query == [
        {
            '$geoNear': {
                'near': {
                    'type': 'Point',
                    'coordinates': [21.422507, 39.826181]
                },
                'distanceField': 'str.__distance',
                'maxDistance': 1000,
                'spherical': True,
            }
        },
        {
            '$match': {
                '__deleted': {
                    '$exists': False
                }
            }
        },
        {
            '$match': {
                '__create_draft': {
                    '$exists': False
                }
            }
        },
        {
            '$match': {
                '__update_draft': {
                    '$exists': False
                }
            }
        },
        {
            '$group': {
                '_id': '$_id'
            }
        },
    ]
Exemple #3
0
def test_compile_query_no_steps(mocker):
    mock_compile_query_step = mocker.patch.object(_query,
                                                  '_compile_query_step')
    _query._compile_query(
        collection_name='collection_name',
        attrs={},
        query=Query([]),
        watch_mode=False,
    )
    mock_compile_query_step.assert_not_called()
Exemple #4
0
	async def _(*, module, method, skip_events=[], env=None, query=[], doc={}):
		if not env:
			env = mock_env()
		query = Query(query)

		permissions = await _check_permissions(
			skip_events=skip_events,
			env=env,
			query=query,
			doc=doc,
			module=MockRegistry.modules[module],
			permissions=MockRegistry.modules[module].methods[method].permissions,
		)

		return permissions
Exemple #5
0
	async def _(
		*, module, handler, results, skip_events=[], env=None, query=[], doc={}, payload={}
	):
		if not env:
			env = mock_env()
		query = Query(query)
		if 'docs' in results.keys():
			results['docs'] = [DictObj(doc) for doc in results['docs']]
		return await getattr(MockRegistry.modules[module], handler)(
			results=results,
			skip_events=skip_events,
			env=env,
			query=query,
			doc=doc,
			payload=payload,
		)
Exemple #6
0
def test_compile_query_one_step(mocker):
    mock_compile_query_step = mocker.patch.object(_query,
                                                  '_compile_query_step')
    _query._compile_query(
        collection_name='collection_name',
        attrs={},
        query=Query([{
            'attr': 'match_term'
        }]),
        watch_mode=False,
    )
    mock_compile_query_step.assert_called_once_with(
        aggregate_prefix=[
            {
                '$match': {
                    '__deleted': {
                        '$exists': False
                    }
                }
            },
            {
                '$match': {
                    '__create_draft': {
                        '$exists': False
                    }
                }
            },
            {
                '$match': {
                    '__update_draft': {
                        '$exists': False
                    }
                }
            },
        ],
        aggregate_suffix=[{
            '$group': {
                '_id': '$_id'
            }
        }],
        aggregate_match=[],
        collection_name='collection_name',
        attrs={},
        step={'attr': 'match_term'},
        watch_mode=False,
    )
Exemple #7
0
def test_compile_query_attrs_mixed_in_attrs():
    skip, limit, sort, group, aggregate_query = _query._compile_query(
        collection_name='collection_name',
        attrs={'attr1': ATTR.ANY()},
        query=Query([{
            '$attrs': ['attr1', 'attr2']
        }]),
        watch_mode=False,
    )
    assert skip == None
    assert limit == None
    assert sort == {'_id': -1}
    assert group == None
    assert aggregate_query == [
        {
            '$match': {
                '__deleted': {
                    '$exists': False
                }
            }
        },
        {
            '$match': {
                '__create_draft': {
                    '$exists': False
                }
            }
        },
        {
            '$match': {
                '__update_draft': {
                    '$exists': False
                }
            }
        },
        {
            '$group': {
                '_id': '$_id',
                'attr1': {
                    '$first': '$attr1'
                }
            }
        },
    ]
Exemple #8
0
def test_compile_query_group():
    skip, limit, sort, group, aggregate_query = _query._compile_query(
        collection_name='collection_name',
        attrs={},
        query=Query([{
            '$group': [{
                'by': 'price',
                'count': 10
            }]
        }]),
        watch_mode=False,
    )
    assert skip == None
    assert limit == None
    assert sort == {'_id': -1}
    assert group == [{'by': 'price', 'count': 10}]
    assert aggregate_query == [
        {
            '$match': {
                '__deleted': {
                    '$exists': False
                }
            }
        },
        {
            '$match': {
                '__create_draft': {
                    '$exists': False
                }
            }
        },
        {
            '$match': {
                '__update_draft': {
                    '$exists': False
                }
            }
        },
        {
            '$group': {
                '_id': '$_id'
            }
        },
    ]
Exemple #9
0
	async def _(*, module, method, skip_events=[], env=None, query=[], doc={}):
		if not env:
			env = mock_env()
		query = Query(query)

		# [DOC] Following code is actual copy of BaseModule.__call__ method as to replicate the complete process of calling an initialised Nawah module method

		if Event.PERM not in skip_events:
			permissions_check = await call_method_check_permissions(
				module=module, method=method, skip_events=skip_events, env=env, query=query, doc=doc
			)

			if type(permissions_check['query_mod']) == dict:
				permissions_check['query_mod'] = [permissions_check['query_mod']]
			for i in range(len(permissions_check['query_mod'])):
				if type(permissions_check['query_mod'][i]) == dict:
					query_set_list = [permissions_check['query_mod'][i]]
				elif type(permissions_check['query_mod'][i]) == list:
					query_set_list = permissions_check['query_mod'][i]
				for query_set in query_set_list:
					del_args = []
					for attr in query_set.keys():
						if query_set[attr] == None:
							del_args.append(attr)
					for attr in del_args:
						del query_set[attr]
			query.append(permissions_check['query_mod'])

			del_args = []
			for attr in permissions_check['doc_mod'].keys():
				if permissions_check['doc_mod'][attr] == None:
					permissions_check['doc_mod'][attr] = NAWAH_VALUES.NONE_VALUE
			for attr in del_args:
				del permissions_check['doc_mod'][attr]
			doc.update(permissions_check['doc_mod'])
			doc = {
				attr: doc[attr] for attr in doc.keys() if doc[attr] != NAWAH_VALUES.NONE_VALUE
			}

		if Event.ARGS not in skip_events:
			if args_list := MockRegistry.modules[module].methods[method].query_args:
				if type(args_list) != list:
					args_list = [args_list]

				# [DOC] Call ATTR.validate_type on query_args to initialise Attr Type TYPE
				for args_set in args_list:
					for attr in args_set.keys():
						ATTR.validate_type(attr_type=args_set[attr])

				await _validate_args(
					args=query,
					args_list_label='query',
					args_list=args_list,
				)

			if args_list := MockRegistry.modules[module].methods[method].doc_args:
				if type(args_list) != list:
					args_list = [args_list]

				# [DOC] Call ATTR.validate_type on query_args to initialise Attr Type TYPE
				for args_set in args_list:
					for attr in args_set.keys():
						ATTR.validate_type(attr_type=args_set[attr])

				await _validate_args(
					args=doc,
					args_list_label='doc',
					args_list=args_list,
				)
Exemple #10
0
async def test_read_module(mocker):
	module = MockModule()
	with pytest.raises(Exception):
		# [DOC] BaseModule methods don't call directly, but through BaseMethod which passes query arg as Query object, replicate the same behaviour
		await module.read(query=Query([]))
Exemple #11
0
def test_compile_query_search():
    skip, limit, sort, group, aggregate_query = _query._compile_query(
        collection_name='collection_name',
        attrs={},
        query=Query([{
            '$search': 'search_term'
        }]),
        watch_mode=False,
    )
    assert skip == None
    assert limit == None
    assert sort == {'_id': -1}
    assert group == None
    assert aggregate_query == [
        {
            '$match': {
                '$text': {
                    '$search': 'search_term'
                }
            }
        },
        {
            '$match': {
                '__deleted': {
                    '$exists': False
                }
            }
        },
        {
            '$match': {
                '__create_draft': {
                    '$exists': False
                }
            }
        },
        {
            '$match': {
                '__update_draft': {
                    '$exists': False
                }
            }
        },
        {
            '$project': {
                '_id': '$_id',
                '__score': {
                    '$meta': 'textScore'
                }
            }
        },
        {
            '$match': {
                '__score': {
                    '$gt': 0.5
                }
            }
        },
        {
            '$group': {
                '_id': '$_id'
            }
        },
    ]
Exemple #12
0
    async def __call__(
        self,
        *,
        skip_events: NAWAH_EVENTS = None,
        env: NAWAH_ENV = None,
        query: Union[NAWAH_QUERY, Query] = None,
        doc: NAWAH_DOC = None,
        call_id: str = None,
    ) -> Optional[DictObj]:
        if skip_events == None:
            skip_events = []
        if env == None:
            env = {}
        if query == None:
            query = []
        if doc == None:
            doc = {}
        skip_events = cast(NAWAH_EVENTS, skip_events)
        env = cast(NAWAH_ENV, env)
        query = cast(Union[NAWAH_QUERY, Query], query)
        doc = cast(NAWAH_DOC, doc)
        call_id = cast(str, call_id)
        # [DOC] Convert list query to Query object
        query = Query(copy.deepcopy(query))
        # [DOC] deepcopy() doc object ro prevent mutating original doc
        doc = copy.deepcopy(doc)

        logger.debug(
            f'Calling: {self.module.module_name}.{self.method}, with skip_events:{skip_events}, query:{str(query)[:250]}, doc.keys:{doc.keys()}'
        )

        if call_id:
            for analytics_set in self.module.analytics:
                if analytics_set.condition(
                        skip_events=skip_events,
                        env=env,
                        query=query,
                        doc=doc,
                        method=self.method,
                ):
                    try:
                        analytic_doc = analytics_set.doc(
                            skip_events=skip_events,
                            env=env,
                            query=query,
                            doc=doc,
                            method=self.method,
                        )
                        analytic_results = await Config.modules[
                            'analytic'].create(skip_events=[Event.PERM],
                                               env=env,
                                               doc=analytic_doc)
                    except Exception as e:
                        logger.error(
                            f'Failed to create \'Analytic\' doc: {analytic_doc}. Results: {analytic_results}'
                        )
                    if analytic_results.status != 200:
                        logger.error(
                            f'Failed to create \'Analytic\' doc: {analytic_doc}. Results: {analytic_results}'
                        )

        if Event.PERM not in skip_events and env['session']:
            try:
                permissions_check = await _check_permissions(
                    skip_events=skip_events,
                    env=env,
                    query=query,
                    doc=doc,
                    module=self.module,
                    permissions=self.permissions,
                )
                logger.debug(f'permissions_check: Pass.')
            except Exception as e:
                logger.debug(f'permissions_check: Fail.')
                # [DOC] InvalidAttrException, usually raised by Attr Type TYPE
                if type(e) == InvalidAttrException:
                    return await self.return_results(
                        ws=env['ws'] if 'ws' in env.keys() else None,
                        results=DictObj({
                            'status':
                            400,
                            'msg':
                            str(e),
                            'args':
                            DictObj({'code': 'INVALID_ARGS'}),
                        }),
                        call_id=call_id,
                    )
                # [DOC] Any other exception, treat as server error
                elif type(e) != InvalidPermissionsExcpetion:
                    logger.error(
                        f'An error occurred. Details: {traceback.format_exc()}.'
                    )
                    tb = sys.exc_info()[2]
                    if tb is not None:
                        prev = tb
                        current = tb.tb_next
                        while current is not None:
                            prev = current
                            current = current.tb_next
                        logger.error(
                            f'Scope variables: {JSONEncoder().encode(prev.tb_frame.f_locals)}'
                        )
                    return await self.return_results(
                        ws=env['ws'] if 'ws' in env.keys() else None,
                        results=DictObj({
                            'status':
                            500,
                            'msg':
                            'Unexpected error has occurred.',
                            'args':
                            DictObj({'code': 'CORE_SERVER_ERROR'}),
                        }),
                        call_id=call_id,
                    )
                # [DOC] Regular InvalidPermissionsExcpetion failure
                return await self.return_results(
                    ws=env['ws'] if 'ws' in env.keys() else None,
                    results=DictObj({
                        'status':
                        403,
                        'msg':
                        'You don\'t have permissions to access this endpoint.',
                        'args':
                        DictObj({'code': 'CORE_SESSION_FORBIDDEN'}),
                    }),
                    call_id=call_id,
                )
            else:
                if type(permissions_check['query_mod']) == dict:
                    permissions_check['query_mod'] = [
                        permissions_check['query_mod']
                    ]
                for i in range(len(permissions_check['query_mod'])):
                    # [DOC] attempt to process query_set as nested-list (OR) even if it's dict
                    if type(permissions_check['query_mod'][i]) == dict:
                        query_set_list = [permissions_check['query_mod'][i]]
                    elif type(permissions_check['query_mod'][i]) == list:
                        query_set_list = permissions_check['query_mod'][i]
                    # [DOC] loop over query_set_list, query_set
                    for query_set in query_set_list:
                        del_args = []
                        for attr in query_set.keys():
                            # [DOC] Flag attr for deletion if value is None
                            # [TODO] Check why the condition included (or type(query_set[attr]) == ATTR_MOD:)
                            if query_set[attr] == None:
                                del_args.append(attr)
                        for attr in del_args:
                            del query_set[attr]
                # [DOC] Append query permissions args to query
                query.append(permissions_check['query_mod'])

                del_args = []
                for attr in permissions_check['doc_mod'].keys():
                    # [DOC] Replace None value with NONE_VALUE to bypass later validate step
                    if permissions_check['doc_mod'][attr] == None:
                        permissions_check['doc_mod'][
                            attr] = NAWAH_VALUES.NONE_VALUE
                for attr in del_args:
                    del permissions_check['doc_mod'][attr]
                # [DOC] Update doc with doc permissions args
                doc.update(permissions_check['doc_mod'])
                doc = {
                    attr: doc[attr]
                    for attr in doc.keys()
                    if doc[attr] != NAWAH_VALUES.NONE_VALUE
                }

        if Event.ARGS not in skip_events:
            try:
                await _validate_args(args=query,
                                     args_list_label='query',
                                     args_list=self.query_args)
            except InvalidCallArgsException as e:
                test_query = e.args[0]
                for i in range(len(test_query)):
                    test_query[i] = ('[' + ', '.join([
                        f'\'{arg}\': {val.capitalize()}'
                        for arg, val in test_query[i].items() if val != True
                    ]) + ']')
                return await self.return_results(
                    ws=env['ws'] if 'ws' in env.keys() else None,
                    results=DictObj({
                        'status':
                        400,
                        'msg':
                        'Could not match query with any of the required query_args. Failed sets:'
                        + ', '.join(test_query),
                        'args':
                        DictObj({
                            'code':
                            f'{self.module.package_name.upper()}_{self.module.module_name.upper()}_INVALID_QUERY'
                        }),
                    }),
                    call_id=call_id,
                )

            try:
                await _validate_args(args=doc,
                                     args_list_label='doc',
                                     args_list=self.doc_args)
            except InvalidCallArgsException as e:
                test_doc = e.args[0]
                for i in range(len(test_doc)):
                    test_doc[i] = ('[' + ', '.join([
                        f'\'{arg}\': {val.capitalize()}'
                        for arg, val in test_doc[i].items() if val != True
                    ]) + ']')
                return await self.return_results(
                    ws=env['ws'] if 'ws' in env.keys() else None,
                    results=DictObj({
                        'status':
                        400,
                        'msg':
                        'Could not match doc with any of the required doc_args. Failed sets:'
                        + ', '.join(test_doc),
                        'args':
                        DictObj({
                            'code':
                            f'{self.module.package_name.upper()}_{self.module.module_name.upper()}_INVALID_DOC'
                        }),
                    }),
                    call_id=call_id,
                )

        for arg in doc.keys():
            if type(doc[arg]) == BaseModel:
                doc[arg] = doc[arg]._id  # type: ignore

        # [DOC] check if $soft oper is set to add it to events
        if '$soft' in query and query['$soft'] == True:
            skip_events.append(Event.SOFT)
            del query['$soft']

        # [DOC] check if $extn oper is set to add it to events
        if '$extn' in query and query['$extn'] == False:
            skip_events.append(Event.EXTN)
            del query['$extn']

        try:
            # [DOC] Use getattr to get the method implementation as module._method_METHOD_NAME, which is a fake name that allows BaseModule.__getattribute__ to correctly return the implementation rather than BaseMethod
            method = getattr(self.module, f'_method_{self.method}')
            # [DOC] Call method function
            if self.watch_method:
                await env['ws'].send_str(JSONEncoder().encode({
                    'status': 200,
                    'msg': 'Created watch task.',
                    'args': {
                        'code': 'CORE_WATCH_OK',
                        'watch': call_id,
                        'call_id': call_id,
                    },
                }))
                watch_loop = self.watch_loop(
                    ws=env['ws'],
                    stream=method(skip_events=skip_events,
                                  env=env,
                                  query=query,
                                  doc=doc),
                    call_id=call_id,
                    watch_task=env['watch_tasks'][call_id],
                )
                env['watch_tasks'][call_id] = {'watch': watch_loop}
                env['watch_tasks'][call_id]['task'] = asyncio.create_task(
                    watch_loop)
                return None
            else:
                try:
                    results = await method(skip_events=skip_events,
                                           env=env,
                                           query=query,
                                           doc=doc)
                except MethodException as e:
                    results = e.args[0]

                if type(results) == coroutine:
                    raise TypeError(
                        'Method returned coroutine rather than acceptable results format.'
                    )

                results = DictObj(results)
                try:
                    results['args'] = DictObj(results.args)
                except Exception:
                    results['args'] = DictObj({})

                logger.debug(f'Call results: {JSONEncoder().encode(results)}')
                # [DOC] Check for session in results
                if 'session' in results.args:
                    if results.args.session._id == 'f00000000000000000000012':
                        # [DOC] Updating session to __ANON
                        anon_user = _compile_anon_user()
                        anon_session = _compile_anon_session()
                        anon_session['user'] = DictObj(anon_user)
                        env['session'] = BaseModel(anon_session)
                    else:
                        # [DOC] Updating session to user
                        env['session'] = results.args.session

                return await self.return_results(
                    ws=env['ws'] if 'ws' in env.keys() else None,
                    results=results,
                    call_id=call_id)
            # query = Query([])
        except Exception as e:
            logger.error(
                f'An error occurred. Details: {traceback.format_exc()}.')
            tb = sys.exc_info()[2]
            if tb is not None:
                prev = tb
                current = tb.tb_next
                while current is not None:
                    prev = current
                    current = current.tb_next
                logger.error(
                    f'Scope variables: {JSONEncoder().encode(prev.tb_frame.f_locals)}'
                )
            query = Query([])
            if Config.debug:
                return await self.return_results(
                    ws=env['ws'] if 'ws' in env.keys() else None,
                    results=DictObj({
                        'status':
                        500,
                        'msg':
                        f'Unexpected error has occurred [method:{self.module.module_name}.{self.method}] [{str(e)}].',
                        'args':
                        DictObj({
                            'code': 'CORE_SERVER_ERROR',
                            'method':
                            f'{self.module.module_name}.{self.method}',
                            'err': str(e),
                        }),
                    }),
                    call_id=call_id,
                )
            else:
                return await self.return_results(
                    ws=env['ws'] if 'ws' in env.keys() else None,
                    results=DictObj({
                        'status':
                        500,
                        'msg':
                        'Unexpected error has occurred.',
                        'args':
                        DictObj({'code': 'CORE_SERVER_ERROR'}),
                    }),
                    call_id=call_id,
                )