====== Become RDF and RESTful with Pyramid ====== ===== Content Negotiation ===== === Two alternatives: header and extension === The main reason to do content negotiation is that different documents should be provided for humans and for machines. Content Negotiation can be implemented in two different ways: - The header of the http request asks for a specific representation type with the **Accept** field of the header ''Accept: text/html'' - The request provides an extension at the end of the URL path: ''https:example.com/somepath.json'' Both things can be easily implemented with web frameworks or libraries like pyramid ([[biodivinf:pyramid_content_negotiation#Using Python Pyramid for Content Negotiation|see below]]), python liburl2, Java url dispatch libraries. Implementation via apache2 / nginx configuration can only do the basics for redirection, but interferes with program code when developing more complex content negotiation pathes. === The fragment identifier and the resources thing === But in context of RDF there is an additional differentiation between the resource that is identified with an URI and the information about this resource, that is provided to the human or machine. As the resource can in most cases not provided via the web, e. g. the resource https://dbpedia.org/resource/Eiffel_Tower the URI of a resource should not provide any content. Instead a permanent redirection to the appropriate content for machines or humans should be implemented. Again, there are two different ways of implementation: - **Fragment Identifiers**: The URI to the resource contains a fragment identifier, i. e. a part beginning with # at the end of the URI path: http://example.com/foaf/Herbert_Feuerstein#Herbert_Feuerstein. The fragment identifier just points to an anchor within a document but the server always provides the complete document behind the URI path irrespective of whether the anchor exists or not. Thus fragment identifiers are an easy way to do redirection from the resource URI to the representation. - **URL pathes** with a specific part for the resource, e .g ''/resource/''. dbpedia and others use a special path for the resource URIs. Depending on the Accept header in the http request this URIs are redirected to the appropriate URLs for providing human readable or machine readable content. For example calling http://dbpedia.org/resource/Eiffel_Tower from the browser will redirect to http://dbpedia.org/page/Eiffel_Tower. ---- ===== Using Python Pyramid for Content Negotiation ===== Pyramid provides and easy way to manage content negotiation via the views **decorators**. One can define different views that return either text/html content or json, or string, or whatever you want. The views get the same **decorator** with some additional parameters. For example to serve html or json content the following will do the job: Define a path in %%__init __.py%% from pyramid.config import Configurator def main(global_config, **settings): config = Configurator(settings=settings) config.include('pyramid_chameleon') # define a route with tablename as obligate part of the path # and pkids as a list of optional path components: e. g. example.com/table/Identification/12/16/8 config.add_route('table', '/table/{tablename}*pkids') # define another route with tablename as obligate part of the path # and pkids as a list of optional path components: e. g. example.com/download/Identification/12/16/8 config.add_route('download', '/download/{tablename}*pkids') # define a route for content negotiation by file extension config.add_route('file', '/file/{tablename}*pkids.{ext}') [...] config.scan('.views') return config.make_wsgi_app() Define the views and their decorators in views.py: [...] # define a decorator that handles requests with Accept: text/html in header @view_config(route_name='table', accept='text/html', renderer='tablelist.pt') def tablelist(self): # some code that return html content for the renderer tablelist.pt [...] # define a decorator that handles requests with Accept: application/json in header @view_config(route_name='table', accept='application/json', renderer='json') def tablelistJSON(self): # some code that return json content for the renderer json [...] # define a decorator that handles a request with Accept: text/html in header but handles the route download that # opens a download dialog and provides the json content as string @view_config(route_name='download', request_method='GET', accept='text/html', renderer = 'string') def tablelistdownload(self): # some code that returns the json data with pretty formatting in string format [...] # define a decorator that does content negotiation on the extension given in the last part of the path @view_config(route_name='file', request_method='GET') def tablelistfile(self): # this view is only called when an extension with .ext exists at the end of the URL path # the different extensions must be handled via the matchdict construct of python pyramid # I am not sure that *pkids.{ext} work together, otherwise .{ext} must be called with defined pathes # in __init__.py like this: config.add_route('file', '/file/{tablename}/{pkid1}/{pkid2}/{pkid3}.{ext}') tablename = self.request.matchdict['tablename'] pkids = self.request.matchdict['pkids'] ext = self.request.matchdict['ext'] # some code that returns the data in the requested format