Commit 9e44392d authored by Nicolas Chauvat's avatar Nicolas Chauvat
Browse files

[pyramid] add routes /{eid} and /{etype}/{eid} to return RDF when rdf mimetype...

[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
parent c87c3943d6ab
......@@ -231,6 +231,8 @@ def includeme(config):
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
......
......@@ -24,46 +24,57 @@
from cubicweb._exceptions import UnknownEid
class MatchIsETypePredicate(object):
"""A predicate that match if a given etype exist in schema.
class MatchIsEIDPredicate(object):
"""A predicate that match if a given eid exist in the database.
"""
def __init__(self, matchname, config):
self.matchname = matchname
def text(self):
return 'match_is_etype = %s' % self.matchname
return 'match_is_eid = %s' % self.matchname
phash = text
def __call__(self, info, request):
return info['match'][self.matchname].lower() in \
request.registry['cubicweb.registry'].case_insensitive_etypes
try:
eid = int(info['match'][self.matchname])
except ValueError:
return False
try:
request.cw_cnx.entity_from_eid(eid)
except UnknownEid:
return False
return True
class MatchIsEIDPredicate(object):
"""A predicate that match if a given eid exist in the database.
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, matchname, config):
self.matchname = matchname
def __init__(self, matchnames, config):
self.match_etype, self.match_eid = matchnames
def text(self):
return 'match_is_eid = %s' % self.matchname
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.matchname])
eid = int(info['match'][self.match_eid])
except ValueError:
return False
try:
request.cw_cnx.entity_from_eid(eid)
entity = request.cw_cnx.entity_from_eid(eid)
except UnknownEid:
return False
return True
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)
......@@ -18,84 +18,33 @@
# You should have received a copy of the GNU Lesser General Public License along
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
"""Pyramid resource definitions for CubicWeb."""
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
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 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
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
......@@ -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__)
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment