# HG changeset patch # User Nicolas Chauvat # Date 1583880470 -3600 # Node ID a17cbf539a69f3957784605d386e8a79bc9ac488 # Parent c87c3943d6ab362b5a33d820fafe83f871807757 [pyramid] add routes /{eid} and /{etype}/{eid} to return RDF when rdf mimetype in Accept HTTP headers * simplify pyramid/resources.py by making the classmethod that returns a closure a simple function and removing the EntityResource and ETYpeResource classes that are barely used * replace predicate MatchIsETypePredicate with MatchIsETypeAndEIDPredicate Team: famarger, schabot, nchauvat, fferry, ethieblin diff -r c87c3943d6ab -r a17cbf539a69 cubicweb/pyramid/__init__.py --- a/cubicweb/pyramid/__init__.py Tue Mar 10 23:44:45 2020 +0100 +++ b/cubicweb/pyramid/__init__.py Tue Mar 10 23:47:50 2020 +0100 @@ -231,6 +231,8 @@ cwconfig = config.registry.get('cubicweb.config') repo = config.registry.get('cubicweb.repository') + config.include('cubicweb.pyramid.rest_api') + if repo is not None: if cwconfig is None: config.registry['cubicweb.config'] = cwconfig = repo.config diff -r c87c3943d6ab -r a17cbf539a69 cubicweb/pyramid/predicates.py --- a/cubicweb/pyramid/predicates.py Tue Mar 10 23:44:45 2020 +0100 +++ b/cubicweb/pyramid/predicates.py Tue Mar 10 23:47:50 2020 +0100 @@ -24,22 +24,6 @@ from cubicweb._exceptions import UnknownEid -class MatchIsETypePredicate(object): - """A predicate that match if a given etype exist in schema. - """ - def __init__(self, matchname, config): - self.matchname = matchname - - def text(self): - return 'match_is_etype = %s' % self.matchname - - phash = text - - def __call__(self, info, request): - return info['match'][self.matchname].lower() in \ - request.registry['cubicweb.registry'].case_insensitive_etypes - - class MatchIsEIDPredicate(object): """A predicate that match if a given eid exist in the database. """ @@ -64,6 +48,33 @@ return True +class MatchIsETypeAndEIDPredicate(object): + """A predicate that match if a given eid exist in the database and if the + etype of the entity same as the one given in the URL + """ + def __init__(self, matchnames, config): + self.match_etype, self.match_eid = matchnames + + def text(self): + return f"match_is_etype_and_eid = {self.match_etype}/{self.match_eid}" + + phash = text + + def __call__(self, info, request): + try: + eid = int(info['match'][self.match_eid]) + except ValueError: + return False + + try: + entity = request.cw_cnx.entity_from_eid(eid) + except UnknownEid: + return False + + etype = info['match'][self.match_etype] + return entity.__regid__.lower() == etype.lower() + + def includeme(config): - config.add_route_predicate('match_is_etype', MatchIsETypePredicate) config.add_route_predicate('match_is_eid', MatchIsEIDPredicate) + config.add_route_predicate('match_is_etype_and_eid', MatchIsETypeAndEIDPredicate) diff -r c87c3943d6ab -r a17cbf539a69 cubicweb/pyramid/resources.py --- a/cubicweb/pyramid/resources.py Tue Mar 10 23:44:45 2020 +0100 +++ b/cubicweb/pyramid/resources.py Tue Mar 10 23:47:50 2020 +0100 @@ -18,84 +18,33 @@ # You should have received a copy of the GNU Lesser General Public License along # with CubicWeb. If not, see . -"""Pyramid resource definitions for CubicWeb.""" +from pyramid.httpexceptions import HTTPNotFound -from rql import TypeResolverException - -from pyramid.decorator import reify -from pyramid.httpexceptions import HTTPNotFound +from cubicweb import rdf -class EntityResource(object): - - """A resource class for an entity. It provide method to retrieve an entity - by eid. - """ - - @classmethod - def from_eid(cls): - def factory(request): - return cls(request, None, None, request.matchdict['eid']) - return factory - - def __init__(self, request, cls, attrname, value): - self.request = request - self.cls = cls - self.attrname = attrname - self.value = value - - @reify - def rset(self): - req = self.request.cw_request - if self.cls is None: - return req.execute('Any X WHERE X eid %(x)s', - {'x': int(self.value)}) - st = self.cls.fetch_rqlst(self.request.cw_cnx.user, ordermethod=None) - st.add_constant_restriction(st.get_variable('X'), self.attrname, - 'x', 'Substitute') - if self.attrname == 'eid': - try: - rset = req.execute(st.as_string(), {'x': int(self.value)}) - except (ValueError, TypeResolverException): - # conflicting eid/type - raise HTTPNotFound() - else: - rset = req.execute(st.as_string(), {'x': self.value}) - return rset +def negociate_mime_type(request, possible_mimetypes): + accepted_headers_by_weight = sorted( + request.accept.parsed or [], key=lambda h: h[1], reverse=True + ) + mime_type_negociated = None + for parsed_header in accepted_headers_by_weight: + accepted_mime_type = parsed_header[0] + if accepted_mime_type in possible_mimetypes: + mime_type_negociated = accepted_mime_type + break + return mime_type_negociated -class ETypeResource(object): - - """A resource for etype. - """ - @classmethod - def from_match(cls, matchname): - def factory(request): - return cls(request, request.matchdict[matchname]) - return factory - - def __init__(self, request, etype): - vreg = request.registry['cubicweb.registry'] - - self.request = request - self.etype = vreg.case_insensitive_etypes[etype.lower()] - self.cls = vreg['etypes'].etype_class(self.etype) +def rdf_context_from_eid(request): + mime_type = negociate_mime_type(request, rdf.RDF_MIMETYPE_TO_FORMAT) + if mime_type is None: + raise HTTPNotFound() + entity = request.cw_request.entity_from_eid(request.matchdict['eid']) + return RDFResource(entity, mime_type) - def __getitem__(self, value): - # Try eid first, then rest attribute as for URL path evaluation - # mecanism in cubicweb.web.views.urlpublishing. - for attrname in ('eid', self.cls.cw_rest_attr_info()[0]): - resource = EntityResource(self.request, self.cls, attrname, value) - try: - rset = resource.rset - except HTTPNotFound: - continue - if rset.rowcount: - return resource - raise KeyError(value) - @reify - def rset(self): - rql = self.cls.fetch_rql(self.request.cw_cnx.user) - rset = self.request.cw_request.execute(rql) - return rset +class RDFResource: + def __init__(self, entity, mime_type): + self.entity = entity + self.mime_type = mime_type diff -r c87c3943d6ab -r a17cbf539a69 cubicweb/pyramid/rest_api.py --- a/cubicweb/pyramid/rest_api.py Tue Mar 10 23:44:45 2020 +0100 +++ b/cubicweb/pyramid/rest_api.py Tue Mar 10 23:47:50 2020 +0100 @@ -20,26 +20,44 @@ """Experimental REST API for CubicWeb using Pyramid.""" -from __future__ import absolute_import - +import rdflib from pyramid.view import view_config -from cubicweb.pyramid.resources import EntityResource, ETypeResource +from pyramid.response import Response + +from cubicweb import rdf +from cubicweb.pyramid.resources import rdf_context_from_eid, RDFResource @view_config( - route_name='cwentities', - context=EntityResource, - request_method='DELETE') -def delete_entity(context, request): - context.rset.one().cw_delete() - request.response.status_int = 204 - return request.response + route_name='one_entity', + context=RDFResource, +) +@view_config( + route_name='one_entity_eid', + context=RDFResource, +) +def view_entity_as_rdf(context, request): + graph = rdflib.ConjunctiveGraph() + rdf.add_entity_to_graph(graph, context.entity) + rdf_format = rdf.RDF_MIMETYPE_TO_FORMAT[context.mime_type] + response = Response(graph.serialize(format=rdf_format)) + response.content_type = context.mime_type + return response def includeme(config): config.include('.predicates') config.add_route( - 'cwentities', '/{etype}/*traverse', - factory=ETypeResource.from_match('etype'), match_is_etype='etype') + 'one_entity', + '/{etype}/{eid}', + factory=rdf_context_from_eid, + match_is_etype_and_eid=('etype', 'eid'), + ) + config.add_route( + 'one_entity_eid', + '/{eid}', + factory=rdf_context_from_eid, + match_is_eid='eid' + ) config.scan(__name__)