def get_filters( self, *, query: Optional[QueryType] = None, query_schema: SchemaTypeOrStr = "query_schema", ) -> Dict[str, Any]: """Collect a dictionary of filters from the request query string. If :attr:`path_schema` is defined, it collects filter data from path variables as well. :param query: optional query dictionary (will be overwritten by the request.query) :param query_schema: a dataclass or an the name of an attribute in Operation for collecting query filters """ combined = MultiDict(query or ()) combined.update(self.request.query) try: params = self.cleaned(query_schema, combined, multiple=True) except web.HTTPNotImplemented: params = {} if self.path_schema: path = self.cleaned("path_schema", self.request.match_info) params.update(path) return params
def update_query(self, *args, **kwargs): """Return a new URL with query part updated.""" s = self._get_str_query(*args, **kwargs) new_query = MultiDict(parse_qsl(s, keep_blank_values=True)) query = MultiDict(self.query) query.update(new_query) return URL(self._val._replace(query=self._get_str_query(query)), encoded=True)
def update_query(self, *args, **kwargs): """Return a new URL with query part updated.""" s = self._get_str_query(*args, **kwargs) new_query = MultiDict(parse_qsl(s, keep_blank_values=True)) query = MultiDict(self.query) query.update(new_query) return URL(self._val._replace(query=self._get_str_query(query)), encoded=True)
async def statements(self, triple=(None, None, None), context=None, infer=None, **kwargs): """ An async generator over statements and contexts in a RDF4J Triple store. This returns the triples in a streaming matter. Which allows direct processing of the incoming data, but does not allow sorting or slicing. THIS IS AN ASYNC GENERATOR :param triple_pattern: an rdflib triple pattern for (S, P, O) :param context: the context. If None (default) the default graph is used. Which usually means the union of all graphs is iterated. Context should be an URI or but can also accept a rdflib.Graph. List/set/tuple of URIs/Graphs are also accepted. :param infer: Overwrites the default infer parameter. :param kwargs: Get merged with the POST kwargs. Overwrites already set kwargs. Uses this to add a custom timeout=X (X=0 by default), for example. The keyword "headers" is removed from kwargs. :return: An async generator of :class Statements ((S,P,O), C) """ #_ = kwargs.pop("params", None) _ = kwargs.pop("header", None) url = self.rest_services["statements"] header = {"ACCEPT": "application/x-binary-rdf"} infer = self._infer if infer is None else infer assert type(infer) == bool, f"infer ist not a boolean: {infer}" s, p, o = triple params = MultiDict(dict(infer=str(infer).lower())) if s: assert isinstance( s, (URIRef, BNode)), f"{s}: {type(s)} -> not a valid subject" params["subj"] = s.n3() if p: assert isinstance( p, (URIRef, BNode)), f"{p}: {type(p)} -> not a valid predicate" params["pred"] = p.n3() if o: assert isinstance( o, (URIRef, BNode, Literal)), f"{o}: {type(o)} -> not a valid object" params["obj"] = o.n3() if context: if isinstance(context, (set, list, tuple)): params.update([("context", repair_context(i, as_n3=True)) for i in context]) else: params.update([("context", repair_context(context, as_n3=True))]) # params.update([("context", 'null')]) xkwargs = dict(params=params, timeout=0, headers=header) xkwargs.update(kwargs) logger.debug(f"GET params are: {xkwargs}") resp = await self._session.get(url, **xkwargs) async for i in BinaryRDFParser(resp.content): yield i
class View(web.View): def __init__(self, request: web.Request, match_dict: T.Optional[T.Mapping[str, str]] = None, *args, **kwargs): super().__init__() if match_dict is None: rel_url = request.rel_url else: rel_url = self.aiohttp_resource().url_for(**match_dict) self.__rel_url = rel_url self.__embed = None self.__query = None self.__canonical_rel_url = None self.__match_dict = dict( # Ugly: we're using non-public member ``_match()`` of # :class:`aiohttp.web.Resource`. But most alternatives are # equally ugly. self.aiohttp_resource()._match(self.rel_url.raw_path)) def __getitem__(self, item): # language=rst """Shorthand for :meth:`self.match_dict[item] <match_dict>`""" return self.__match_dict[item] @property def match_dict(self): # language=rst """ Todo: Add documentation, explaining the difference between this method and :attr:`self.request.match_info <aiohttp.web.Request.match_info>`. """ return self.__match_dict if self.__match_dict is not None else self.request.match_info @match_dict.setter def match_dict(self, value): self.__match_dict = dict(value) @property def rel_url(self) -> web.URL: # language=rst """The relative URL as passed to the constructor.""" return self.__rel_url @property def canonical_rel_url(self) -> web.URL: # language=rst """Like :meth:`rel_url`, but with all default query parameters explicitly listed.""" if self.__canonical_rel_url is None: self.__canonical_rel_url = self.__rel_url.with_query(self.query) # noinspection PyTypeChecker return self.__canonical_rel_url @property def query(self): # language=rst """Like ``self.rel_url.query``, but with default parameters added. These default parameters are retrieved from the swagger definition. """ if self.__query is None: self.__query = MultiDict(self.default_query_params) self.__query.update(self.__rel_url.query) return self.__query @property def default_query_params(self) -> T.Dict[str, str]: return {} @classmethod def add_to_router(cls, router: web.UrlDispatcher, path: str, expect_handler: T.Callable = None): # language=rst """Adds this View class to the aiohttp router.""" cls._aiohttp_resource = router.add_resource(path) # Register the current class in the appropriate registry: # if isinstance(cls._aiohttp_resource, web.DynamicResource): # View.PATTERNS[cls._aiohttp_resource.get_info()['pattern']] = cls # elif isinstance(cls._aiohttp_resource, web.PlainResource): # View.PATHS[cls._aiohttp_resource.get_info()['path']] = cls # else: # _logger.critical("aiohttp router method 'add_resource()' returned resource object of unexpected type %s", cls._aiohttp_resource.__class__) if (not isinstance(cls._aiohttp_resource, web.DynamicResource) and not isinstance(cls._aiohttp_resource, web.PlainResource)): _logger.critical( "aiohttp router method 'add_resource()' returned resource object of unexpected type %s", cls._aiohttp_resource.__class__) cls._aiohttp_resource.rest_utils_class = cls cls._aiohttp_resource.add_route('*', cls, expect_handler=expect_handler) return cls._aiohttp_resource @classmethod def aiohttp_resource( cls) -> T.Union[web.PlainResource, web.DynamicResource]: assert hasattr(cls, '_aiohttp_resource'), \ f"{cls!s}.aiohttp_resource() called before {cls!s}.add_to_router()" try: return cls._aiohttp_resource except AttributeError: raise AssertionError async def get(self) -> web.StreamResponse: if _GET_IN_PROGRESS in self.request: raise web.HTTPInternalServerError() self.request[_GET_IN_PROGRESS] = True if self.request.method == 'GET': data = await self.to_json() response = web.StreamResponse() if isinstance(await self.etag(), str): response.headers.add('ETag', await self.etag()) response.content_type = self.best_content_type response.enable_compression() if str(self.canonical_rel_url) != str(self.request.rel_url): response.headers.add('Content-Location', str(self.canonical_rel_url)) await response.prepare(self.request) if self.request.method == 'GET': pass # TODO: implement. response.write_eof() del self.request[_GET_IN_PROGRESS] return response async def head(self): return await self.get()
class View(web.View): SEGMENT_RE = re.compile(r'([^/{}]+)/?$') PATHS = {} PATTERNS = {} def __init__(self, request: web.Request, match_dict: T.Optional[collections.abc.Mapping] = None, embed=None): super().__init__(request) if match_dict is None: rel_url = request.rel_url else: rel_url = self.aiohttp_resource().url_for(**match_dict) if embed is not None: rel_url = rel_url.update_query(embed=embed) self.__rel_url = rel_url self.__embed = None self.__query = None self.__canonical_rel_url = None self.__etag = None self.__match_dict = dict( # Ugly: we're using non-public member ``_match()`` of # :class:`aiohttp.web.Resource`. But most alternatives are # equally ugly. self.aiohttp_resource()._match(self.rel_url.raw_path)) def __getitem__(self, item): # language=rst """Shorthand for ``self.match_dict[item]``""" return self.__match_dict[item] @property def match_dict(self): return self.__match_dict def add_embed_to_url(self, url: web.URL, link_relation): embed = self.embed.get(link_relation) if embed is None: return url return url.update_query(embed=embed) @property def rel_url(self) -> web.URL: # language=rst """The relative URL as passed to the constructor.""" return self.__rel_url @property def canonical_rel_url(self) -> web.URL: # language=rst """Like :meth:`rel_url`, but with all default query parameters explicitly listed.""" if self.__canonical_rel_url is None: self.__canonical_rel_url = self.__rel_url.with_query(self.query) # noinspection PyTypeChecker return self.__canonical_rel_url @property def to_link(self) -> T.Dict[str, str]: """The HAL JSON link object to this resource.""" result = {'href': str(self.canonical_rel_url)} if self.link_name is not None: result['name'] = self.link_name if self.link_title is not None: result['title'] = self.link_title return result async def etag(self) -> T.Union[None, bool, str]: # language=rst """ Return values have the following meanings: ``True`` Resource exists but doesn't support ETags ``False`` Resource doesn't exist and doesn't support ETags ``None`` Resource doesn't exist and supports ETags. ETag string: Resource exists and supports ETags. """ return isinstance(self.aiohttp_resource(), web.PlainResource) @property def query(self): # language=rst """Like ``self.rel_url.query``, but with default parameters added. These default parameters are retrieved from the swagger definition. """ if self.__query is None: self.__query = MultiDict(self.default_query_params) self.__query.update(self.__rel_url.query) return self.__query @property def embed(self): if self.__embed is None: embed = ','.join(self.query.getall('embed', default=[])) self.__embed = parse_embed(embed) return self.__embed @staticmethod def construct_resource_for(request: web.Request, rel_url: web.URL) \ -> T.Optional[web.View]: # language=rst """ .. deprecated:: v0 Only used by :meth:`PlainView._links` and :meth:`DynamicView._links`, which are themselves deprecated. """ for resource in request.app.router.resources(): match_dict = resource._match(rel_url.raw_path) if match_dict is not None: if hasattr(resource, 'rest_utils_class'): return resource.rest_utils_class(request, rel_url) _logger.error( "Path %s doesn't resolve to rest_utils.Resource.", str(rel_url)) return None return None @property def default_query_params(self) -> T.Dict[str, str]: return {} @classmethod def add_to_router(cls, router: web.UrlDispatcher, path: str, expect_handler: T.Callable = None): # language=rst """Adds this View class to the aiohttp router.""" cls._aiohttp_resource = router.add_resource(path) # Register the current class in the appropriate registry: if isinstance(cls._aiohttp_resource, web.DynamicResource): View.PATTERNS[cls._aiohttp_resource.get_info()['pattern']] = cls elif isinstance(cls._aiohttp_resource, web.PlainResource): View.PATHS[cls._aiohttp_resource.get_info()['path']] = cls else: _logger.critical( "aiohttp router method 'add_resource()' returned resource object of unexpected type %s", cls._aiohttp_resource.__class__) cls._aiohttp_resource.rest_utils_class = cls cls._aiohttp_resource.add_route('*', cls, expect_handler=expect_handler) return cls._aiohttp_resource @classmethod def aiohttp_resource( cls) -> T.Union[web.PlainResource, web.DynamicResource]: assert hasattr(cls, '_aiohttp_resource'), \ "%s.aiohttp_resource() called before .add_to_router()" % cls return cls._aiohttp_resource @property def link_name(self) -> T.Optional[str]: # language=rst """A more or less unique name for the resource. This default implementation returns the last path segment of the url of this resource if that last path segment is templated. Otherwise `None` is returned (in which case there's no `name` attribute in link objects for this resource). See also :meth:`to_link`. Subclasses can override this default implementation. """ formatter = self.aiohttp_resource().get_info().get('formatter') if formatter is not None and re.search(r'\}[^/]*/?$', formatter): return self.rel_url.name or self.rel_url.parent.name return None @property def link_title(self) -> T.Optional[str]: # language=rst """The title of this resource, to be used in link objects. This default implementation returns `None`, and there's no `title` attribute in HAL link objects. See also :meth:`to_link`. Subclasses can override this default implementation. """ return None async def attributes(self): # language=rst """ This default implementation returns *no* attributes, ie. an empty `dict`. Most subclasses should override this default implementation. """ return {} async def _links(self) -> T.Dict[str, T.Any]: # language=rst """ Called by :meth:`.links` and :meth:`.embedded`. See the documentation of these methods for more info. Most subclasses should override this default implementation. :returns: This method must return a dict. The values must have one of the following types: - asynchronous generator of `.View` objects - generator of `.View` objects - a `.View` object - a *link object* - Iterable of `.View`\s and/or *link objects* (may be mixed) where *link object* means a HALJSON link object, ie. a `dict` with at least a key ``href``. """ return {} async def embedded(self) -> T.Dict[str, T.Any]: result = {} _links = await self._links() for key, value in _links.items(): if key in self.embed: if (inspect.isasyncgen(value) or inspect.isgenerator(value) or isinstance(value, View) or isinstance(value, collections.abc.Iterable)): result[key] = value else: _logger.error("Don't know how to embed object: %s", value) return result async def links(self) -> T.Dict[str, T.Any]: result = {} _links = await self._links() for key, value in _links.items(): if key == 'item': key = 'item' if isinstance(value, View): if key not in self.embed: result[key] = value.to_link elif inspect.isasyncgen(value): if key not in self.embed: async def g1(resources): async for resource in resources: yield resource.to_link result[key] = g1(value) elif inspect.isgenerator(value): if key not in self.embed: def g2(resources): for resource in resources: yield resource.to_link result[key] = g2(value) elif isinstance(value, collections.Mapping): if key in self.embed: _logger.info( 'Client asked to embed unembeddable object: %s', value) result[key] = value elif isinstance(value, collections.abc.Iterable): def g3(key, value): for o in value: if not isinstance(o, View): if key in self.embed: _logger.info( 'Client asked to embed unembeddable object: %s', o) yield o elif key not in self.embed: yield o.to_link result[key] = g3(key, value) elif key not in self.embed: _logger.error("Don't know how to render object as link: %s", value) return result async def get(self) -> web.StreamResponse: # Assert we're not calling `get()` recursively within a single request: assert 'GET_IN_PROGRESS' not in self.request self.request['GET_IN_PROGRESS'] = True etag = await self.etag() if not etag: raise web.HTTPNotFound() assert_preconditions(self.request, etag) if self.request.method == 'GET': data = await self.to_dict() response = web.StreamResponse() if isinstance(await self.etag(), str): response.headers.add('ETag', await self.etag()) response.content_type = self.request[BEST_CONTENT_TYPE] response.enable_compression() if str(self.canonical_rel_url) != str(self.request.rel_url): response.headers.add('Content-Location', str(self.canonical_rel_url)) await response.prepare(self.request) if self.request.method == 'GET': async for chunk in _json.json_encode(data): response.write(chunk) response.write_eof() del self.request['GET_IN_PROGRESS'] return response async def head(self): return await self.get() async def to_dict(self): result = await self.attributes() if isinstance(await self.etag(), str): result['_etag'] = await self.etag() result['_links'] = await self.links() if 'self' not in result['_links']: result['_links']['self'] = self.to_link result['_embedded'] = await self.embedded() if len(result['_embedded']) == 0: del result['_embedded'] return result
def as_params(*, params=None, **kwargs): params = MultiDict(params if params is not None else {}) params.update(kwargs) return params