def test_invalid_subresource_path_fail(): """Ensure a bad subresource path fails.""" query_params = {"album.tracks._sorts_.failhere": "track_id,-name"} parser = ModelQueryParamParser(query_params) with raises(ParseError) as excinfo: parser.parse_subfilters() assert excinfo.value.code == "invalid_subresource_path"
def test_parse_simple_subquery_fail(): """Test a simple subquery fails with invalid input.""" query_params = {"tracks._subquery_": 5} parser = ModelQueryParamParser(query_params) with raises(FilterParseError) as excinfo: parser.parse_subfilters() assert excinfo.value.code == "invalid_complex_filters"
def test_suboffset_parser_bad_value_fail(): """Ensure basic suboffset parsing fails appropriately.""" query_params = {"album.tracks._offset_": "test"} parser = ModelQueryParamParser(query_params) with raises(OffsetLimitParseError) as excinfo: parser.parse_subfilters() assert excinfo.value.code == "invalid_suboffset_value"
def test_parse_simple_subquery(): """Test a simple subquery is handled properly.""" query_params = {"tracks._subquery_.playlists.playlist_id": 5} parser = ModelQueryParamParser(query_params) result = parser.parse_subfilters() filters = result["tracks"].filters assert filters["$and"][0]["playlists.playlist_id"]["$eq"] == 5
def test_subfilter_parser(): """Ensure basic subfilter parsing works.""" query_params = {"album.tracks._subquery_": '{"track_id": 5}'} parser = ModelQueryParamParser(query_params) result = parser.parse_subfilters() expected_result = {"$and": [{"track_id": 5}]} assert expected_result == result["album.tracks"].filters
def test_parse_complex_subquery(): """Test a complex subquery is handled properly.""" query_params = { "tracks._subquery_.playlists": json.dumps({"playlist_id": 5}) } parser = ModelQueryParamParser(query_params) result = parser.parse_subfilters() filters = result["tracks"].filters assert filters["$and"][0]["playlists"]["playlist_id"] == 5
def test_subsorts_parser(): """Ensure basic subsorts parsing works.""" query_params = {"album.tracks._sorts_": "track_id,-name"} parser = ModelQueryParamParser(query_params) result = parser.parse_subfilters() assert len(result["album.tracks"].sorts) == 2 assert result["album.tracks"].sorts[0].attr == "track_id" assert result["album.tracks"].sorts[0].direction == "ASC" assert result["album.tracks"].sorts[1].attr == "name" assert result["album.tracks"].sorts[1].direction == "DESC"
def test_subresource_nested_query(db_session): """Ensure a simple subresource query works.""" query_params = { "tracks._subquery_.track_id-gte": 5, "tracks.playlists._subquery_.playlist_id-lte": 5 } parser = ModelQueryParamParser(query_params) album_resource = AlbumResource(session=db_session) result = album_resource.get_collection( subfilters=parser.parse_subfilters(), embeds=parser.parse_embeds()) success = False for album in result: if album["album_id"] == 3: assert len(album["tracks"]) == 1 assert album["tracks"][0]["track_id"] == 5 success = True assert success
def test_invalid_subresource_path_ignore(): """Ensure silent failure on subresource path when not strict.""" query_params = {"album.tracks._sorts_.failhere": "track_id,-name"} parser = ModelQueryParamParser(query_params) result = parser.parse_subfilters(strict=False) assert len(result) == 0
def test_suboffset_parser(): """Ensure basic suboffset parsing works.""" query_params = {"album.tracks._offset_": 5} parser = ModelQueryParamParser(query_params) result = parser.parse_subfilters() assert result["album.tracks"].offset == 5
def test_sublimit_parser_bad_value_ignore(): """Ensure non strict basic sublimit parsing ignores errors.""" query_params = {"album.tracks._limit_": "test", "album.tracks._offset_": 5} parser = ModelQueryParamParser(query_params) result = parser.parse_subfilters(strict=False) assert result["album.tracks"].offset == 5
def get(self, path, query_params=None, strict=True, head=False): """Generic API router for GET requests. :param str path: The resource path specified. This should not include the root ``/api`` or any versioning info. :param query_params: Dictionary of query parameters, likely provided as part of a request. Defaults to an empty dict. :type query_params: dict or None :param bool strict: If ``True``, bad query params will raise non fatal errors rather than ignoring them. :param bool head: ``True`` if this was a HEAD request. :return: If this is a single entity query, an individual resource or ``None``. If this is a collection query, a list of resources. If it's an instance field query, the raw field value. :raise ResourceNotFoundError: If no resource can be found at the provided path. :raise BadRequestError: Invalid filters, sorts, fields, embeds, offset, or limit as defined in the provided query params will result in a raised exception if strict is set to ``True``. """ if self.resource is None: self._deduce_resource(path) path_objs = self._get_path_objects(path) resource = path_objs.get("resource", None) path_part = path_objs.get("path_part", None) query_session = path_objs.get("query_session", None) ident = path_objs.get("ident", None) parser = ModelQueryParamParser(query_params, context=self.context) fields = parser.parse_fields() embeds = parser.parse_embeds() try: subfilters = parser.parse_subfilters(strict=strict) except ParseError as exc: if strict: raise BadRequestError(code=exc.code, message=exc.message, **exc.kwargs) subfilters = None # last path_part determines what type of request this is if isinstance(path_part, Field) and not isinstance( path_part, NestedPermissibleABC): # Simple property, such as album_id # return only the value field_name = path_part.data_key or path_part.name result = resource.get(ident=ident, fields=[field_name], strict=strict, session=query_session, head=head) if result is not None and field_name in result: return result[field_name] raise self.make_error("resource_not_found", path=path) # pragma: no cover if isinstance(path_part, Field) or isinstance(path_part, BaseModelResource): # resource collection # any non subresource field would already have been handled try: filters = parser.parse_filters( resource.model, convert_key_names_func=resource.convert_key_name) except FilterParseError as e: if strict: raise BadRequestError(code=e.code, message=e.message, **e.kwargs) filters = None if not (isinstance(path_part, Nested) and not path_part.many): try: offset_limit_info = parser.parse_offset_limit( resource.page_max_size) offset = offset_limit_info.offset limit = offset_limit_info.limit except OffsetLimitParseError as e: if strict: raise BadRequestError(code=e.code, message=e.message, **e.kwargs) offset, limit = None, None sorts = parser.parse_sorts() results = resource.get_collection(filters=filters, subfilters=subfilters, fields=fields, embeds=embeds, sorts=sorts, offset=offset, limit=limit, session=query_session, strict=strict, head=head) if query_params.get("page") is not None or not offset: results.current_page = int(query_params.get("page") or 1) results.page_size = limit or resource.page_max_size return results else: result = resource.get_collection(fields=fields, embeds=embeds, subfilters=subfilters, session=query_session, strict=strict, head=head) if len(result) != 1: # pragma: no cover # failsafe, _get_path_objects will catch this first. raise self.make_error("resource_not_found", path=path) return result[0] elif isinstance(path_part, tuple): # path part is a resource identifier # individual instance return resource.get(ident=path_part, fields=fields, embeds=embeds, subfilters=subfilters, strict=strict, session=query_session, head=head) raise self.make_error("resource_not_found", path=path) # pragma: no cover