def test_sparql_update(self): """ Update a resource using a SPARQL Update string. Use a mix of relative and absolute URIs. """ uid = '/test_sparql' rdf_data = b'<> <http://purl.org/dc/terms/title> "Original title." .' update_str = '''DELETE { <> <http://purl.org/dc/terms/title> "Original title." . } INSERT { <> <http://purl.org/dc/terms/title> "Title #2." . <info:fcres/test_sparql> <http://purl.org/dc/terms/title> "Title #3." . <#h1> <http://purl.org/dc/terms/title> "This is a hash." . } WHERE { }''' rsrc_api.create_or_replace(uid, rdf_data=rdf_data, rdf_fmt='turtle') ver_uid = rsrc_api.create_version(uid, 'v1').split('fcr:versions/')[-1] rsrc = rsrc_api.update(uid, update_str) with env.app_globals.rdf_store.txn_ctx(): assert ( (rsrc.uri, nsc['dcterms'].title, Literal('Original title.')) not in set(rsrc.imr)) assert ( (rsrc.uri, nsc['dcterms'].title, Literal('Title #2.')) in set(rsrc.imr)) assert ( (rsrc.uri, nsc['dcterms'].title, Literal('Title #3.')) in set(rsrc.imr)) assert (( URIRef(str(rsrc.uri) + '#h1'), nsc['dcterms'].title, Literal('This is a hash.')) in set(rsrc.imr))
def test_delta_update_wildcard(self): """ Update a resource using wildcard modifiers. """ uid = '/test_delta_patch_wc' uri = nsc['fcres'][uid] init_trp = { (URIRef(uri), nsc['rdf'].type, nsc['foaf'].Person), (URIRef(uri), nsc['foaf'].name, Literal('Joe Bob')), (URIRef(uri), nsc['foaf'].name, Literal('Joe Average Bob')), (URIRef(uri), nsc['foaf'].name, Literal('Joe 12oz Bob')), } remove_trp = { (URIRef(uri), nsc['foaf'].name, None), } add_trp = { (URIRef(uri), nsc['foaf'].name, Literal('Joan Knob')), } gr = Graph() gr += init_trp rsrc_api.create_or_replace(uid, graph=gr) rsrc_api.update_delta(uid, remove_trp, add_trp) rsrc = rsrc_api.get(uid) assert rsrc.imr[rsrc.uri:nsc['rdf'].type:nsc['foaf'].Person] assert rsrc.imr[rsrc.uri:nsc['foaf'].name:Literal('Joan Knob')] assert not rsrc.imr[rsrc.uri:nsc['foaf'].name:Literal('Joe Bob')] assert not rsrc.imr[rsrc.uri:nsc['foaf'].name:Literal('Joe Average Bob' )] assert not rsrc.imr[rsrc.uri:nsc['foaf'].name:Literal('Joe 12oz Bob')]
def test_create_version(self): """ Create a version snapshot. """ uid = '/test_version1' rdf_data = b'<> <http://purl.org/dc/terms/title> "Original title." .' update_str = '''DELETE { <> <http://purl.org/dc/terms/title> "Original title." . } INSERT { <> <http://purl.org/dc/terms/title> "Title #2." . } WHERE { }''' rsrc_api.create_or_replace(uid, rdf_data=rdf_data, rdf_fmt='turtle') ver_uid = rsrc_api.create_version(uid, 'v1').split('fcr:versions/')[-1] #FIXME Without this, the test fails. #set(rsrc_api.get_version(uid, ver_uid)) rsrc_api.update(uid, update_str) current = rsrc_api.get(uid) with env.app_globals.rdf_store.txn_ctx(): assert ( (current.uri, nsc['dcterms'].title, Literal('Title #2.')) in current.imr) assert ( (current.uri, nsc['dcterms'].title, Literal('Original title.')) not in current.imr) v1 = rsrc_api.get_version(uid, ver_uid) with env.app_globals.rdf_store.txn_ctx(): assert ( (v1.uri, nsc['dcterms'].title, Literal('Original title.')) in set(v1)) assert ( (v1.uri, nsc['dcterms'].title, Literal('Title #2.')) not in set(v1))
def test_delta_update(self): """ Update a resource with two sets of add and remove triples. """ uid = '/test_delta_patch' uri = nsc['fcres'][uid] init_trp = { (URIRef(uri), nsc['rdf'].type, nsc['foaf'].Person), (URIRef(uri), nsc['foaf'].name, Literal('Joe Bob')), } remove_trp = { (URIRef(uri), nsc['rdf'].type, nsc['foaf'].Person), } add_trp = { (URIRef(uri), nsc['rdf'].type, nsc['foaf'].Organization), } gr = Graph() gr += init_trp rsrc_api.create_or_replace(uid, graph=gr) rsrc_api.update_delta(uid, remove_trp, add_trp) rsrc = rsrc_api.get(uid) assert rsrc.imr[rsrc.uri:nsc['rdf'].type:nsc['foaf'].Organization] assert rsrc.imr[rsrc.uri:nsc['foaf'].name:Literal('Joe Bob')] assert not rsrc.imr[rsrc.uri:nsc['rdf'].type:nsc['foaf'].Person]
def test_replace_rsrc(self): uid = '/test_replace' uri = nsc['fcres'][uid] with env.app_globals.rdf_store.txn_ctx(): gr1 = from_rdf( data='<> a <http://ex.org/type#A> .', format='turtle', publicID=uri ) evt, _ = rsrc_api.create_or_replace(uid, graph=gr1) assert evt == RES_CREATED rsrc = rsrc_api.get(uid) with env.app_globals.rdf_store.txn_ctx(): assert rsrc.imr[ rsrc.uri : nsc['rdf'].type : URIRef('http://ex.org/type#A')] assert rsrc.imr[ rsrc.uri : nsc['rdf'].type : nsc['ldp'].RDFSource] with env.app_globals.rdf_store.txn_ctx(): gr2 = from_rdf( data='<> a <http://ex.org/type#B> .', format='turtle', publicID=uri ) #pdb.set_trace() evt, _ = rsrc_api.create_or_replace(uid, graph=gr2) assert evt == RES_UPDATED rsrc = rsrc_api.get(uid) with env.app_globals.rdf_store.txn_ctx(): assert not rsrc.imr[ rsrc.uri : nsc['rdf'].type : URIRef('http://ex.org/type#A')] assert rsrc.imr[ rsrc.uri : nsc['rdf'].type : URIRef('http://ex.org/type#B')] assert rsrc.imr[ rsrc.uri : nsc['rdf'].type : nsc['ldp'].RDFSource]
def test_replace_incompatible_type(self): """ Verify replacing resources with incompatible type. Replacing a LDP-NR with a LDP-RS, or vice versa, should fail. """ uid_rs = '/test_incomp_rs' uid_nr = '/test_incomp_nr' data = b'mock binary content' gr = Graph().parse(data='<> a <http://ex.org/type#A> .', format='turtle', publicID=nsc['fcres'][uid_rs]) rsrc_api.create_or_replace(uid_rs, graph=gr) rsrc_api.create_or_replace(uid_nr, stream=BytesIO(data), mimetype='text/plain') with pytest.raises(IncompatibleLdpTypeError): rsrc_api.create_or_replace(uid_nr, graph=gr) with pytest.raises(IncompatibleLdpTypeError): rsrc_api.create_or_replace(uid_rs, stream=BytesIO(data), mimetype='text/plain') with pytest.raises(IncompatibleLdpTypeError): rsrc_api.create_or_replace(uid_nr)
def test_create_child_uri_graph(self): """ Create a resource with empty string ("self") URIs in the RDF body. """ uid = '/reluri06' uri = nsc['fcres'][uid] gr = Graph() with env.app_globals.rdf_store.txn_ctx(): gr.add({ (URIRef('child1'), nsc['rdf']['type'], URIRef('urn:type:A')), ( URIRef('http://ex.org/external'), URIRef('urn:pred:x'), URIRef('child22') ) }) rsrc_api.create_or_replace(uid, graph=gr) rsrc = rsrc_api.get(uid) with env.app_globals.rdf_store.txn_ctx(): assert rsrc.imr[ URIRef(str(uri) + '/child1'): nsc['rdf'].type: URIRef('urn:type:A')] assert rsrc.imr[ URIRef('http://ex.org/external'): URIRef('urn:pred:x'): URIRef(str(uri) + '/child2')]
def test_replace_rsrc(self): uid = '/test_replace' uri = nsc['fcres'][uid] gr1 = Graph().parse(data='<> a <http://ex.org/type#A> .', format='turtle', publicID=uri) evt = rsrc_api.create_or_replace(uid, graph=gr1) assert evt == RES_CREATED rsrc = rsrc_api.get(uid) assert rsrc.imr[rsrc.uri:nsc['rdf'].type:URIRef('http://ex.org/type#A' )] assert rsrc.imr[rsrc.uri:nsc['rdf'].type:nsc['ldp'].RDFSource] gr2 = Graph().parse(data='<> a <http://ex.org/type#B> .', format='turtle', publicID=uri) #pdb.set_trace() evt = rsrc_api.create_or_replace(uid, graph=gr2) assert evt == RES_UPDATED rsrc = rsrc_api.get(uid) assert not rsrc.imr[rsrc.uri:nsc['rdf']. type:URIRef('http://ex.org/type#A')] assert rsrc.imr[rsrc.uri:nsc['rdf'].type:URIRef('http://ex.org/type#B' )] assert rsrc.imr[rsrc.uri:nsc['rdf'].type:nsc['ldp'].RDFSource]
def test_fixity_check_ok(self): """ Verify that fixity check passes for a non-corrupted resource. """ content = BytesIO(uuid4().bytes) uid = f'/{uuid4()}' rsrc_api.create_or_replace(uid, stream=content) admin_api.fixity_check(uid)
def test_soft_delete(self): """ Soft-delete (bury) a resource. """ uid = '/test_soft_delete01' rsrc_api.create_or_replace(uid) rsrc_api.delete(uid) with pytest.raises(TombstoneError): rsrc_api.get(uid)
def test_hard_delete(self): """ Hard-delete (forget) a resource. """ uid = '/test_hard_delete01' rsrc_api.create_or_replace(uid) rsrc_api.delete(uid, False) with pytest.raises(ResourceNotExistsError): rsrc_api.get(uid) with pytest.raises(ResourceNotExistsError): rsrc_api.resurrect(uid)
def test_resurrect(self): """ Restore (resurrect) a soft-deleted resource. """ uid = '/test_soft_delete02' rsrc_api.create_or_replace(uid) rsrc_api.delete(uid) rsrc_api.resurrect(uid) rsrc = rsrc_api.get(uid) with env.app_globals.rdf_store.txn_ctx(): assert nsc['ldp'].Resource in rsrc.ldp_types
def test_create_ldp_nr(self): """ Create a non-RDF resource (LDP-NR). """ uid = '/{}'.format(uuid4()) data = b'Hello. This is some dummy content.' rsrc_api.create_or_replace(uid, stream=BytesIO(data), mimetype='text/plain') rsrc = rsrc_api.get(uid) assert rsrc.content.read() == data
def test_create_ldp_dc_post(self, dc_rdf): """ Create an LDP Direct Container via POST. """ rsrc_api.create_or_replace('/member') dc_rsrc = rsrc_api.create( '/', 'test_dc_post', rdf_data=dc_rdf, rdf_fmt='turtle') member_rsrc = rsrc_api.get('/member') with env.app_globals.rdf_store.txn_ctx(): assert nsc['ldp'].Container in dc_rsrc.ldp_types assert nsc['ldp'].DirectContainer in dc_rsrc.ldp_types
def test_indirect_container(self, ic_rdf): """ Create an indirect container and verify special properties. """ cont_uid = '/top_container' ic_uid = '{}/test_ic'.format(cont_uid) member_uid = '{}/ic_member'.format(ic_uid) target_uid = '/ic_target' ic_member_rdf = b''' PREFIX ore: <http://www.openarchives.org/ore/terms/> <> ore:proxyFor <info:fcres/ic_target> .''' rsrc_api.create_or_replace(cont_uid) rsrc_api.create_or_replace(target_uid) rsrc_api.create_or_replace(ic_uid, rdf_data=ic_rdf, rdf_fmt='turtle') rsrc_api.create_or_replace( member_uid, rdf_data=ic_member_rdf, rdf_fmt='turtle') ic_rsrc = rsrc_api.get(ic_uid) with env.app_globals.rdf_store.txn_ctx(): assert nsc['ldp'].Container in ic_rsrc.ldp_types assert nsc['ldp'].IndirectContainer in ic_rsrc.ldp_types assert nsc['ldp'].DirectContainer not in ic_rsrc.ldp_types member_rsrc = rsrc_api.get(member_uid) top_cont_rsrc = rsrc_api.get(cont_uid) with env.app_globals.rdf_store.txn_ctx(): assert top_cont_rsrc.imr[ top_cont_rsrc.uri: nsc['dcterms'].relation: nsc['fcres'][target_uid]]
def test_check_refint_ok(self): """ Check that referential integrity is OK. """ uid1 = '/test_refint1' uid2 = '/test_refint2' with env.app_globals.rdf_store.txn_ctx(): gr = from_rdf( store=env.app_globals.rdf_store, data=f'<> <http://ex.org/ns#p1> <info:fcres{uid1}> .', format='turtle', publicID=nsc['fcres'][uid2] ) rsrc_api.create_or_replace(uid1, graph=gr) assert admin_api.integrity_check() == set()
def test_create_self_uri_rdf(self): """ Create a resource with empty string ("self") URIs in the RDF body. """ uid = '/reluri01' uri = nsc['fcres'][uid] data = ''' <> a <urn:type:A> . <http://ex.org/external> <urn:pred:x> <> . ''' rsrc_api.create_or_replace(uid, rdf_data=data, rdf_fmt='ttl') rsrc = rsrc_api.get(uid) with env.app_globals.rdf_store.txn_ctx(): assert rsrc.imr[uri: nsc['rdf']['type']: URIRef('urn:type:A')] assert rsrc.imr[ URIRef('http://ex.org/external'): URIRef('urn:pred:x'): uri]
def test_delete_children(self): """ Soft-delete a resource with children. """ uid = '/test_soft_delete_children01' rsrc_api.create_or_replace(uid) for i in range(3): rsrc_api.create_or_replace('{}/child{}'.format(uid, i)) rsrc_api.delete(uid) with pytest.raises(TombstoneError): rsrc_api.get(uid) for i in range(3): with pytest.raises(TombstoneError): rsrc_api.get('{}/child{}'.format(uid, i)) # Cannot resurrect children of a tombstone. with pytest.raises(TombstoneError): rsrc_api.resurrect('{}/child{}'.format(uid, i))
def migrate(self, start_pts=None, list_file=None): """ Migrate the database. This method creates a fully functional and configured Lakesuperior data set contained in a folder from an LDP repository. :param start_pts: List of starting points to retrieve resources from. It would typically be the repository root in case of a full dump or one or more resources in the repository for a partial one. :type start_pts: tuple or list :param str list_file: path to a local file containing a list of URIs, one per line. """ from lakesuperior.api import resource as rsrc_api self._ct = 0 with StoreWrapper(self.rdfly.store): if start_pts: for start in start_pts: if not start.startswith('/'): raise ValueError( 'Starting point {} does not begin with a slash.'. format(start)) if not rsrc_api.exists(start): # Create the full hierarchy with link to the parents. rsrc_api.create_or_replace(start) # Then populate the new resource and crawl for more # relationships. self._crawl(start) elif list_file: with open(list_file, 'r') as fp: for uri in fp: uid = uri.strip().replace(self.src, '') if not rsrc_api.exists(uid): try: rsrc_api.create_or_replace(uid) except InvalidResourceError: pass self._crawl(uid) logger.info('Dumped {} resources.'.format(self._ct)) return self._ct
def test_hard_delete_children(self): """ Hard-delete (forget) a resource with its children. This uses fixtures from the previous test. """ uid = '/test_hard_delete_children01' rsrc_api.create_or_replace(uid) for i in range(3): rsrc_api.create_or_replace('{}/child{}'.format(uid, i)) rsrc_api.delete(uid, False) with pytest.raises(ResourceNotExistsError): rsrc_api.get(uid) with pytest.raises(ResourceNotExistsError): rsrc_api.resurrect(uid) for i in range(3): with pytest.raises(ResourceNotExistsError): rsrc_api.get('{}/child{}'.format(uid, i)) with pytest.raises(ResourceNotExistsError): rsrc_api.resurrect('{}/child{}'.format(uid, i))
def test_hard_delete_descendants(self): """ Forget a resource with all its descendants. """ uid = '/test_hard_delete_descendants01' rsrc_api.create_or_replace(uid) for i in range(1, 4): rsrc_api.create_or_replace('{}/child{}'.format(uid, i)) for j in range(i): rsrc_api.create_or_replace('{}/child{}/grandchild{}'.format( uid, i, j)) rsrc_api.delete(uid, False) with pytest.raises(ResourceNotExistsError): rsrc_api.get(uid) with pytest.raises(ResourceNotExistsError): rsrc_api.resurrect(uid) for i in range(1, 4): with pytest.raises(ResourceNotExistsError): rsrc_api.get('{}/child{}'.format(uid, i)) with pytest.raises(ResourceNotExistsError): rsrc_api.resurrect('{}/child{}'.format(uid, i)) for j in range(i): with pytest.raises(ResourceNotExistsError): rsrc_api.get('{}/child{}/grandchild{}'.format( uid, i, j)) with pytest.raises(ResourceNotExistsError): rsrc_api.resurrect('{}/child{}/grandchild{}'.format( uid, i, j))
def put_resource(uid): """ https://www.w3.org/TR/ldp/#ldpr-HTTP_PUT Add or replace a new resource at a specified URI. """ # Parse headers. logger.debug('Request headers: {}'.format(request.headers)) rsp_headers = {'Content-Type': 'text/plain; charset=utf-8'} handling, disposition = set_post_put_params() stream, mimetype = _bistream_from_req() if LdpFactory.is_rdf_parsable(mimetype): # If the content is RDF, localize in-repo URIs. global_rdf = stream.read() local_rdf = g.tbox.localize_payload(global_rdf) graph = Graph().parse(data=local_rdf, format=mimetype, publicID=nsc['fcres'][uid]) stream = mimetype = None else: graph = None try: evt = rsrc_api.create_or_replace(uid, stream=stream, mimetype=mimetype, graph=graph, handling=handling, disposition=disposition) except (InvalidResourceError, ResourceExistsError) as e: return str(e), 409 except (ServerManagedTermError, SingleSubjectError) as e: return str(e), 412 except IncompatibleLdpTypeError as e: return str(e), 415 except TombstoneError as e: return _tombstone_response(e, uid) uri = g.tbox.uid_to_uri(uid) if evt == RES_CREATED: rsp_code = 201 rsp_headers['Location'] = rsp_body = uri if mimetype and not graph: rsp_headers['Link'] = ( '<{0}/fcr:metadata>; rel="describedby"'.format(uri)) else: rsp_code = 204 rsp_body = '' return rsp_body, rsp_code, rsp_headers
def test_create_self_uri_graph(self): """ Create a resource with empty string ("self") URIs in a RDFlib graph. """ uid = '/reluri02' uri = nsc['fcres'][uid] gr = Graph() with env.app_globals.rdf_store.txn_ctx(): gr.add({ (URIRef(''), nsc['rdf']['type'], URIRef('urn:type:A')), ( URIRef('http://ex.org/external'), URIRef('urn:pred:x'), URIRef('') ), }) rsrc_api.create_or_replace(uid, graph=gr) rsrc = rsrc_api.get(uid) with env.app_globals.rdf_store.txn_ctx(): assert rsrc.imr[uri: nsc['rdf']['type']: URIRef('urn:type:A')] assert rsrc.imr[ URIRef('http://ex.org/external'): URIRef('urn:pred:x'): uri]
def test_add_dc_member(self, dc_rdf): """ Add members to a direct container and verify special properties. """ dc_uid = '/test_dc_put02' _, dc_rsrc = rsrc_api.create_or_replace( dc_uid, rdf_data=dc_rdf, rdf_fmt='turtle') child_uid = rsrc_api.create(dc_uid).uid member_rsrc = rsrc_api.get('/member') with env.app_globals.rdf_store.txn_ctx(): assert member_rsrc.imr[ member_rsrc.uri: nsc['dcterms'].relation: nsc['fcres'][child_uid]]
def test_fixity_check_corrupt(self): """ Verify that fixity check fails for a corrupted resource. """ content = BytesIO(uuid4().bytes) uid = f'/{uuid4()}' _, rsrc = rsrc_api.create_or_replace(uid, stream=content) with env.app_globals.rdf_store.txn_ctx(): with open(rsrc.local_path, 'wb') as fh: fh.write(uuid4().bytes) with pytest.raises(ChecksumValidationError): admin_api.fixity_check(uid)
def test_create_ldp_rs(self): """ Create an RDF resource (LDP-RS) from a provided graph. """ uid = '/rsrc_from_graph' uri = nsc['fcres'][uid] gr = Graph().parse(data='<> a <http://ex.org/type#A> .', format='turtle', publicID=uri) #pdb.set_trace() evt = rsrc_api.create_or_replace(uid, graph=gr) rsrc = rsrc_api.get(uid) assert rsrc.imr[rsrc.uri:nsc['rdf'].type:URIRef('http://ex.org/type#A' )] assert rsrc.imr[rsrc.uri:nsc['rdf'].type:nsc['ldp'].RDFSource]
def _ingest_py(method, dest, data, ref): from lakesuperior.api import resource as rsrc_api kwargs = {} if isinstance(data, rdflib.Graph): kwargs['graph'] = data else: kwargs['stream'] = data kwargs['mimetype'] = 'image/png' if method == 'put': _, rsrc = rsrc_api.create_or_replace(dest, **kwargs) else: rsrc = rsrc_api.create(dest, **kwargs) return rsrc.uid
def put_resource(uid): """ https://www.w3.org/TR/ldp/#ldpr-HTTP_PUT Add or replace a new resource at a specified URI. """ # Parse headers. logger.debug('Request headers: {}'.format(request.headers)) cond_ret = _process_cond_headers(uid, request.headers, False) if cond_ret: return cond_ret try: kwargs = _create_args_from_req(uid) evt, rsrc = rsrc_api.create_or_replace(uid, **kwargs) except exc.RdfParsingError as e: return str(e), 400 except exc.IndigestibleError: return (f'Unable to parse digest header: {request.headers["digest"]}', 400) except (exc.InvalidResourceError, exc.ChecksumValidationError, exc.ResourceExistsError) as e: return str(e), 409 except (exc.ServerManagedTermError, exc.SingleSubjectError) as e: return str(e), 412 except exc.IncompatibleLdpTypeError as e: return str(e), 415 except exc.TombstoneError as e: return _tombstone_response(e, uid) with store.txn_ctx(): rsp_headers = _headers_from_metadata(rsrc) rsp_headers['Content-Type'] = 'text/plain; charset=utf-8' uri = g.tbox.uid_to_uri(uid) if evt == RES_CREATED: rsp_code = 201 rsp_headers['Location'] = rsp_body = uri if kwargs.get('mimetype') and not kwargs.get('rdf_data'): rsp_headers['Link'] = f'<{uri}/fcr:metadata>; rel="describedby"' else: rsp_code = 204 rsp_body = '' return rsp_body, rsp_code, rsp_headers
def test_create_ldp_rs(self): """ Create an RDF resource (LDP-RS) from a provided graph. """ uid = '/rsrc_from_graph' uri = nsc['fcres'][uid] with env.app_globals.rdf_store.txn_ctx(): gr = from_rdf( data='<> a <http://ex.org/type#A> .', format='turtle', publicID=uri) evt, _ = rsrc_api.create_or_replace(uid, graph=gr) rsrc = rsrc_api.get(uid) with env.app_globals.rdf_store.txn_ctx(): assert rsrc.imr[ rsrc.uri : nsc['rdf'].type : URIRef('http://ex.org/type#A')] assert rsrc.imr[ rsrc.uri : nsc['rdf'].type : nsc['ldp'].RDFSource]
def test_create_ldp_dc_defaults3(self): """ Create an LDP Direct Container with default values. """ dc_rdf = b''' PREFIX dcterms: <http://purl.org/dc/terms/> PREFIX ldp: <http://www.w3.org/ns/ldp#> <> a ldp:DirectContainer . ''' dc_uid = '/test_dc_defaults3' _, dc_rsrc = rsrc_api.create_or_replace( dc_uid, rdf_data=dc_rdf, rdf_fmt='turtle') child_uid = rsrc_api.create(dc_uid, None).uid member_rsrc = rsrc_api.get(dc_uid) with env.app_globals.rdf_store.txn_ctx(): assert member_rsrc.imr[ member_rsrc.uri: nsc['ldp'].member: nsc['fcres'][child_uid]]