# standard imports from functools import partial import operator import unittest # external imports import rdflib # bsie imports from bsfs import schema as bsc from bsfs.graph.ac import NullAC from bsfs.graph.walk import Walk from bsfs.namespace import Namespace, ns from bsfs.triple_store.sparql import SparqlStore from bsfs.utils import errors, URI # objects to test from bsfs.graph.nodes import Nodes ## code ## ns.bse = ns.bsfs.Entity() ns.bst = ns.bsfs.Tag() class TestNodes(unittest.TestCase): def setUp(self): # initialize backend self.backend = SparqlStore() self.backend.schema = bsc.from_string(''' prefix rdfs: prefix xsd: prefix bsfs: prefix bsl: prefix bsn: prefix bse: prefix bst: bsfs:Entity rdfs:subClassOf bsfs:Node . bsfs:Tag rdfs:subClassOf bsfs:Node . bsfs:User rdfs:subClassOf bsfs:Node . xsd:string rdfs:subClassOf bsfs:Literal . bsl:Number rdfs:subClassOf bsfs:Literal . xsd:integer rdfs:subClassOf bsl:Number . # predicates mandated by Nodes bsn:t_created rdfs:subClassOf bsfs:Predicate ; rdfs:domain bsfs:Node ; rdfs:range xsd:integer ; bsfs:unique "true"^^xsd:boolean . # additionally defined predicates bse:comment rdfs:subClassOf bsfs:Predicate ; rdfs:domain bsfs:Entity ; rdfs:range xsd:string ; bsfs:unique "false"^^xsd:boolean . bse:filesize rdfs:subClassOf bsfs:Predicate ; rdfs:domain bsfs:Entity ; rdfs:range xsd:integer ; bsfs:unique "true"^^xsd:boolean . bse:tag rdfs:subClassOf bsfs:Predicate ; rdfs:domain bsfs:Entity ; rdfs:range bsfs:Tag ; bsfs:unique "false"^^xsd:boolean . bse:author rdfs:subClassOf bsfs:Predicate ; rdfs:domain bsfs:Entity ; rdfs:range bsfs:User ; bsfs:unique "true"^^xsd:boolean . bst:label rdfs:subClassOf bsfs:Predicate ; rdfs:domain bsfs:Tag ; rdfs:range xsd:string ; bsfs:unique "true"^^xsd:boolean . bst:representative rdfs:subClassOf bsfs:Predicate ; rdfs:domain bsfs:Tag ; rdfs:range bsfs:Entity ; bsfs:unique "true"^^xsd:boolean . ''') self.schema_triples = { # schema hierarchy (rdflib.URIRef(ns.bsfs.Entity), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Node)), (rdflib.URIRef(ns.bsfs.Tag), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Node)), (rdflib.URIRef(ns.bsfs.User), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Node)), (rdflib.URIRef(ns.xsd.string), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Literal)), (rdflib.URIRef(ns.bsl.Array), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Literal)), (rdflib.URIRef(ns.bsl.BinaryBlob), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Literal)), (rdflib.URIRef(ns.bsl.Array.Feature), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsl.Array)), (rdflib.URIRef(ns.bsl.Number), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Literal)), (rdflib.URIRef(ns.bsl.Time), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Literal)), (rdflib.URIRef(ns.xsd.integer), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsl.Number)), (rdflib.URIRef(ns.bsn.t_created), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)), (rdflib.URIRef(ns.bse.comment), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)), (rdflib.URIRef(ns.bse.filesize), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)), (rdflib.URIRef(ns.bse.tag), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)), (rdflib.URIRef(ns.bse.author), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)), (rdflib.URIRef(ns.bst.representative), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)), (rdflib.URIRef(ns.bst.label), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)), } # Nodes constructor args self.user = URI('http://example.com/me') self.ac = NullAC(self.backend, self.user) # set args self.tag_type = self.backend.schema.node(ns.bsfs.Tag) self.ent_type = self.backend.schema.node(ns.bsfs.Entity) self.user_type = self.backend.schema.node(ns.bsfs.User) self.p_filesize = self.backend.schema.predicate(ns.bse.filesize) self.p_author = self.backend.schema.predicate(ns.bse.author) self.p_tag = self.backend.schema.predicate(ns.bse.tag) self.p_representative = self.backend.schema.predicate(ns.bst.representative) self.p_label = self.backend.schema.predicate(ns.bst.label) self.t_created = self.backend.schema.predicate(ns.bsn.t_created) self.ent_ids = { URI('http://example.com/me/entity#1234'), URI('http://example.com/me/entity#4321'), } self.tag_ids = { URI('http://example.com/me/tag#1234'), URI('http://example.com/me/tag#4321'), } def test_construct(self): self.assertIsInstance(Nodes(self.backend, self.ac, self.ent_type, {'http://example.com/me-and-you'}), Nodes) self.assertRaises(ValueError, Nodes, self.backend, self.ac, self.ent_type, {'http://example.com/me and you'}) def test_str(self): # str baseline nodes = Nodes(self.backend, self.ac, self.ent_type, {URI('http://example.com/me/entity#1234')}) self.assertEqual(str(nodes), f"Nodes({self.ent_type}, {{'http://example.com/me/entity#1234'}})") self.assertEqual(repr(nodes), f"Nodes({self.backend}, {self.ac}, {self.ent_type}, {{'http://example.com/me/entity#1234'}})") # str respects node_type nodes = Nodes(self.backend, self.ac, self.tag_type, {URI('http://example.com/me/tag#1234')}) self.assertEqual(str(nodes), f"Nodes({self.tag_type}, {{'http://example.com/me/tag#1234'}})") self.assertEqual(repr(nodes), f"Nodes({self.backend}, {self.ac}, {self.tag_type}, {{'http://example.com/me/tag#1234'}})") # str respects guids nodes = Nodes(self.backend, self.ac, self.ent_type, {URI('http://example.com/me/entity#foo')}) self.assertEqual(str(nodes), f'Nodes({self.ent_type}, {{\'http://example.com/me/entity#foo\'}})') self.assertEqual(repr(nodes), f'Nodes({self.backend}, {self.ac}, {self.ent_type}, {{\'http://example.com/me/entity#foo\'}})') # repr respects backend class Foo(SparqlStore): pass backend = Foo.Open() backend.schema = self.backend.schema nodes = Nodes(backend, self.ac, self.ent_type, {URI('http://example.com/me/entity#1234')}) self.assertEqual(repr(nodes), f"Nodes({backend}, {self.ac}, {self.ent_type}, {{'http://example.com/me/entity#1234'}})") # repr respects user nodes = Nodes(self.backend, NullAC(self.backend, URI('http://example.com/you')), self.ent_type, {URI('http://example.com/me/entity#1234')}) self.assertEqual(repr(nodes), f"Nodes({self.backend}, NullAC(http://example.com/you), {self.ent_type}, {{'http://example.com/me/entity#1234'}})") def test_equality(self): nodes = Nodes(self.backend, self.ac, self.ent_type, self.ent_ids) # instance is equal to itself self.assertEqual(nodes, nodes) self.assertEqual(hash(nodes), hash(nodes)) # instance is equal to a clone self.assertEqual(nodes, Nodes(self.backend, self.ac, self.ent_type, self.ent_ids)) self.assertEqual(Nodes(self.backend, self.ac, self.ent_type, self.ent_ids), nodes) self.assertEqual(hash(nodes), hash(Nodes(self.backend, self.ac, self.ent_type, self.ent_ids))) # equality respects backend backend = SparqlStore.Open() backend.schema = self.backend.schema self.assertNotEqual(nodes, Nodes(backend, self.ac, self.ent_type, self.ent_ids)) self.assertNotEqual(hash(nodes), hash(Nodes(backend, self.ac, self.ent_type, self.ent_ids))) # equality respects user self.assertNotEqual(nodes, Nodes(self.backend, NullAC(self.backend, URI('http://example.com/you')), self.ent_type, self.ent_ids)) self.assertNotEqual(hash(nodes), hash(Nodes(self.backend, NullAC(self.backend, URI('http://example.com/you')), self.ent_type, self.ent_ids))) # equality respects node_type self.assertNotEqual(nodes, Nodes(self.backend, self.ac, self.tag_type, self.ent_ids)) self.assertNotEqual(hash(nodes), hash(Nodes(self.backend, self.ac, self.tag_type, self.ent_ids))) # equality respects guids self.assertNotEqual(nodes, Nodes(self.backend, self.ac, self.ent_type, self.tag_ids)) self.assertNotEqual(hash(nodes), hash(Nodes(self.backend, self.ac, self.ent_type, self.tag_ids))) def test_properties(self): # node_type self.assertEqual(self.ent_type, Nodes( self.backend, self.ac, self.ent_type, self.ent_ids).node_type) self.assertEqual(self.tag_type, Nodes( self.backend, self.ac, self.tag_type, self.tag_ids).node_type) # guids self.assertSetEqual(self.ent_ids, set(Nodes( self.backend, self.ac, self.ent_type, self.ent_ids).guids)) self.assertSetEqual(self.tag_ids, set(Nodes( self.backend, self.ac, self.tag_type, self.tag_ids).guids)) def test__ensure_nodes(self): nodes = Nodes(self.backend, self.ac, self.ent_type, self.ent_ids) # missing nodes are created self.assertSetEqual(self.ent_ids, nodes._ensure_nodes(self.ent_type, self.ent_ids)) # get creation time from backend manually time_triples = list(self.backend._graph.objects(rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(self.t_created.uri))) t_ent_created = float(time_triples[0]) if len(time_triples) > 0 else 0.0 # check triples self.assertSetEqual(set(self.backend._graph), self.schema_triples | { # entity definitions (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Entity')), (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Entity')), # bookkeeping (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(self.t_created.uri), rdflib.Literal(t_ent_created, datatype=rdflib.XSD.integer)), (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(self.t_created.uri), rdflib.Literal(t_ent_created, datatype=rdflib.XSD.integer)), }) # existing nodes remain unchanged self.assertSetEqual(self.ent_ids, nodes._ensure_nodes(self.ent_type, self.ent_ids)) self.assertSetEqual(set(self.backend._graph), self.schema_triples | { # entity definitions (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Entity')), (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Entity')), # bookkeeping (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(self.t_created.uri), rdflib.Literal(t_ent_created, datatype=rdflib.XSD.integer)), (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(self.t_created.uri), rdflib.Literal(t_ent_created, datatype=rdflib.XSD.integer)), }) # type and guids don't need to match the node instance's members self.assertSetEqual(self.tag_ids, nodes._ensure_nodes(self.tag_type, self.tag_ids)) # get creation time from backend manually time_triples = list(self.backend._graph.objects(rdflib.URIRef('http://example.com/me/tag#1234'), rdflib.URIRef(self.t_created.uri))) t_tag_created = float(time_triples[0]) if len(time_triples) > 0 else 0.0 # check triples self.assertSetEqual(set(self.backend._graph), self.schema_triples | { # previous triples (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Entity')), (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Entity')), (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(self.t_created.uri), rdflib.Literal(t_ent_created, datatype=rdflib.XSD.integer)), (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(self.t_created.uri), rdflib.Literal(t_ent_created, datatype=rdflib.XSD.integer)), # new triples (rdflib.URIRef('http://example.com/me/tag#1234'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Tag')), (rdflib.URIRef('http://example.com/me/tag#4321'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Tag')), (rdflib.URIRef('http://example.com/me/tag#1234'), rdflib.URIRef(self.t_created.uri), rdflib.Literal(t_tag_created, datatype=rdflib.XSD.integer)), (rdflib.URIRef('http://example.com/me/tag#4321'), rdflib.URIRef(self.t_created.uri), rdflib.Literal(t_tag_created, datatype=rdflib.XSD.integer)), }) def test___set(self): # setup nodes = Nodes(self.backend, self.ac, self.ent_type, {URI('http://example.com/me/entity#1234'), URI('http://example.com/me/entity#4321')}) self.assertSetEqual(set(self.backend._graph), self.schema_triples | set()) set_ = nodes._Nodes__set # node_type must match predicate's domain self.assertRaises(errors.ConsistencyError, set_, self.p_representative.uri, self.ent_ids) # cannot set protected predicates self.assertRaises(errors.PermissionDeniedError, set_, self.t_created.uri, 1234) # set literal value set_(self.p_filesize.uri, 1234) # get creation time from backend manually time_triples = list(self.backend._graph.objects(rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(self.t_created.uri))) t_ent_created = float(time_triples[0]) if len(time_triples) > 0 else 0.0 # verify triples self.assertSetEqual(set(self.backend._graph), self.schema_triples | { # entity definitions (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Entity')), (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Entity')), # bookkeeping (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(self.t_created.uri), rdflib.Literal(t_ent_created, datatype=rdflib.XSD.integer)), (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(self.t_created.uri), rdflib.Literal(t_ent_created, datatype=rdflib.XSD.integer)), # literals (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(self.p_filesize.uri), rdflib.Literal(1234, datatype=rdflib.XSD.integer)), (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(self.p_filesize.uri), rdflib.Literal(1234, datatype=rdflib.XSD.integer)), }) # set node value tags = Nodes(self.backend, self.ac, self.tag_type, {URI('http://example.com/me/tag#1234'), URI('http://example.com/me/tag#4321')}) set_(self.p_tag.uri, tags) # get creation time from backend manually time_triples = list(self.backend._graph.objects(rdflib.URIRef('http://example.com/me/tag#1234'), rdflib.URIRef(self.t_created.uri))) t_tag_created = float(time_triples[0]) if len(time_triples) > 0 else 0.0 # verify triples self.assertSetEqual(set(self.backend._graph), self.schema_triples | { # previous values (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Entity')), (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Entity')), (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(self.p_filesize.uri), rdflib.Literal(1234, datatype=rdflib.XSD.integer)), (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(self.p_filesize.uri), rdflib.Literal(1234, datatype=rdflib.XSD.integer)), (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(self.t_created.uri), rdflib.Literal(t_ent_created, datatype=rdflib.XSD.integer)), (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(self.t_created.uri), rdflib.Literal(t_ent_created, datatype=rdflib.XSD.integer)), # tag definitions (rdflib.URIRef('http://example.com/me/tag#1234'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Tag')), (rdflib.URIRef('http://example.com/me/tag#4321'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Tag')), # tag bookkeeping (rdflib.URIRef('http://example.com/me/tag#1234'), rdflib.URIRef(self.t_created.uri), rdflib.Literal(t_tag_created, datatype=rdflib.XSD.integer)), (rdflib.URIRef('http://example.com/me/tag#4321'), rdflib.URIRef(self.t_created.uri), rdflib.Literal(t_tag_created, datatype=rdflib.XSD.integer)), # entity -> tag links (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(self.p_tag.uri), rdflib.URIRef('http://example.com/me/tag#1234')), (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(self.p_tag.uri), rdflib.URIRef('http://example.com/me/tag#1234')), (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(self.p_tag.uri), rdflib.URIRef('http://example.com/me/tag#4321')), (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(self.p_tag.uri), rdflib.URIRef('http://example.com/me/tag#4321')), }) # value must be a nodes instance self.assertRaises(TypeError, set_, self.p_tag.uri, 'foobar') self.assertRaises(TypeError, set_, self.p_tag.uri, self.tag_ids) self.assertRaises(TypeError, set_, self.p_tag.uri, URI('http://example.com/me/tag#1234')) # value's node_type must match the predicate's range self.assertRaises(errors.ConsistencyError, set_, self.p_tag.uri, Nodes(self.backend, self.ac, self.ent_type, self.ent_ids)) def test_set(self): self.assertSetEqual(set(self.backend._graph), self.schema_triples | set()) nodes = Nodes(self.backend, self.ac, self.ent_type, self.ent_ids) # can set literal values self.assertEqual(nodes, nodes.set(self.p_filesize.uri, 1234)) self.assertTrue(set(self.backend._graph).issuperset({ # nodes exist (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Entity')), (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Entity')), # links exist (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(self.p_filesize.uri), rdflib.Literal(1234, datatype=rdflib.XSD.integer)), (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(self.p_filesize.uri), rdflib.Literal(1234, datatype=rdflib.XSD.integer)), })) # can set node values self.assertEqual(nodes, nodes.set(self.p_tag.uri, Nodes(self.backend, self.ac, self.tag_type, self.tag_ids))) self.assertTrue(set(self.backend._graph).issuperset({ # nodes exist (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Entity')), (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Entity')), (rdflib.URIRef('http://example.com/me/tag#1234'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Tag')), (rdflib.URIRef('http://example.com/me/tag#4321'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Tag')), # links exist (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(self.p_tag.uri), rdflib.URIRef('http://example.com/me/tag#1234')), (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(self.p_tag.uri), rdflib.URIRef('http://example.com/me/tag#4321')), (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(self.p_tag.uri), rdflib.URIRef('http://example.com/me/tag#1234')), (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(self.p_tag.uri), rdflib.URIRef('http://example.com/me/tag#4321')), })) # cannot set protected predicate curr = set(self.backend._graph) self.assertRaises(errors.PermissionDeniedError, nodes.set, self.t_created.uri, 12345) self.assertSetEqual(curr, set(self.backend._graph)) # predicate.domain must match node_type self.assertRaises(errors.ConsistencyError, nodes.set, self.p_representative.uri, nodes) self.assertSetEqual(curr, set(self.backend._graph)) # value's node_type must match predicate's range self.assertRaises(errors.ConsistencyError, nodes.set, self.p_tag.uri, nodes) self.assertSetEqual(curr, set(self.backend._graph)) # value type must match predicate's range type self.assertRaises(TypeError, nodes.set, self.p_tag.uri, 'invalid') self.assertSetEqual(curr, set(self.backend._graph)) # cannot assing multiple values to unique predicate self.assertRaises(ValueError, nodes.set, self.p_author.uri, Nodes(self.backend, self.ac, self.user_type, {URI('http://example.com/me/user#1234'), URI('http://example.com/me/user#4321')})) self.assertSetEqual(curr, set(self.backend._graph)) # can set on empty nodes nodes = Nodes(self.backend, self.ac, self.ent_type, {}) self.assertEqual(nodes, nodes.set(self.p_filesize.uri, 1234)) def test_set_from_iterable(self): self.assertSetEqual(set(self.backend._graph), self.schema_triples | set()) nodes = Nodes(self.backend, self.ac, self.ent_type, self.ent_ids) # can set literal and node values simultaneously self.assertEqual(nodes, nodes.set_from_iterable({ self.p_filesize.uri: 1234, self.p_tag.uri: Nodes(self.backend, self.ac, self.tag_type, self.tag_ids), }.items())) self.assertTrue(set(self.backend._graph).issuperset({ # nodes exist (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Entity')), (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Entity')), (rdflib.URIRef('http://example.com/me/tag#1234'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Tag')), (rdflib.URIRef('http://example.com/me/tag#4321'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Tag')), # links exist (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(self.p_filesize.uri), rdflib.Literal(1234, datatype=rdflib.XSD.integer)), (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(self.p_filesize.uri), rdflib.Literal(1234, datatype=rdflib.XSD.integer)), (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(self.p_tag.uri), rdflib.URIRef('http://example.com/me/tag#1234')), (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(self.p_tag.uri), rdflib.URIRef('http://example.com/me/tag#4321')), (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(self.p_tag.uri), rdflib.URIRef('http://example.com/me/tag#1234')), (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(self.p_tag.uri), rdflib.URIRef('http://example.com/me/tag#4321')), })) # cannot set protected predicate curr = set(self.backend._graph) self.assertRaises(errors.PermissionDeniedError, nodes.set_from_iterable, ((self.p_filesize.uri, 1234), (self.t_created.uri, 12345))) self.assertSetEqual(curr, set(self.backend._graph)) # predicate.domain must match node_type self.assertRaises(errors.ConsistencyError, nodes.set_from_iterable, ((self.p_filesize.uri, 1234), (self.p_representative.uri, nodes))) self.assertSetEqual(curr, set(self.backend._graph)) # value's node_type must match predicate's range self.assertRaises(errors.ConsistencyError, nodes.set_from_iterable, ((self.p_filesize.uri, 1234), (self.p_tag.uri, nodes))) self.assertSetEqual(curr, set(self.backend._graph)) # value type must match predicate's range type self.assertRaises(TypeError, nodes.set_from_iterable, ((self.p_filesize.uri, 1234), (self.p_tag.uri, 'invalid'))) self.assertSetEqual(curr, set(self.backend._graph)) # cannot assing multiple values to unique predicate self.assertRaises(ValueError, nodes.set_from_iterable, ((self.p_filesize.uri, 1234), (self.p_author.uri, Nodes(self.backend, self.ac, self.user_type, {URI('http://example.com/me/user#1234'), URI('http://example.com/me/user#4321')})))) self.assertSetEqual(curr, set(self.backend._graph)) # can set on empty nodes nodes = Nodes(self.backend, self.ac, self.ent_type, {}) self.assertEqual(nodes, nodes.set_from_iterable([(self.p_filesize.uri, 1234)])) def test_get(self): # setup: add some instances Nodes(self.backend, self.ac, self.ent_type, {'http://example.com/me/entity#1234'}) \ .set(ns.bse.comment, 'hello world') \ .set(ns.bse.filesize, 1234) \ .set(ns.bse.tag, Nodes(self.backend, self.ac, self.tag_type, {'http://example.com/me/tag#1234'})) Nodes(self.backend, self.ac, self.ent_type, {'http://example.com/me/entity#4321'}) \ .set(ns.bse.filesize, 4321) \ .set(ns.bse.tag, Nodes(self.backend, self.ac, self.tag_type, {'http://example.com/me/tag#4321'})) Nodes(self.backend, self.ac, self.tag_type, {'http://example.com/me/tag#1234'}) \ .set(ns.bst.label, 'tag_label_1234') Nodes(self.backend, self.ac, self.tag_type, {'http://example.com/me/tag#4321'}) \ .set(ns.bst.label, 'tag_label_4321') # setup: get nodes instance nodes = Nodes(self.backend, self.ac, self.ent_type, self.ent_ids) # must pass at least one path self.assertRaises(AttributeError, nodes.get) # view must be list or dict self.assertRaises(ValueError, nodes.get, ns.bse.filesize, view='hello') self.assertRaises(ValueError, nodes.get, ns.bse.filesize, view=1234) self.assertRaises(ValueError, nodes.get, ns.bse.filesize, view=tuple) # can pass path as URI self.assertDictEqual(nodes.get(ns.bse.filesize), { Nodes(self.backend, self.ac, self.ent_type, {'http://example.com/me/entity#1234'}): 1234, Nodes(self.backend, self.ac, self.ent_type, {'http://example.com/me/entity#4321'}): 4321, }) # can pass path as sequence of URI self.assertDictEqual(nodes.get((ns.bse.tag, ns.bst.label)), { Nodes(self.backend, self.ac, self.ent_type, {'http://example.com/me/entity#1234'}): {'tag_label_1234'}, Nodes(self.backend, self.ac, self.ent_type, {'http://example.com/me/entity#4321'}): {'tag_label_4321'}, }) # get returns the same path that was passed self.assertCountEqual(list(nodes.get((ns.bse.tag, ns.bst.label), path=True, view=list)), [ (Nodes(self.backend, self.ac, self.ent_type, {'http://example.com/me/entity#1234'}), (ns.bse.tag, ns.bst.label), 'tag_label_1234'), (Nodes(self.backend, self.ac, self.ent_type, {'http://example.com/me/entity#4321'}), (ns.bse.tag, ns.bst.label), 'tag_label_4321'), ]) self.assertCountEqual(list(nodes.get([ns.bse.tag, ns.bst.label], path=True, view=list)), [ (Nodes(self.backend, self.ac, self.ent_type, {'http://example.com/me/entity#1234'}), [ns.bse.tag, ns.bst.label], 'tag_label_1234'), (Nodes(self.backend, self.ac, self.ent_type, {'http://example.com/me/entity#4321'}), [ns.bse.tag, ns.bst.label], 'tag_label_4321'), ]) # paths must be URI or sequence thereof self.assertRaises(TypeError, nodes.get, 1234) self.assertRaises(TypeError, nodes.get, (ns.bse.tag, 1234)) self.assertRaises(TypeError, nodes.get, (1234, ns.bse.tag)) self.assertRaises(ValueError, nodes.get, 'hello world') self.assertRaises(errors.ConsistencyError, nodes.get, 'hello_world') self.assertRaises(errors.ConsistencyError, nodes.get, ns.bse.invalid) self.assertRaises(errors.ConsistencyError, nodes.get, (ns.bse.tag, ns.bst.invalid)) # can pass multiple paths self.assertDictEqual(nodes.get(ns.bse.filesize, (ns.bse.tag, ns.bst.label)), { Nodes(self.backend, self.ac, self.ent_type, {'http://example.com/me/entity#1234'}): { ns.bse.filesize: 1234, (ns.bse.tag, ns.bst.label): {'tag_label_1234'}, }, Nodes(self.backend, self.ac, self.ent_type, {'http://example.com/me/entity#4321'}): { ns.bse.filesize: 4321, (ns.bse.tag, ns.bst.label): {'tag_label_4321'}, }, }) # get respects view self.assertDictEqual(nodes.get(ns.bse.filesize, view=dict), { Nodes(self.backend, self.ac, self.ent_type, {'http://example.com/me/entity#1234'}): 1234, Nodes(self.backend, self.ac, self.ent_type, {'http://example.com/me/entity#4321'}): 4321, }) self.assertSetEqual(set(nodes.get(ns.bse.filesize, view=list)), { (Nodes(self.backend, self.ac, self.ent_type, {'http://example.com/me/entity#1234'}), 1234), (Nodes(self.backend, self.ac, self.ent_type, {'http://example.com/me/entity#4321'}), 4321), }) # get returns Nodes instance when fetching a node self.assertDictEqual(nodes.get(ns.bse.tag), { Nodes(self.backend, self.ac, self.ent_type, {'http://example.com/me/entity#1234'}): {Nodes(self.backend, self.ac, self.tag_type, {'http://example.com/me/tag#1234'})}, Nodes(self.backend, self.ac, self.ent_type, {'http://example.com/me/entity#4321'}): {Nodes(self.backend, self.ac, self.tag_type, {'http://example.com/me/tag#4321'})}, }) # get returns a value when fetching a value and omits missing values self.assertDictEqual(nodes.get(ns.bse.comment), { Nodes(self.backend, self.ac, self.ent_type, {'http://example.com/me/entity#1234'}): {'hello world'}, }) # results can be empty nodes = Nodes(self.backend, self.ac, self.ent_type, {'http://example.com/me/entity#4321'}) # has filesize, tag but no comment # unique paths return the default value self.assertIsNone(nodes.get(ns.bse.author)) self.assertEqual(nodes.get(ns.bse.author, default=1234), 1234) # non-unique paths return an empty set self.assertSetEqual(nodes.get(ns.bse.comment), set()) # nodes can have no guids nodes = Nodes(self.backend, self.ac, self.ent_type, set()) # empty nodes does not excuse an invalid request self.assertRaises(TypeError, nodes.get, 1234) self.assertRaises(errors.ConsistencyError, nodes.get, ns.bse.invalid) # list view always returns an empty list self.assertListEqual(list(nodes.get(ns.bse.comment, view=list)), []) self.assertListEqual(list(nodes.get(ns.bse.comment, view=list, node=True)), []) self.assertListEqual(list(nodes.get(ns.bse.comment, view=list, path=True)), []) self.assertListEqual(list(nodes.get(ns.bse.comment, view=list, node=True, path=True, value=True)), []) self.assertListEqual(list(nodes.get(ns.bse.comment, view=list, node=False)), []) self.assertListEqual(list(nodes.get(ns.bse.comment, view=list, path=False)), []) self.assertListEqual(list(nodes.get(ns.bse.comment, view=list, node=False, path=False, value=False)), []) self.assertListEqual(list(nodes.get(ns.bse.comment, ns.bse.filesize, view=list)), []) self.assertListEqual(list(nodes.get(ns.bse.comment, ns.bse.filesize, view=list, node=True)), []) self.assertListEqual(list(nodes.get(ns.bse.comment, ns.bse.filesize, view=list, path=True)), []) self.assertListEqual(list(nodes.get(ns.bse.comment, ns.bse.filesize, view=list, node=True, path=True, value=True)), []) self.assertListEqual(list(nodes.get(ns.bse.comment, ns.bse.filesize, view=list, node=False)), []) self.assertListEqual(list(nodes.get(ns.bse.comment, ns.bse.filesize, view=list, path=False)), []) self.assertListEqual(list(nodes.get(ns.bse.comment, ns.bse.filesize, view=list, node=False, path=False, value=False)), []) # dict view returns an empty dict or an empty set self.assertDictEqual(nodes.get(ns.bse.comment, view=dict), {}) self.assertDictEqual(nodes.get(ns.bse.comment, view=dict, node=True), {}) self.assertDictEqual(nodes.get(ns.bse.comment, view=dict, path=True), {}) self.assertDictEqual(nodes.get(ns.bse.comment, view=dict, node=True, path=True, value=True, default=None), {}) self.assertSetEqual(nodes.get(ns.bse.comment, view=dict, node=False), set()) self.assertDictEqual(nodes.get(ns.bse.comment, view=dict, path=False), {}) self.assertSetEqual(nodes.get(ns.bse.comment, view=dict, node=False, path=False), set()) self.assertSetEqual(nodes.get(ns.bse.comment, view=dict, node=False, path=False, value=False, default=None), set()) self.assertDictEqual(nodes.get(ns.bse.comment, ns.bse.filesize, view=dict), {}) self.assertDictEqual(nodes.get(ns.bse.comment, ns.bse.filesize, view=dict, node=True), {}) self.assertDictEqual(nodes.get(ns.bse.comment, ns.bse.filesize, view=dict, path=True), {}) self.assertDictEqual(nodes.get(ns.bse.comment, ns.bse.filesize, view=dict, node=True, path=True, value=True, default=None), {}) self.assertDictEqual(nodes.get(ns.bse.comment, ns.bse.filesize, view=dict, node=False), {}) self.assertDictEqual(nodes.get(ns.bse.comment, ns.bse.filesize, view=dict, path=False), {}) self.assertSetEqual(nodes.get(ns.bse.comment, ns.bse.filesize, view=dict, node=False, path=False), set()) self.assertSetEqual(nodes.get(ns.bse.comment, ns.bse.filesize, view=dict, node=False, path=False, value=False, default=None), set()) def test_getattr(self): nodes = Nodes(self.backend, self.ac, self.ent_type, {'http://example.com/me/entity#1234'}) # can get walks to values self.assertEqual(nodes.filesize, Walk(nodes, (self.p_filesize, ))) # can get walks to nodes self.assertEqual(nodes.tag, Walk(nodes, (self.p_tag, ))) # can do multiple hops self.assertEqual(nodes.tag.label, Walk(nodes, (self.p_tag, self.p_label))) # invalid step raises an error self.assertRaises(ValueError, getattr, nodes, 'foobar') def test_schema(self): self.assertEqual(Nodes(self.backend, self.ac, self.ent_type, {URI('http://example.com/me/entity#1234')}).schema, self.backend.schema) def test_operators(self): # __add__, __or__, __sub__, __and__ gen = partial(Nodes, self.backend, self.ac, self.ent_type) nodes = gen({URI('http://example.com/me/entity#1234')}) # add/or concatenates guids self.assertEqual( gen({URI('http://example.com/me/entity#1234')}) + gen({URI('http://example.com/me/entity#4321')}), # target gen({ URI('http://example.com/me/entity#1234'), URI('http://example.com/me/entity#4321')})) self.assertEqual( gen({URI('http://example.com/me/entity#1234')}) | gen({URI('http://example.com/me/entity#4321')}), # target gen({ URI('http://example.com/me/entity#1234'), URI('http://example.com/me/entity#4321')})) # repeated guids are ignored self.assertEqual( gen({URI('http://example.com/me/entity#1234')}) + gen({URI('http://example.com/me/entity#1234')}), # target gen({URI('http://example.com/me/entity#1234')})) self.assertEqual( gen({URI('http://example.com/me/entity#1234')}) | gen({URI('http://example.com/me/entity#1234')}), # target gen({URI('http://example.com/me/entity#1234')})) # sub substracts guids self.assertEqual( gen({URI('http://example.com/me/entity#1234'), URI('http://example.com/me/entity#4321')}) - gen({URI('http://example.com/me/entity#4321')}), # target gen({URI('http://example.com/me/entity#1234')})) # missing guids are ignored self.assertEqual( gen({URI('http://example.com/me/entity#1234')}) - gen({URI('http://example.com/me/entity#4321')}), # target gen({URI('http://example.com/me/entity#1234')})) # and intersects guids self.assertEqual( gen({URI('http://example.com/me/entity#1234'), URI('http://example.com/me/entity#4321')}) & gen({URI('http://example.com/me/entity#4321'), URI('http://example.com/me/entity#5678')}), # target gen({URI('http://example.com/me/entity#4321')})) for op in (operator.add, operator.or_, operator.sub, operator.and_): # type must match self.assertRaises(TypeError, op, nodes, 1234) self.assertRaises(TypeError, op, nodes, 'hello world') # backend must match self.assertRaises(ValueError, op, nodes, Nodes(None, self.ac, self.ent_type, {URI('http://example.com/me/entity#1234')})) # ac must match self.assertRaises(ValueError, op, nodes, Nodes(self.backend, NullAC(self.backend, ''), self.ent_type, {URI('http://example.com/me/entity#1234')})) # node type must match self.assertRaises(ValueError, op, nodes, Nodes(self.backend, self.ac, self.tag_type, {URI('http://example.com/me/entity#1234')})) def test_len(self): self.assertEqual(1, len(Nodes(self.backend, self.ac, self.ent_type, { URI('http://example.com/me/entity#1234'), }))) self.assertEqual(2, len(Nodes(self.backend, self.ac, self.ent_type, { URI('http://example.com/me/entity#1234'), URI('http://example.com/me/entity#4321'), }))) self.assertEqual(4, len(Nodes(self.backend, self.ac, self.ent_type, { URI('http://example.com/me/entity#1234'), URI('http://example.com/me/entity#4321'), URI('http://example.com/me/entity#5678'), URI('http://example.com/me/entity#8765'), }))) def test_iter(self): # __iter__ gen = partial(Nodes, self.backend, self.ac, self.ent_type) self.assertSetEqual(set(Nodes(self.backend, self.ac, self.ent_type, { URI('http://example.com/me/entity#1234'), URI('http://example.com/me/entity#4321'), URI('http://example.com/me/entity#5678'), URI('http://example.com/me/entity#8765'), })), { gen({URI('http://example.com/me/entity#1234')}), gen({URI('http://example.com/me/entity#4321')}), gen({URI('http://example.com/me/entity#5678')}), gen({URI('http://example.com/me/entity#8765')}), }) ## main ## if __name__ == '__main__': unittest.main() ## EOF ##