def _neighbours_rdf(self, mimetype): query = ''' SELECT * WHERE { <%(uri)s> ?p ?o . } ''' % { 'uri': self.uri } g = Graph() g.bind('prov', Namespace('http://www.w3.org/ns/prov#')) for r in _database.query(query)['results']['bindings']: if r['o']['type'] == 'literal': g.add((URIRef(self.uri), URIRef(r['p']['value']), Literal(r['o']['value']))) else: g.add((URIRef(self.uri), URIRef(r['p']['value']), URIRef(r['o']['value']))) query2 = ''' SELECT * WHERE { ?s ?p <%(uri)s> . } ''' % { 'uri': self.uri } for r in _database.query(query2)['results']['bindings']: g.add((URIRef(r['s']['value']), URIRef(r['p']['value']), URIRef(self.uri))) return Response( g.serialize(format=LDAPI.get_rdf_parser_for_mimetype(mimetype)), status=200, mimetype=mimetype)
def lodge_reportingsystem(): """Insert a ReportingSystem into the provenance _database""" # only accept RDF documents acceptable_mimes = LDAPI.get_rdf_mimetypes_list() ct = request.content_type if ct not in acceptable_mimes: return api_functions.client_error_response( 'The ReportingSystem posted is not encoded with a valid RDF Content-Type. Must be one of: ' + ', '.join(acceptable_mimes) + '.') # validate ReportingSystem rs = class_reportingsystems.IncomingReportingSystem(request) if not rs.valid(): return api_functions.client_error_response( 'The ReportingSystem posted is not valid for the following reasons: ' + ', '.join(rs.error_messages) + '.') # get the ReportingSystem's URI rs.determine_uri() rs.generate_named_graph_metadata() # store the ReportingSystem if not rs.stored(): return api_functions.server_error_response( 'ReportingSystem posted is valid but cannot be stored for the following reasons: ' + ', '.join(rs.error_messages) + '.') # reply to sender return Response(rs.uri, status=201, mimetype='text/plain')
def render(self, view, mimetype): if view == 'neighbours': if mimetype in LDAPI.get_rdf_mimetypes_list(): return self._neighbours_rdf(mimetype) elif mimetype == 'text/html': self._get_details() return self._neighbours_html() elif view == 'prov': if mimetype in LDAPI.get_rdf_mimetypes_list(): return Response(self._prov_rdf().serialize( format=LDAPI.get_rdf_parser_for_mimetype(mimetype)), status=200, mimetype=mimetype) elif mimetype == 'text/html': self._get_details() return self._prov_html()
def lodge_agent(): """Insert an Agent into the provenance _database""" # only accept RDF documents acceptable_mimes = LDAPI.get_rdf_mimetypes_list() ct = request.content_type if ct not in acceptable_mimes: return api_functions.client_error_response( 'The Agent posted is not encoded with a valid RDF Content-Type. Must be one of: ' + ', '.join(acceptable_mimes) + '.') # validate Agent sr = class_agents.IncomingAgent(request.data, request.content_type) if not sr.valid(): return api_functions.client_error_response( 'The Agent posted is not valid for the following reasons: ' + ', '.join(sr.error_messages) + '.') # get the Agent's URI sr.determine_uri() # store the Agent if not sr.stored(): return api_functions.server_error_response( 'The Agent posted is valid but cannot be stored for the following reasons: ' + ', '.join(sr.error_messages) + '.') # reply to sender return Response(sr.uri, status=201, mimetype='text/plain')
def render(self, view, mimetype): if mimetype == 'text/html': self._get_details() return self._html() else: self._get_details() return Response(self._rdf().serialize( format=LDAPI.get_rdf_parser_for_mimetype(mimetype)), mimetype=mimetype)
def instance(): """ Responds with one of a number of HTTP responses according to an allowed model and format of this object in the graph :return: and HTTP response """ # must have the URI of an object in the graph instance_uri = request.args.get('_uri') try: g = get_class_object(instance_uri) if not g: return client_error_response( 'No URI of an object in the provenance database was supplied. ' 'Expecting a query string argument \'_uri\'.') except ConnectionError: return render_template('error_db_connection.html'), 500 # the URI is of something in the graph so now we validate the requested model and format # find the class of the URI for s, p, o in g: if str(p) == 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type': # validate this request's model and format class_uri = str(o) views_formats = objects_functions.get_classes_views_formats().get( class_uri) try: view, mime_format = LDAPI.get_valid_view_and_format( request.args.get('_view'), request.args.get('_format'), views_formats) # if alternates model, return this info from file if view == 'alternates': instance_uri_encoded = urllib.parse.quote_plus( request.args.get('_uri')) class_uri_encoded = urllib.parse.quote_plus(class_uri) del views_formats['renderer'] return api_functions.render_alternates_view( class_uri, class_uri_encoded, instance_uri, instance_uri_encoded, views_formats, mime_format) else: # chooses a class to render this instance based on the specified renderer in # classes_views_formats.json # no need for further validation as instance_uri, model & format are already validated renderer = getattr(__import__('model'), views_formats['renderer']) endpoints = { 'instance': url_for('.instance'), 'sparql': url_for('api.sparql') } return renderer(instance_uri, endpoints).render(view, mime_format) except LdapiParameterError as e: return client_error_response(e)
def render(self, view, mimetype): if view == 'reg': # is an RDF format requested? if mimetype in LDAPI.get_rdf_mimetypes_list(): # it is an RDF format so make the graph for serialization self._make_dpr_graph(view) rdflib_format = LDAPI.get_rdf_parser_for_mimetype(mimetype) return Response(self.g.serialize(format=rdflib_format), status=200, mimetype=mimetype) elif mimetype == 'text/html': return render_template( 'class_register.html', class_name=self.request.args.get('_uri'), register=self.register) else: return Response( 'The requested model model is not valid for this class', status=400, mimetype='text/plain')
def _convert_proms_pingback_to_rdf(self): PROMS = Namespace('http://promsns.org/def/proms#') self.graph.bind('proms', PROMS) # type this pingback specifically self.graph.add( (URIRef(self.named_graph_uri), RDF.type, PROMS.PromsPingback)) # convert the data to RDF (just de-serialise it) self.graph += Graph().parse(data=self.request.data, format=LDAPI.get_rdf_parser_for_mimetype( self.request.mimetype))
def render(self, view, mimetype): if view == 'neighbours': # no work to be done as we have already loaded the triples if mimetype in LDAPI.get_rdf_mimetypes_list(): return self._neighbours_rdf(mimetype) elif mimetype == 'text/html': self._get_details() return self._neighbours_html() elif view == 'prov': if mimetype in LDAPI.get_rdf_mimetypes_list(): return Response(self._prov_rdf().serialize( format=LDAPI.get_rdf_parser_for_mimetype(mimetype)), status=200, mimetype=mimetype) # TODO: pre-render the viz.js image and serve static image # elif mimetype == 'image/svg': # return Response( # # ) elif mimetype == 'text/html': self._get_details() return self._prov_html()
def register(): """ Responds with a Register model response for classes listed in the graph Supported classes statically loaded from classes_views_formats.json In the future, we will dynamically work out which classes are supported. :param class_name: the name of a class of object in the graph db :return: an HTTP message based on a particular model and format of the class """ # check for a class URI uri = request.args.get('_uri') # ensure the class URI is one of the classes in the views_formats class_uris = objects_functions.get_class_uris() if uri not in class_uris: return client_error_response( 'No URI of a class in the provenance database was supplied. Expecting a query string argument \'_uri\' ' 'equal to one of the following: ' + ', '.join(class_uris) ) # validate this request's model and format class_uri = 'http://purl.org/linked-data/registry#Register' views_formats = objects_functions.get_classes_views_formats().get(class_uri) try: view, mime_format = LDAPI.get_valid_view_and_format( request.args.get('_view'), request.args.get('_format'), views_formats ) except LdapiParameterError as e: return client_error_response(e) # if alternates model, return this info from file if view == 'alternates': del views_formats['renderer'] return api_functions.render_alternates_view(uri, uriparse.parse.quote_plus(uri), None, None, views_formats, mime_format) # get the register of this class of thing from the provenance database try: class_register = get_class_register(uri) except ConnectionError: return render_template('error_db_connection.html'), 500 # since everything's valid, use the Renderer to return a response endpoints = { 'instance': url_for('.instance'), 'sparql': url_for('api.sparql') } return model.RegisterRenderer(request, uri, endpoints, class_register).render(view, mime_format)
def lodge_report(): """Insert a Report into the provenance database This function should be edited if any custom actions are to be undertaken by PROMS on reciept of a Report, such as Ppingbacks """ # only accept RDF documents acceptable_mimes = LDAPI.get_rdf_mimetypes_list() ct = request.content_type if ct not in acceptable_mimes: return api_functions.client_error_response( 'The Report posted is not encoded with a valid RDF Content-Type. Must be one of: ' + ', '.join(acceptable_mimes) + '.') # validate Report, using the controller validator class, IncomingReport r = class_reports.IncomingReport(request) if not r.valid(): return api_functions.client_error_response( 'The Report posted is not valid for the following reasons: ' + ', '.join(r.error_messages) + '.') # generate Named Graph metadata r.determine_uri() r.generate_named_graph_metadata() # store the Report # since it is valid and NG metadata has been built, we can store it if not r.stored(): return api_functions.server_error_response( 'Report posted is valid but cannot be stored for the following reasons: ' + ', '.join(r.error_messages) + '.') # Custom Actions # Only try to any custom actions, e.g. Pingbacks, if the module is turned on if conf.MODULES.get('pingbacks').get('valid'): # kick off any Pingbacks for this Report, as per chosen Pingbacks strategies # TODO: split Pingbacks process off into another thread from modules.pingbacks.engine import Engine e = Engine(r.graph, r.uri, url_for('modelx.instance'), url_for('.sparql')) # reply to sender return r.uri, 201
def valid(self): """Validates an incoming Pingback using direct _tests using the Pingbacks RuleSet""" # PROV Pingbacks can only be of mimtype text/uri-list if self.request.mimetype == 'text/uri-list': print(self.request.headers) conformant_pingback = ProvPingback(self.request) # ensure that this Pingback has the URI(s) of the Resource(s) it is for if self.request.args.get('resource_uri') is None: error_message = 'No resource URI is indicated for this pingback. Pingbacks sent to a PROMS Server ' \ 'instance must be posted to ' \ 'http://{POROMS_INTANCE}/function/lodge-pingback?resource_uri={RESOURCE_URI}' if self.error_messages is not None: self.error_messages.append(error_message) else: self.error_messages = [error_message] return False elif not LDAPI.is_a_uri(self.request.args.get('resource_uri')): error_message = 'The resource URI indicated for this pingback does not validate as a URI' if self.error_messages is not None: self.error_messages.append(error_message) else: self.error_messages = [error_message] return False # PROMS Pingbacks can only be of an RDF mimetype else: conformant_pingback = PromsPingback(self.request, self.pingback_endpoint) if not conformant_pingback.passed: self.error_messages = conformant_pingback.fail_reasons return False return True
def lodge_report(): """Insert a Report into the provenance database""" # only accept RDF documents acceptable_mimes = LDAPI.get_rdf_mimetypes_list() ct = request.content_type if ct not in acceptable_mimes: return api_functions.client_error_response( 'The Report posted is not encoded with a valid RDF Content-Type. Must be one of: ' + ', '.join(acceptable_mimes) + '.') # validate Report r = class_reports.IncomingReport(request) print(str(r)) if not r.valid(): return api_functions.client_error_response( 'The Report posted is not valid for the following reasons: ' + ', '.join(r.error_messages) + '.') # get the Report's URI r.determine_uri() r.generate_named_graph_metadata() # store the Report if not r.stored(): return api_functions.server_error_response( 'Report posted is valid but cannot be stored for the following reasons: ' + ', '.join(r.error_messages) + '.') # kick off any Pingbacks for this Report, as per chosen Pingbacks strategies # TODO: split this off into another thread from modules.pingbacks.engine import Engine e = Engine(r.graph, r.uri, url_for('modelx.instance'), url_for('.sparql')) # reply to sender return r.uri, 201
def render_alternates_view(class_uri, class_uri_encoded, instance_uri, instance_uri_encoded, views_formats, mimetype): """Renders an HTML table, a JSON object string or a serialised RDF representation of the alternate views of an object""" if mimetype == 'application/json': del views_formats[ 'renderer'] # the renderer used is not for public consumption! return Response(json.dumps(views_formats), status=200, mimetype='application/json') elif mimetype in LDAPI.get_rdf_mimetypes_list(): g = Graph() LDAPI_O = Namespace('http://promsns.org/def/ldapi#') g.bind('ldapi', LDAPI_O) DCT = Namespace('http://purl.org/dc/terms/') g.bind('dct', DCT) class_uri_ref = URIRef(uriparse.unquote_plus(class_uri)) if instance_uri: instance_uri_ref = URIRef(instance_uri) g.add((instance_uri_ref, RDF.type, class_uri_ref)) else: g.add((class_uri_ref, RDF.type, LDAPI_O.ApiResource)) # alternates model alternates_view = BNode() g.add((alternates_view, RDF.type, LDAPI_O.View)) g.add((alternates_view, DCT.title, Literal('alternates', datatype=XSD.string))) g.add((class_uri_ref, LDAPI_O.view, alternates_view)) # default model default_view = BNode() g.add((default_view, DCT.title, Literal('default', datatype=XSD.string))) g.add((class_uri_ref, LDAPI_O.defaultView, default_view)) default_title = views_formats['default'] # the ApiResource is incorrectly assigned to the class URI for view_name, formats in views_formats.items(): if view_name == 'alternates': for f in formats: g.add((alternates_view, URIRef('http://purl.org/dc/terms/format'), Literal(f, datatype=XSD.string))) elif view_name == 'default': pass elif view_name == 'renderer': pass else: x = BNode() if view_name == default_title: g.add((default_view, RDF.type, x)) g.add((class_uri_ref, LDAPI_O.view, x)) g.add((x, DCT.title, Literal(view_name, datatype=XSD.string))) for f in formats: g.add((x, URIRef('http://purl.org/dc/terms/format'), Literal(f, datatype=XSD.string))) rdflib_format = [ item[1] for item in LDAPI.MIMETYPES_PARSERS if item[0] == mimetype ][0] return Response(g.serialize(format=rdflib_format), status=200, mimetype=mimetype) else: # HTML return render_template('alternates_view.html', class_uri=class_uri, class_uri_encoded=class_uri_encoded, instance_uri=instance_uri, instance_uri_encoded=instance_uri_encoded, views_formats=views_formats)
class IncomingPingback(IncomingClass): acceptable_mimes = LDAPI.get_rdf_mimetypes_list() + ['text/uri-list'] def __init__(self, request): IncomingClass.__init__(self, request) self.pingback_endpoint = request.url self.determine_uri() self._generate_named_graph_uri() def valid(self): """Validates an incoming Pingback using direct tests using the Pingbacks RuleSet""" # PROV Pingbacks can only be of mimtype text/uri-list if self.request.mimetype == 'text/uri-list': print((self.request.headers)) conformant_pingback = ProvPingback(self.request) # ensure that this Pingback has the URI(s) of the Resource(s) it is for if self.request.args.get('resource_uri') is None: error_message = 'No resource URI is indicated for this pingback. Pingbacks sent to a PROMS Server ' \ 'instance must be posted to ' \ 'http://{POROMS_INTANCE}/function/lodge-pingback?resource_uri={RESOURCE_URI}' if self.error_messages is not None: self.error_messages.append(error_message) else: self.error_messages = [error_message] return False elif not LDAPI.is_a_uri(self.request.args.get('resource_uri')): error_message = 'The resource URI indicated for this pingback does not validate as a URI' if self.error_messages is not None: self.error_messages.append(error_message) else: self.error_messages = [error_message] return False # PROMS Pingbacks can only be of an RDF mimetype else: conformant_pingback = PromsPingback(self.request, self.pingback_endpoint) if not conformant_pingback.passed: self.error_messages = conformant_pingback.fail_reasons return False return True def determine_uri(self): pass # no need for this! def _generate_named_graph_uri(self): self.named_graph_uri = settings.PINGBACK_NAMED_GRAPH_BASE_URI + str(uuid.uuid4()) def convert_pingback_to_rdf(self): # the URI of the Named Graph for this Pingback must have been generated before doing this # so we can refer to the graph if self.named_graph_uri is not None: self.graph = Graph() # PROV Pingbacks can only be of mimtype text/uri-list if self.request.mimetype == 'text/uri-list': self._convert_prov_pingback_to_rdf() # PROMS Pingbacks can only be of an RDF mimetype else: self._convert_proms_pingback_to_rdf() else: raise Exception('The Incoming Pingback must have had a URI generated for it by PROMS Server before the data' 'for it is stored. The function determine_uri() generated the URI.') def _convert_prov_pingback_to_rdf(self): # every URI in the PROV-AQ message is treated as a provenance statement about the resource PROV = Namespace('http://www.w3.org/ns/prov#') self.graph.bind('prov', PROV) for uri_line in self.request.data.split('\n'): self.graph.add(( URIRef(self.request.args.get('resource_uri')), PROV.has_provenance, URIRef(uri_line) )) # if there are Link headers about other resources, create DCT provenance indicators for them too if self.request.headers.get('Link'): for link_header in self.request.headers.get('Link').split(','): uri, rel, anchor = link_header.split(';') self.graph.add(( URIRef(uri.strip('<>')), URIRef(rel.strip().replace('rel=', '').strip('"')), URIRef(anchor.strip().replace('anchor=', '').strip('"')) )) def _convert_proms_pingback_to_rdf(self): PROMS = Namespace('http://promsns.org/def/proms#') self.graph.bind('proms', PROMS) # type this pingback specifically self.graph.add(( URIRef(self.named_graph_uri), RDF.type, PROMS.PromsPingback )) # convert the data to RDF (just de-serialise it) self.graph += Graph().parse( data=self.request.data, format=LDAPI.get_rdf_parser_for_mimetype(self.request.mimetype) ) def generate_named_graph_metadata(self): # add graph metadata, regardless of the type of Pingback PROV = Namespace('http://www.w3.org/ns/prov#') self.graph.bind('prov', PROV) PROMS = Namespace('http://promsns.org/def/proms#') self.graph.bind('proms', PROMS) DCT = Namespace('http://purl.org/dc/terms/') self.graph.bind('dct', DCT) # ... the date this Pingback was sent to this PROMS Server self.graph.add(( URIRef(self.named_graph_uri), DCT.dateSubmitted, Literal(datetime.now().isoformat(), datatype=XSD.dateTime) )) # ... who contributed this Pingback self.graph.add(( URIRef(self.named_graph_uri), DCT.contributor, URIRef(self.request.remote_addr) )) # TODO: add other useful metadata variables gleaned from the HTTP message headers # PROV Pingbacks can only be of mimtype text/uri-list if self.request.mimetype == 'text/uri-list': self._generate_prov_pingback_named_graph_metadata() else: self._generate_proms_pingback_named_graph_metadata() def _generate_prov_pingback_named_graph_metadata(self): PROMS = Namespace('http://promsns.org/def/proms#') self.graph.bind('proms', PROMS) # type this pingback specifically self.graph.add(( URIRef(self.named_graph_uri), RDF.type, PROMS.ProvAqPingbackNamedGraph )) def _generate_proms_pingback_named_graph_metadata(self): PROMS = Namespace('http://promsns.org/def/proms#') self.graph.bind('proms', PROMS) # type this pingback specifically self.graph.add(( URIRef(self.named_graph_uri), RDF.type, PROMS.PromsPingbackNamedGraph ))
def instance(): """ Responds with one of a number of HTTP responses according to an allowed model and format of this object in the graph :return: and HTTP response """ # must have the URI of an object in the graph instance_uri = request.args.get('_uri') try: g = get_class_object(instance_uri) if not g: return client_error_response( 'No URI of an object in the provenance _database was supplied. ' 'Expecting a query string argument \'_uri\'.') except ConnectionError: return render_template('error_db_connection.html'), 500 # the URI is of something in the graph so now we validate the requested model and format # find the class of the URI q = ''' SELECT DISTINCT ?o WHERE { <%s> a ?o . } ''' % instance_uri for r in g.query(q): class_uri = str(str(r['o'])) # first preference render if class_uri in [ 'http://www.w3.org/ns/prov#Activity', 'http://www.w3.org/ns/prov#Agent', 'http://www.w3.org/ns/prov#Entity', 'http://promsns.org/def/proms#BasicReport', 'http://promsns.org/def/proms#ExternalReport', 'http://promsns.org/def/proms#InternalReport', 'http://promsns.org/def/proms#ReportingSystem' ]: # validate this request's model and format views_formats = objects_functions.get_classes_views_formats().get(class_uri) try: view, mime_format = LDAPI.get_valid_view_and_format( request.args.get('_view'), request.args.get('_format'), views_formats ) # if alternates model, return this info from file if view == 'alternates': instance_uri_encoded = uriparse.quote_plus(request.args.get('_uri')) class_uri_encoded = uriparse.quote_plus(class_uri) del views_formats['renderer'] return api_functions.render_alternates_view( class_uri, class_uri_encoded, instance_uri, instance_uri_encoded, views_formats, mime_format ) else: # chooses a class to render this instance based on the specified renderer in # classes_views_formats.json # no need for further validation as instance_uri, model & format are already validated renderer = getattr(__import__('model'), views_formats['renderer']) endpoints = { 'instance': url_for('.instance'), 'sparql': url_for('api.sparql') } return renderer( instance_uri, endpoints ).render(view, mime_format) except LdapiParameterError as e: return client_error_response(e)