def test_text_turtle_another_session(self): """Test to a non-default session.""" another_session = CoreSession() with CoreSession() as session: test_data_path = str( Path(__file__).parent / "test_importexport_data.ttl") with open(test_data_path, "r") as test_data_file: test_data = test_data_file.read() test_data = io.StringIO(test_data) loaded_objects = import_cuds(test_data, format="text/turtle", session=another_session) # The expected objects will not be found in session, they will # be none. expected_objects = tuple( session.load_from_iri( rdflib.URIRef( f"http://example.org/test-ontology#x_{i}")).first() for i in range(1, 5)) self.assertTrue(all(x is None for x in expected_objects)) # Test correctness in the other session. data_integrity(self, another_session, loaded_objects, label="import")
def test_all(self): """Test the all() method.""" r = QueryResult(CoreSession(), iter(range(10))) self.assertEqual(r.all(), list(range(10))) self.assertEqual(r.all(), list(range(10))) r = QueryResult(CoreSession(), iter(range(10))) next(r) self.assertEqual(r.all(), list(range(10))) self.assertEqual(r.all(), list(range(10)))
def test_first(self): """Test the first() method.""" r = QueryResult(CoreSession(), iter(range(10))) self.assertEqual(r.first(), 0) self.assertEqual(r.first(), 0) self.assertEqual(r.all(), list(range(10))) r = QueryResult(CoreSession(), iter(range(0))) self.assertEqual(r.first(), None) self.assertEqual(r.first(), None) self.assertEqual(r.all(), list(range(0)))
def test_text_turtle(self): """Test importing and exporting the `text/turtle` mime type.""" with CoreSession() as session: test_data_path = str( Path(__file__).parent / "test_importexport_data.ttl") loaded_objects = import_cuds(test_data_path, format="text/turtle") data_integrity(self, session, loaded_objects, label="import") exported_file = io.StringIO() export_cuds(file=exported_file, format="text/turtle") exported_file.seek(0) with CoreSession() as session: exported_objects = import_cuds(exported_file, format="text/turtle") data_integrity(self, session, exported_objects, label="export")
def test_application_json(self): """Test importing and exporting the `application/ld+json` mime type.""" with CoreSession() as session: test_data_path = str( Path(__file__).parent / "test_importexport_data.json") loaded_objects = import_cuds(test_data_path, format="application/ld+json") data_integrity(self, session, loaded_objects, label="import") exported_file = io.StringIO() export_cuds(file=exported_file, format="application/ld+json") exported_file.seek(0) with CoreSession() as session: exported_objects = import_cuds(exported_file, format="application/ld+json") data_integrity(self, session, exported_objects, label="export")
def test_one(self): """Test the one() method.""" r = QueryResult(CoreSession(), iter(range(10))) self.assertRaises(MultipleResultsError, r.one) self.assertRaises(MultipleResultsError, r.one) self.assertEqual(r.all(), list(range(10))) r = QueryResult(CoreSession(), iter(range(2))) next(r) self.assertRaises(MultipleResultsError, r.one) self.assertRaises(MultipleResultsError, r.one) self.assertEqual(r.all(), list(range(2))) r = QueryResult(CoreSession(), iter(range(0))) self.assertRaises(ResultEmptyError, r.one) self.assertRaises(ResultEmptyError, r.one) self.assertEqual(r.all(), list(range(0)))
def test_recursive_store(self): """Test if _recursive_store correctly stores cuds_objects correctly. It should correct dangling and one-way connections. """ c = city.City(name="Freiburg") p1 = city.Citizen() p2 = city.Citizen() p3 = city.Citizen() p4 = city.Citizen() with CoreSession() as session: w = city.CityWrapper(session=session) cw = w.add(c) c.add(p1, p2, p3, rel=city.hasInhabitant) p3.add(p1, p2, rel=city.isChildOf) p3.add(p4, rel=city.hasChild) cw = w._recursive_store(c, cw) p1w, p2w, p3w = cw.get(p1.uid, p2.uid, p3.uid) p4w = p3w.get(p4.uid) self.assertEqual(w.get(rel=city.hasPart), [cw]) self.assertEqual(set(cw.get(rel=city.hasInhabitant)), {p1w, p2w, p3w}) self.assertEqual(set(cw.get(rel=city.isPartOf)), {w}) self.assertEqual(p3w.get(rel=city.INVERSE_OF_hasInhabitant), [cw]) self.assertEqual(set(p3w.get(rel=city.isChildOf)), {p1w, p2w}) self.assertEqual(p3w.get(rel=city.hasChild), [p4w])
def test_fix_old_neighbors(self): """Check if _fix_old_neighbors. - Deletes old children. - Adds connection to old parents. """ c = city.City(name="Freiburg") with CoreSession() as session: wrapper = city.CityWrapper(session=session) cw = wrapper.add(c) n = city.Neighborhood(name="Zähringen") nw = cw.add(n) c = clone_cuds_object(c) c._session = session old_neighbor_diff = get_neighbor_diff(cw, c) old_neighbors = session.load(*[x[0] for x in old_neighbor_diff]) Cuds._fix_old_neighbors( new_cuds_object=c, old_cuds_object=cw, old_neighbors=old_neighbors, old_neighbor_diff=old_neighbor_diff, ) self.assertEqual(c.get(rel=city.isPartOf), [wrapper]) self.assertEqual(c.get(rel=city.hasPart), []) self.assertEqual(nw.get(rel=city.isPartOf), []) self.assertEqual(wrapper.get(rel=city.hasPart), [c])
def test_application_rdf_xml_guess_format(self): """Test guessing and importing the `application/rdf+xml` mime type.""" with CoreSession() as session: test_data_path = str( Path(__file__).parent / "test_importexport_data.owl") loaded_objects = import_cuds(test_data_path) data_integrity(self, session, loaded_objects, label="import")
def test_text_turtle_guess_format(self): """Test guessing and importing the `text/turtle` mime type.""" with CoreSession() as session: test_data_path = str( Path(__file__).parent / "test_importexport_data.ttl") loaded_objects = import_cuds(test_data_path) data_integrity(self, session, loaded_objects, label="import")
def test_next(self): """Test __next__ magic method.""" r = QueryResult(CoreSession(), iter(range(10))) self.assertEqual(0, next(r)) self.assertEqual(1, next(r)) self.assertEqual(2, next(r)) self.assertEqual(r.all(), list(range(10))) self.assertRaises(StopIteration, next, r)
def test_create_recycle(self): """Test creation of cuds_objects for different session.""" default_session = CoreSession() osp.core.cuds.Cuds._session = default_session a = city.City(name="Freiburg") self.assertIs(a.session, default_session) with TestWrapperSession() as session: w = city.CityWrapper(session=session) with EngineContext(session): b = create_recycle(oclass=city.City, kwargs={"name": "Offenburg"}, uid=a.uid, session=session, fix_neighbors=False) self.assertEqual(b.name, "Offenburg") self.assertEqual(b.uid, a.uid) self.assertEqual(set(default_session._registry.keys()), {a.uid}) self.assertIs(default_session._registry.get(a.uid), a) self.assertEqual(set(session._registry.keys()), {b.uid, w.uid}) self.assertIs(session._registry.get(b.uid), b) self.assertEqual(session._buffers, [[{ w.uid: w }, dict(), dict()], [{ b.uid: b }, dict(), dict()]]) x = city.Citizen() x = b.add(x, rel=city.hasInhabitant) c = create_recycle(oclass=city.City, kwargs={"name": "Emmendingen"}, session=session, uid=a.uid, fix_neighbors=False) self.assertIs(b, c) self.assertEqual(c.name, "Emmendingen") self.assertEqual(c.get(rel=cuba.relationship), []) self.assertNotEqual(x.get(rel=cuba.relationship), []) self.assertEqual(set(default_session._registry.keys()), {a.uid, x.uid}) self.assertIs(default_session._registry.get(a.uid), a) self.assertEqual(session._buffers, [[{ w.uid: w, x.uid: x }, { c.uid: c }, dict()], [dict(), dict(), dict()]]) x = city.Citizen() x = c.add(x, rel=city.hasInhabitant) c = create_recycle(oclass=city.City, kwargs={"name": "Karlsruhe"}, session=session, uid=a.uid, fix_neighbors=True) self.assertEqual(x.get(rel=cuba.relationship), [])
def test_iter(self): """Test the __iter__() magic method.""" r = QueryResult(CoreSession(), iter(range(10))) i = iter(r) self.assertEqual(list(zip(i, range(5))), list(zip(range(5), range(5)))) self.assertEqual(list(zip(i, range(5))), list(zip(range(6, 10), range(5)))) self.assertEqual(r.all(), list(range(10)))
def test_text_turtle_file_handle(self): """Test importing the `text/turtle` mime type from a file handle.""" with CoreSession() as session: test_data_path = str( Path(__file__).parent / "test_importexport_data.ttl") with open(test_data_path, "r") as test_data_file: loaded_objects = import_cuds(test_data_file, format="text/turtle") data_integrity(self, session, loaded_objects, label="import")
def test_add_multi_session(self): """Test the add method in a comext of multiple sessions.""" with CoreSession() as session: wrapper = cuba.Wrapper(session=session) aw = cuba.Entity(session=session) b = cuba.Entity() c = cuba.Entity() bw = aw.add(b, rel=cuba.activeRelationship) c.add(b, rel=cuba.activeRelationship) wrapper.add(c, rel=cuba.activeRelationship) self.assertIn(bw, aw.get(rel=cuba.activeRelationship)) self.assertIn(aw, bw.get(rel=cuba.passiveRelationship))
def test_clone_cuds_object(self): """Test cloning of cuds.""" a = city.City(name="Freiburg") with CoreSession() as session: w = city.CityWrapper(session=session) aw = w.add(a) clone = clone_cuds_object(aw) self.assertIsNot(aw, None) self.assertIs(clone.session, aw.session) self.assertEqual(clone.uid, aw.uid) self.assertIs(aw, session._registry.get(aw.uid)) self.assertEqual(clone.name, "Freiburg")
def test_add_twice(self): """Test what happens if you add the same object twice.""" p = city.Citizen(name="Ralf") c1 = city.City(name="Freiburg") c2 = city.City(name="Offenburg") with CoreSession() as session: w = city.CityWrapper(session=session) c1w, c2w = w.add(c1, c2) pw1 = c1w.add(p, rel=city.hasInhabitant) pw2 = c2w.add(p, rel=city.hasInhabitant) self.assertIs(pw1, pw2) self.assertEqual(set(pw1.get(rel=city.INVERSE_OF_hasInhabitant)), {c1w, c2w})
def test_application_json_doc_city(self): """Test importing the `application/ld+json` mime type from doc dict. This test uses a city ontology instead. """ # Importing test_data_path = str( Path(__file__).parent / "test_importexport_city_import.json") with open(test_data_path, "r") as file: json_doc = json.loads(file.read()) with CoreSession(): cuds = import_cuds(json_doc, format="application/ld+json") self.assertTrue(cuds.is_a(city.Citizen)) self.assertEqual(cuds.name, "Peter") self.assertEqual(cuds.age, 23) export_file = io.StringIO() export_cuds(cuds, file=export_file, format="application/ld+json") export_file.seek(0) assertJsonLdEqual(self, json_doc, json.loads(export_file.read())) # Exporting test_data_path = str( Path(__file__).parent / "test_importexport_city_export.json") with open(test_data_path, "r") as file: json_doc = json.loads(file.read()) with CoreSession(): c = branch( city.City(name="Freiburg", uid=1), branch( city.Neighborhood(name="Littenweiler", uid=2), city.Street(name="Schwarzwaldstraße", uid=3), ), ) export_file = io.StringIO() export_cuds(c, file=export_file, format="application/ld+json") export_file.seek(0) assertJsonLdEqual(self, json.loads(export_file.read()), json_doc)
def test_creation(self): """Test the instantiation and type of the objects.""" self.assertRaises(TypeError, math.Real, hasNumericalData=1.2, uid=0, unwanted="unwanted") self.assertRaises(TypeError, math.Real) r = math.Real(hasNumericalData=1.2, hasSymbolData="1.2") r2 = math.Real(hasNumericalData=1.2) p = holistic.Process() self.assertEqual(r.oclass, math.Real) self.assertEqual(r2.oclass, math.Real) self.assertEqual(p.oclass, holistic.Process) cuba.Wrapper(session=CoreSession())
def test_create_from_cuds_object(self): """Test copying cuds_objects to different session.""" default_session = CoreSession() Cuds._session = default_session a = city.City(name="Freiburg") self.assertIs(a.session, default_session) with TestWrapperSession() as session: w = city.CityWrapper(session=session) with EngineContext(session): b = create_from_cuds_object(a, session) self.assertEqual(b.name, "Freiburg") self.assertEqual(b.uid, a.uid) self.assertEqual(set(default_session._registry.keys()), {a.uid}) self.assertIs(default_session._registry.get(a.uid), a) self.assertEqual(set(session._registry.keys()), {b.uid, w.uid}) self.assertIs(session._registry.get(b.uid), b) self.assertEqual( session._buffers, [[dict(), dict(), dict()], [{ b.uid: b }, dict(), dict()]], ) b.name = "Emmendingen" x = city.Citizen(age=54, name="Franz", session=default_session) b.add(x, rel=city.hasInhabitant) y = city.Citizen(age=21, name="Rolf", session=default_session) a.add(y, rel=city.hasInhabitant) c = create_from_cuds_object(a, session) self.assertIs(b, c) self.assertEqual(c.name, "Freiburg") self.assertEqual(len(c.get(rel=cuba.relationship)), 1) self.assertEqual(c._neighbors[city.hasInhabitant], {y.uid: [city.Citizen]}) self.assertEqual(set(default_session._registry.keys()), {a.uid, x.uid, y.uid}) self.assertIs(default_session._registry.get(a.uid), a) self.assertEqual( session._buffers, [[{ x.uid: x }, { c.uid: c }, dict()], [dict(), dict(), dict()]], )
def test_fix_new_parents(self): """Check _fix_new_parent. Make sure the method: - Deletes connection to new parents not available in new session - Adds connection to new parents available in new session """ n = city.Neighborhood(name="Zähringen") # parent in both sessions c1 = city.City(name="Berlin") # only parent in default session (available in both) c2 = city.City(name="Paris") n.add(c1, c2, rel=city.isPartOf) c3 = city.City(name="London") with CoreSession() as session: wrapper = city.CityWrapper(session=session) c1w, c2w = wrapper.add(c1, c2) nw = c2w.get(n.uid) nw.remove(c2.uid, rel=cuba.relationship) # only parent + available in default session n.add(c3, rel=city.isPartOf) n = clone_cuds_object(n) n._session = session new_parent_diff = get_neighbor_diff(n, nw, mode="non-active") new_parents = session.load(*[x[0] for x in new_parent_diff]) missing = dict() Cuds._fix_new_parents( new_cuds_object=n, new_parents=new_parents, new_parent_diff=new_parent_diff, missing=missing, ) self.assertEqual( set(n.get(rel=city.isPartOf)), {c1w, c2w, None}, # missing parent, should be in missing dict ) self.assertEqual(missing, {c3.uid: [(n, city.isPartOf)]}) self.assertEqual(c2w.get(rel=city.hasPart), [n])
def test_creation(self): """Tests the instantiation and type of the objects.""" self.assertRaises( TypeError, city.City, name="name", coordinates=[1, 2], uid=0, unwanted="unwanted", ) self.assertRaises(TypeError, city.City) c = city.City(name="a city") p = city.Person() self.assertEqual(c.oclass, city.City) self.assertEqual(p.oclass, city.Person) self.assertRaises(TypeError, cuba.Nothing) self.assertRaises(TypeError, cuba.Wrapper) cuba.Wrapper(session=CoreSession())
def test_update(self): """Test the standard, normal behavior of the update() method.""" c = holistic.Process() n = math.Real(hasNumericalData=1.2) new_n = create_from_cuds_object(n, CoreSession()) new_s = math.Integer(hasNumericalData=42) new_n.add(new_s) c.add(n) old_real = c.get(n.uid) old_integers = old_real.get(oclass=math.Integer) self.assertEqual(old_integers, []) c.update(new_n) new_real = c.get(n.uid) new_integers = new_real.get(oclass=math.Integer) self.assertEqual(new_integers, [new_s]) self.assertRaises(ValueError, c.update, n)
def test_update(self): """Tests the standard, normal behavior of the update() method.""" c = city.City(name="a city") n = city.Neighborhood(name="a neighborhood") new_n = create_from_cuds_object(n, CoreSession()) new_s = city.Street(name="a new street") new_n.add(new_s) c.add(n) old_neighborhood = c.get(n.uid) old_streets = old_neighborhood.get(oclass=city.Street) self.assertEqual(old_streets, []) c.update(new_n) new_neighborhood = c.get(n.uid) self.assertIs(new_neighborhood, n) new_streets = new_neighborhood.get(oclass=city.Street) self.assertEqual(new_streets, [new_s]) self.assertRaises(ValueError, c.update, n)
def test_default_session_context_manager(self): """Test changing the default session with a session context manager.""" default_session = CoreSession() Cuds._session = default_session bern = city.City(name="Bern") with TestSession() as session1: freiburg = city.City(name="Freiburg") with TestSession() as session2: berlin = city.City(name="Berlin") with TestSession() as session3: madrid = city.City(name="Madrid") with TestSession() as session4: beijing = city.City(name="北京") self.assertIs(freiburg.session, session1) self.assertIs(berlin.session, session2) self.assertIs(madrid.session, session3) self.assertIs(beijing.session, session4) paris = city.City(name="Paris") self.assertIs(berlin.session, paris.session) self.assertIsNot(berlin.session, beijing.session) tokyo = city.City(name="Tokyo") # Test default session restore. self.assertIs(bern.session, tokyo.session) self.assertIs(bern.session, default_session)
def test_recursive_add(self): """Tests if add() works correctly. In this test case the added cuds_object is from another session. """ c = city.City(name="City") p1 = city.Citizen() c.add(p1, rel=city.hasInhabitant) second_session = CoreSession() w = city.CityWrapper(session=second_session) cw = w.add(c) p1w = cw.get(p1.uid) self.assertIs(cw.session, second_session) self.assertIs(p1w.session, second_session) self.assertIsNot(c.session, second_session) self.assertIsNot(p1.session, second_session) self.assertTrue(c._stored) self.assertTrue(p1._stored) self.assertTrue(w._stored) self.assertTrue(cw._stored) self.assertTrue(p1w._stored) p2 = city.Citizen() p3 = city.Citizen() p4 = city.Citizen() c.add(p2, p3, rel=city.hasInhabitant) p1.add(p2, rel=city.hasChild) p3.add(p2, rel=city.hasChild) p2.add(p4, rel=city.hasChild) cw.add(p2, rel=city.hasInhabitant) p2w = cw.get(p2.uid) p4w = p2w.get(p4.uid) # check if there are unexpected changes in the first session # first check active relationships self.assertEqual( set(c._neighbors[city.hasInhabitant].keys()), set([p1.uid, p2.uid, p3.uid]), ) self.assertEqual(set([p2.uid]), set(p1._neighbors[city.hasChild].keys())) self.assertEqual(set([p2.uid]), set(p3._neighbors[city.hasChild].keys())) self.assertEqual(set([p4.uid]), set(p2._neighbors[city.hasChild].keys())) # check passive relationships self.assertEqual( set([c.uid]), set(p1._neighbors[city.INVERSE_OF_hasInhabitant].keys()), ) self.assertEqual(set([p1.uid, p3.uid]), set(p2._neighbors[city.isChildOf].keys())) self.assertEqual( set([c.uid]), set(p2._neighbors[city.INVERSE_OF_hasInhabitant].keys()), ) self.assertEqual( set([c.uid]), set(p3._neighbors[city.INVERSE_OF_hasInhabitant].keys()), ) self.assertEqual(set([p2.uid]), set(p4._neighbors[city.isChildOf].keys())) # check if items correctly added in second session # active relations self.assertEqual( set(cw._neighbors[city.hasInhabitant].keys()), set([p1w.uid, p2w.uid]), ) self.assertEqual(set([p2w.uid]), set(p1w._neighbors[city.hasChild].keys())) self.assertEqual(set([p4w.uid]), set(p2w._neighbors[city.hasChild].keys())) # passive relations self.assertEqual( set([cw.uid]), set(p1w._neighbors[city.INVERSE_OF_hasInhabitant].keys()), ) self.assertEqual(set([p1w.uid]), set(p2w._neighbors[city.isChildOf].keys())) self.assertEqual( set([cw.uid]), set(p2w._neighbors[city.INVERSE_OF_hasInhabitant].keys()), ) self.assertEqual(set([p2w.uid]), set(p4w._neighbors[city.isChildOf].keys()))
def test_fix_neighbors(self): """Test fixing the neighbors after replacing CUDS objects.""" w1 = city.CityWrapper(session=CoreSession()) w2 = city.CityWrapper(session=CoreSession()) c1w1 = city.City(name="city1") # parent in both wrappers c2w1 = city.City(name="city2") # parent in w1, not present in w2 c3w1 = city.City(name="city3") # parent only in w1, present in w2 c4w1 = city.City(name="city4") # parent only in w2 p1w1 = city.Citizen(name="citizen") p2w1 = city.Citizen(name="child") w2.add(c1w1, c3w1, c4w1) c1w2, c3w2, c4w2 = w2.get(c1w1.uid, c3w1.uid, c4w1.uid) c1w2.add(p1w1, rel=city.hasInhabitant) c4w2.add(p1w1, rel=city.hasInhabitant) p1w2 = c1w2.get(p1w1.uid) p1w2.add(p2w1, rel=city.hasChild) p2w2 = p1w2.get(p2w1.uid) w1.add(c1w1, c2w1, c3w1, c4w1) c1w1.add(p1w1, rel=city.hasInhabitant) c2w1.add(p1w1, rel=city.hasInhabitant) c3w1.add(p1w1, rel=city.hasInhabitant) self.assertEqual( set(p1w1._neighbors[city.INVERSE_OF_hasInhabitant].keys()), set([c1w1.uid, c2w1.uid, c3w1.uid]), ) self.assertEqual(set(p2w2._neighbors[city.isChildOf].keys()), set([p1w2.uid])) missing = dict() Cuds._fix_neighbors( new_cuds_object=p1w1, old_cuds_object=p1w2, session=p1w2.session, missing=missing, ) # check if connections cuds_objects that are no # longer parents are in the missing dict self.assertIn(c2w1.uid, missing) self.assertEqual( set(p1w1._neighbors[city.INVERSE_OF_hasInhabitant].keys()), set([c1w1.uid, c2w1.uid, c3w1.uid, c4w1.uid]), ) self.assertNotIn(city.isPartOf, p2w2._neighbors) # check if there are no unexpected other changes self.assertEqual(set(c1w1._neighbors[city.hasInhabitant].keys()), set([p1w1.uid])) self.assertEqual(set(c2w1._neighbors[city.hasInhabitant].keys()), set([p1w1.uid])) self.assertEqual(set(c3w1._neighbors[city.hasInhabitant].keys()), set([p1w1.uid])) self.assertNotIn(city.hasInhabitant, c4w1._neighbors) # check if the parents in w2 have been updated self.assertEqual(set(c1w2._neighbors[city.hasInhabitant].keys()), set([p1w1.uid])) self.assertEqual(set(c3w2._neighbors[city.hasInhabitant].keys()), set([p1w1.uid])) self.assertEqual(set(c4w2._neighbors[city.hasInhabitant].keys()), set([p1w1.uid]))
class Cuds(): """A Common Universal Data Structure. The CUDS object has attributes and is connected to other cuds objects via relationships. It is an ontology individual that can be used like a container. """ _session = CoreSession() def __init__( self, attributes: Dict[OntologyAttribute, Any], oclass: OntologyEntity, session: Session = None, uid: uuid.UUID = None ): """Initialize a CUDS object. This method should not be called by the user directly. Instead use the __call__ magic method of OntologyClass. Construct the CUDS object. This will also register the CUDS objects in the corresponding session. Args: attributes (Dict[OntologyAttribute, Any]): Mapping from ontology attribute to specified value. oclass (OntologyEntity): The ontology class of the CUDS object. session (Session, optional): The session associated with the CUDS, if None is given it will be associated with the CoreSession. Defaults to None. uid (uuid.UUID, optional): A unique identifier. If None given, a random uid will be created. Defaults to None. Raises: ValueError: Uid of zero is not allowed. """ self._session = session or Cuds._session self._graph = rdflib.Graph() self.__uid = uuid.uuid4() if uid is None else convert_to(uid, "UUID") if self.__uid.int == 0: raise ValueError("Invalid UUID") for k, v in attributes.items(): self._graph.add(( self.iri, k.iri, rdflib.Literal(k.convert_to_datatype(v), datatype=k.datatype) )) if oclass: self._graph.add(( self.iri, rdflib.RDF.type, oclass.iri )) self.session._store(self) @property def uid(self) -> uuid.UUID: """Get he uid of the cuds object.""" return self.__uid @property def iri(self): """Get the IRI of the CUDS object.""" return iri_from_uid(self.uid) @property def session(self): """Get the session of the cuds object.""" return self._session @property def oclasses(self): """Get the ontology classes of this CUDS object.""" result = list() for s, p, o in self._graph.triples((self.iri, rdflib.RDF.type, None)): r = from_iri(o, raise_error=False) if r is not None: result.append(r) return result @property def oclass(self): """Get the type of the cuds object.""" oclasses = self.oclasses if oclasses: return oclasses[0] return None @property def _neighbors(self): return NeighborDictRel(self) @property def _stored(self): return self._graph is self.session.graph def get_triples(self, include_neighbor_types=False): """Get the triples of the cuds object.""" o_set = set() for s, p, o in self._graph.triples((self.iri, None, None)): yield s, p, o o_set.add(o) if include_neighbor_types: for o in o_set: yield from self._graph.triples((o, rdflib.RDF.type, None)) def get_attributes(self): """Get the attributes as a dictionary.""" if self.session: self.session._notify_read(self) result = {} for s, p, o in self._graph.triples((self.iri, None, None)): obj = from_iri(p, raise_error=False) if isinstance(obj, OntologyAttribute): result[obj] = o.toPython() return result def is_a(self, oclass): """Check if the CUDS object is an instance of the given oclass. Args: oclass (OntologyClass): Check if the CUDS object is an instance of this oclass. Returns: bool: Whether the CUDS object is an instance of the given oclass. """ return any(oc in oclass.subclasses for oc in self.oclasses) def add(self, *args: "Cuds", rel: OntologyRelationship = None) -> Union["Cuds", List["Cuds"]]: """Add CUDS objects to their respective relationship. If the added objects are associated with the same session, only a link is created. Otherwise, the a deepcopy is made and added to the session of this Cuds object. Before adding, check for invalid keys to avoid inconsistencies later. Args: args (Cuds): The objects to be added rel (OntologyRelationship): The relationship between the objects. Raises: TypeError: Ne relationship given and no default specified. ValueError: Added a CUDS object that is already in the container. Returns: Union[Cuds, List[Cuds]]: The CUDS objects that have been added, associated with the session of the current CUDS object. Result type is a list, if more than one CUDS object is returned. """ check_arguments(Cuds, *args) rel = rel or self.oclass.namespace.get_default_rel() if rel is None: raise TypeError("Missing argument 'rel'! No default " "relationship specified for namespace %s." % self.oclass.namespace) result = list() # update cuds objects if they are already in the session old_objects = self._session.load( *[arg.uid for arg in args if arg.session != self.session]) for arg in args: # Recursively add the children to the registry if rel in self._neighbors and arg.uid in self._neighbors[rel]: message = '{!r} is already in the container' raise ValueError(message.format(arg)) if self.session != arg.session: arg = self._recursive_store(arg, next(old_objects)) self._add_direct(arg, rel) arg._add_inverse(self, rel) result.append(arg) return result[0] if len(args) == 1 else result def get(self, *uids: uuid.UUID, rel: OntologyRelationship = cuba.activeRelationship, oclass: OntologyClass = None, return_rel: bool = False) -> Union["Cuds", List["Cuds"]]: """Return the contained elements. Filter elements by given type, uid or relationship. Expected calls are get(), get(*uids), get(rel), get(oclass), get(*uids, rel), get(rel, oclass). If uids are specified: The position of each element in the result is determined by to the position of the corresponding uid in the given list of uids. In this case, the result can contain None values if a given uid is not a child of this cuds_object. If only a single uid is given, only this one element is returned (i.e. no list). If no uids are specified: The result is a collection, where the elements are ordered randomly. Args: uids (uuid.UUID): UUIDs of the elements. rel (OntologyRelationship, optional): Only return cuds_object which are connected by subclass of given relationship. Defaults to cuba.activeRelationship. oclass (OntologyClass, optional): Only return elements which are a subclass of the given ontology class. Defaults to None. return_rel (bool, optional): Whether to return the connecting relationship. Defaults to False. Returns: Union[Cuds, List[Cuds]]: The queried objects. """ result = list( self.iter(*uids, rel=rel, oclass=oclass, return_rel=return_rel) ) if len(uids) == 1: return result[0] return result def update(self, *args: "Cuds") -> List["Cuds"]: """Update the Cuds object. Updates the object by providing updated versions of CUDS objects that are directly in the container of this CUDS object. The updated versions must be associated with a different session. Args: args (Cuds): The updated versions to use to update the current object. Raises: ValueError: Provided a CUDS objects is not in the container of the current CUDS ValueError: Provided CUDS object is associated with the same session as the current CUDS object. Therefore it is not an updated version. Returns: Union[Cuds, List[Cuds]]: The CUDS objects that have been updated, associated with the session of the current CUDS object. Result type is a list, if more than one CUDS object is returned. """ check_arguments(Cuds, *args) old_objects = self.get(*[arg.uid for arg in args]) if len(args) == 1: old_objects = [old_objects] if any(x is None for x in old_objects): message = 'Cannot update because cuds_object not added.' raise ValueError(message) result = list() for arg, old_cuds_object in zip(args, old_objects): if arg.session is self.session: raise ValueError("Please provide CUDS objects from a " "different session to update()") # Updates all instances result.append(self._recursive_store(arg, old_cuds_object)) if len(args) == 1: return result[0] return result def remove(self, *args: Union["Cuds", uuid.UUID], rel: OntologyRelationship = cuba.activeRelationship, oclass: OntologyClass = None): """Remove elements from the CUDS object. Expected calls are remove(), remove(*uids/Cuds), remove(rel), remove(oclass), remove(*uids/Cuds, rel), remove(rel, oclass) Args: args (Union[Cuds, UUID]): UUIDs of the elements to remove or the elements themselves. rel (OntologyRelationship, optional): Only remove cuds_object which are connected by subclass of given relationship. Defaults to cuba.activeRelationship. oclass (OntologyClass, optional): Only remove elements which are a subclass of the given ontology class. Defaults to None. Raises: RuntimeError: No CUDS object removed, because specified CUDS objects are not in the container of the current CUDS object directly. """ uids = [arg.uid if isinstance(arg, Cuds) else arg for arg in args] # Get mapping from uids to connecting relationships _, relationship_mapping = self._get(*uids, rel=rel, oclass=oclass, return_mapping=True) if not relationship_mapping: raise RuntimeError("Did not remove any Cuds object, " "because none matched your filter.") uid_relationships = list(relationship_mapping.items()) # load all the neighbors to delete and remove inverse relationship neighbors = self.session.load(*[uid for uid, _ in uid_relationships]) for uid_relationship, neighbor in zip(uid_relationships, neighbors): uid, relationships = uid_relationship for relationship in relationships: self._remove_direct(relationship, uid) neighbor._remove_inverse(relationship, self.uid) def iter(self, *uids: uuid.UUID, rel: OntologyRelationship = cuba.activeRelationship, oclass: OntologyClass = None, return_rel: bool = False) -> Iterator["Cuds"]: """Iterate over the contained elements. Only iterate over objects of a given type, uid or oclass. Expected calls are iter(), iter(*uids), iter(rel), iter(oclass), iter(*uids, rel), iter(rel, oclass). If uids are specified: The position of each element in the result is determined by to the position of the corresponding uid in the given list of uids. In this case, the result can contain None values if a given uid is not a child of this cuds_object. If no uids are specified: The result is ordered randomly. Args: uids (uuid.UUID): UUIDs of the elements. rel (OntologyRelationship, optional): Only return cuds_object which are connected by subclass of given relationship. Defaults to cuba.activeRelationship. oclass (OntologyClass, optional): Only return elements which are a subclass of the given ontology class. Defaults to None. return_rel (bool, optional): Whether to return the connecting relationship. Defaults to False. Returns: Iterator[Cuds]: The queried objects. """ if return_rel: collected_uids, mapping = self._get(*uids, rel=rel, oclass=oclass, return_mapping=True) else: collected_uids = self._get(*uids, rel=rel, oclass=oclass) result = self._load_cuds_objects(collected_uids) for r in result: if not return_rel: yield r else: yield from ((r, m) for m in mapping[r.uid]) def _recursive_store(self, new_cuds_object, old_cuds_object=None): """Recursively store cuds_object and all its children. One-way relationships and dangling references are fixed. Args: new_cuds_object (Cuds): The Cuds object to store recursively. old_cuds_object (Cuds, optional): The old version of the CUDS object. Defaults to None. Returns: Cuds: The added CUDS object. """ # add new_cuds_object to self and replace old_cuds_object queue = [(self, new_cuds_object, old_cuds_object)] uids_stored = {new_cuds_object.uid, self.uid} missing = dict() result = None while queue: # Store copy in registry add_to, new_cuds_object, old_cuds_object = queue.pop(0) if new_cuds_object.uid in missing: del missing[new_cuds_object.uid] old_cuds_object = clone_cuds_object(old_cuds_object) new_child_getter = new_cuds_object new_cuds_object = create_from_cuds_object(new_cuds_object, add_to.session) # fix the connections to the neighbors add_to._fix_neighbors(new_cuds_object, old_cuds_object, add_to.session, missing) result = result or new_cuds_object for outgoing_rel in new_cuds_object._neighbors: # do not recursively add parents if not outgoing_rel.is_subclass_of(cuba.activeRelationship): continue # add children not already added for child_uid in new_cuds_object._neighbors[outgoing_rel]: if child_uid not in uids_stored: new_child = new_child_getter.get( child_uid, rel=outgoing_rel) old_child = self.session.load(child_uid).first() queue.append((new_cuds_object, new_child, old_child)) uids_stored.add(new_child.uid) # perform the deletion for uid in missing: for cuds_object, rel in missing[uid]: del cuds_object._neighbors[rel][uid] if not cuds_object._neighbors[rel]: del cuds_object._neighbors[rel] return result @staticmethod def _fix_neighbors(new_cuds_object, old_cuds_object, session, missing): """Fix all the connections of the neighbors of a Cuds object. That CUDS is going to be replaced later. Behavior when neighbors change: - new_cuds_object has parents, that weren't parents of old_cuds_object. - the parents are already stored in the session of old_cuds_object. - they are not already stored in the session of old_cuds_object. --> Add references between new_cuds_object and the parents that are already in the session. --> Delete references between new_cuds_object and parents that are not available. - new_cuds_object has children, that weren't children of old_cuds_object. --> add/update them recursively. - A parent of old_cuds_object is no longer a parent of new_cuds_object. --> Add a relationship between that parent and the new cuds_object. - A child of old_cuds_object is no longer a child of new_cuds_object. --> Remove the relationship between child and new_cuds_object. Args: new_cuds_object (Cuds): Cuds object that will replace the old one. old_cuds_object (Cuds, optional): Cuds object that will be replaced by a new one. Can be None if the new Cuds object does not replace any object. session (Session): The session where the adjustments should take place. missing (Dict): dictionary that will be populated with connections to objects, that are currently not available in the new session. The recursive add might add it later. """ old_cuds_object = old_cuds_object or None # get the parents that got parents after adding the new Cuds new_parent_diff = get_neighbor_diff( new_cuds_object, old_cuds_object, mode="non-active") # get the neighbors that were neighbors # before adding the new cuds_object old_neighbor_diff = get_neighbor_diff(old_cuds_object, new_cuds_object) # Load all the cuds_objects of the session cuds_objects = iter(session.load( *[uid for uid, _ in new_parent_diff + old_neighbor_diff])) # Perform the fixes Cuds._fix_new_parents(new_cuds_object=new_cuds_object, new_parents=cuds_objects, new_parent_diff=new_parent_diff, missing=missing) Cuds._fix_old_neighbors(new_cuds_object=new_cuds_object, old_cuds_object=old_cuds_object, old_neighbors=cuds_objects, old_neighbor_diff=old_neighbor_diff) @staticmethod def _fix_new_parents(new_cuds_object, new_parents, new_parent_diff, missing): """Fix the relationships of the added Cuds objects. Fixes relationships to the parents of the added Cuds object. Args: new_cuds_object (Cuds): The added Cuds object new_parents (Iterator[Cuds]): The new parents of the added CUDS object new_parent_diff (List[Tuple[UID, Relationship]]): The uids of the new parents and the relations they are connected with. missing (dict): dictionary that will be populated with connections to objects, that are currently not available in the new session. The recursive_add might add it later. """ # Iterate over the new parents for (parent_uid, relationship), parent in zip(new_parent_diff, new_parents): if relationship.is_subclass_of(cuba.activeRelationship): continue inverse = relationship.inverse # Delete connection to parent if parent is not present if parent is None: if parent_uid not in missing: missing[parent_uid] = list() missing[parent_uid].append((new_cuds_object, relationship)) continue # Add the inverse to the parent if inverse not in parent._neighbors: parent._neighbors[inverse] = {} parent._neighbors[inverse][new_cuds_object.uid] = \ new_cuds_object.oclasses @staticmethod def _fix_old_neighbors(new_cuds_object, old_cuds_object, old_neighbors, old_neighbor_diff): """Fix the relationships of the added Cuds objects. Fixes relationships to Cuds object that were previously neighbors. Args: new_cuds_object (Cuds): The added Cuds object old_cuds_object (Cuds, optional): The Cuds object that is going to be replaced old_neighbors (Iterator[Cuds]): The Cuds object that were neighbors before the replacement. old_neighbor_diff (List[Tuple[UID, Relationship]]): The uids of the old neighbors and the relations they are connected with. """ # iterate over all old neighbors. for (neighbor_uid, relationship), neighbor in zip(old_neighbor_diff, old_neighbors): inverse = relationship.inverse # delete the inverse if neighbors are children if relationship.is_subclass_of(cuba.activeRelationship): if inverse in neighbor._neighbors: neighbor._remove_direct(inverse, new_cuds_object.uid) # if neighbor is parent, add missing relationships else: if relationship not in new_cuds_object._neighbors: new_cuds_object._neighbors[relationship] = {} for (uid, oclasses), parent in \ zip(old_cuds_object._neighbors[relationship].items(), neighbor._neighbors): if parent is not None: new_cuds_object._neighbors[relationship][uid] = \ oclasses def _add_direct(self, cuds_object, rel): """Add an cuds_object with a specific relationship. Args: cuds_object (Cuds): CUDS object to be added rel (OntologyRelationship): relationship with the cuds_object to add. """ # First element, create set if rel not in self._neighbors.keys(): self._neighbors[rel] = {cuds_object.uid: cuds_object.oclasses} # Element not already there elif cuds_object.uid not in self._neighbors[rel]: self._neighbors[rel][cuds_object.uid] = cuds_object.oclasses def _add_inverse(self, cuds_object, rel): """Add the inverse relationship from self to cuds_object. Args: cuds_object (Cuds): CUDS object to connect with. rel (OntologyRelationship): direct relationship """ inverse_rel = rel.inverse self._add_direct(cuds_object, inverse_rel) def _get(self, *uids, rel=None, oclass=None, return_mapping=False): """Get the uid of contained elements that satisfy the filter. This filter consists of a certain type, uid or relationship. Expected calls are _get(), _get(*uids), _get(rel),_ get(oclass), _get(*uids, rel), _get(rel, oclass). If uids are specified, the result is the input, but non-available uids are replaced by None. Args: uids (UUID): UUIDs of the elements to get. rel (OntologyRelationship, optional): Only return CUDS objects connected with a subclass of relationship. Defaults to None. oclass (OntologyClass, optional): Only return CUDS objects of a subclass of this ontology class. Defaults to None. return_mapping (bool, optional): Whether to return a mapping from uids to relationships, that connect self with the uid. Defaults to False. Raises: TypeError: Specified both uids and oclass. ValueError: Wrong type of argument. Returns: List[UUID] (+ Dict[UUID, Set[Relationship]]): list of uids, or None, if not found. (+ Mapping from UUIDs to relationships, which connect self to the respective Cuds object.) """ if uids and oclass is not None: raise TypeError("Do not specify both uids and oclass") if rel is not None and not isinstance(rel, OntologyRelationship): raise ValueError("Found object of type %s passed to argument rel. " "Should be an OntologyRelationship." % type(rel)) if oclass is not None and not isinstance(oclass, OntologyClass): raise ValueError("Found object of type %s passed to argument " "oclass. Should be an OntologyClass." % type(oclass)) if uids: check_arguments(uuid.UUID, *uids) self.session._notify_read(self) # consider either given relationship and subclasses # or all relationships. consider_relationships = set(self._neighbors.keys()) if rel: consider_relationships &= set(rel.subclasses) consider_relationships = list(consider_relationships) # return empty list if no element of given relationship is available. if not consider_relationships and not return_mapping: return [] if not uids else [None] * len(uids) elif not consider_relationships: return ([], dict()) if not uids else ([None] * len(uids), dict()) if uids: return self._get_by_uids(uids, consider_relationships, return_mapping=return_mapping) return self._get_by_oclass(oclass, consider_relationships, return_mapping=return_mapping) def _get_by_uids(self, uids, relationships, return_mapping): """Check for each given uid if it is connected by a given relationship. If not, replace it with None. Optionally return a mapping from uids to the set of relationships, which connect self and the cuds_object with the uid. Args: uids (List[UUID]): The uids to check. relationships (List[Relationship]): Only consider these relationships. return_mapping (bool): Wether to return a mapping from uids to relationships, that connect self with the uid. Returns: List[UUID] (+ Dict[UUID, Set[Relationship]]): list of found uids, None for not found UUIDs (+ Mapping from UUIDs to relationships, which connect self to the respective Cuds object.) """ not_found_uids = dict(enumerate(uids)) if uids else None relationship_mapping = dict() for relationship in relationships: # Uids are given. # Check which occur as object of current relation. found_uid_indexes = set() # we need to iterate over all uids for every # relationship if we compute a mapping iterator = enumerate(uids) if relationship_mapping \ else not_found_uids.items() for i, uid in iterator: if uid in self._neighbors[relationship]: found_uid_indexes.add(i) if uid not in relationship_mapping: relationship_mapping[uid] = set() relationship_mapping[uid].add(relationship) for i in found_uid_indexes: if i in not_found_uids: del not_found_uids[i] collected_uids = [(uid if i not in not_found_uids else None) for i, uid in enumerate(uids)] if return_mapping: return collected_uids, relationship_mapping return collected_uids def _get_by_oclass(self, oclass, relationships, return_mapping): """Get the cuds_objects with given oclass. Only return objects that are connected to self with any of the given relationships. Optionally return a mapping from uids to the set of relationships, which connect self and the cuds_objects with the uid. Args: oclass (OntologyClass, optional): Filter by the given OntologyClass. None means no filter. relationships (List[Relationship]): Filter by list of relationships. return_mapping (bool): whether to return a mapping from uids to relationships, that connect self with the uid. Returns: List[UUID] (+ Dict[UUID, Set[Relationship]]): The uids of the found CUDS objects (+ Mapping from uuid to set of relationsships that connect self with the respective cuds_object.) """ relationship_mapping = dict() for relationship in relationships: # Collect all uids who are object of the current relationship. # Possibly filter by OntologyClass. for uid, target_classes in self._neighbors[relationship].items(): if oclass is None or any(t.is_subclass_of(oclass) for t in target_classes): if uid not in relationship_mapping: relationship_mapping[uid] = set() relationship_mapping[uid].add(relationship) if return_mapping: return list(relationship_mapping.keys()), relationship_mapping return list(relationship_mapping.keys()) def _load_cuds_objects(self, uids): """Load the cuds_objects of the given uids from the session. Each in cuds_object is at the same position in the result as the corresponding uid in the given uid list. If the given uids contain None values, there will be None values at the same postion in the result. Args: uids (List[UUID]): The uids to fetch from the session. Yields: Cuds: The loaded cuds_objects """ without_none = [uid for uid in uids if uid is not None] cuds_objects = self.session.load(*without_none) for uid in uids: if uid is None: yield None else: try: yield next(cuds_objects) except StopIteration: return None def _remove_direct(self, relationship, uid): """Remove the direct relationship to the object with the given uid. Args: relationship (OntologyRelationship): The relationship to remove. uid (UUID): The uid to remove. """ del self._neighbors[relationship][uid] if not self._neighbors[relationship]: del self._neighbors[relationship] def _remove_inverse(self, relationship, uid): """Remove the inverse of the given relationship. Args: relationship (OntologyRelationship): The relationship to remove. uid (UUID): The uid to remove. """ inverse = relationship.inverse self._remove_direct(inverse, uid) def _check_valid_add(self, to_add, rel): return True # TODO def __str__(self) -> str: """Get a human readable string. Returns: str: string with the Ontology class and uid. """ return "%s: %s" % (self.oclass, self.uid) def __getattr__(self, name): """Set the attributes corresponding to ontology values. Args: name (str): The name of the attribute Raises: AttributeError: Unknown attribute name Returns: The value of the attribute: Any """ try: attr = self.oclass.get_attribute_by_argname(name) if self.session: self.session._notify_read(self) return self._graph.value(self.iri, attr.iri).toPython() except AttributeError as e: if ( # check if user calls session's methods on wrapper self.is_a(cuba.Wrapper) and self._session is not None and hasattr(self._session, name) ): logger.warning( "Trying to get non-defined attribute '%s' " "of wrapper CUDS object '%s'. Will return attribute of " "its session '%s' instead." % (name, self, self._session) ) return getattr(self._session, name) raise AttributeError(name) from e def __setattr__(self, name, new_value): """Set an attribute. Will notify the session of it corresponds to an ontology value. Args: name (str): The name of the attribute. new_value (Any): The new value. Raises: AttributeError: Unknown attribute name """ if name.startswith("_"): super().__setattr__(name, new_value) return attr = self.oclass.get_attribute_by_argname(name) if self.session: self.session._notify_read(self) self._graph.set(( self.iri, attr.iri, rdflib.Literal(attr.convert_to_datatype(new_value), datatype=attr.datatype) )) if self.session: self.session._notify_update(self) def __repr__(self) -> str: """Return a machine readable string that represents the cuds object. Returns: str: Machine readable string representation for Cuds. """ return "<%s: %s, %s: @%s>" % (self.oclass, self.uid, type(self.session).__name__, hex(id(self.session))) def __hash__(self) -> int: """Make Cuds objects hashable. Use the hash of the uid of the object Returns: int: unique hash """ return hash(self.uid) def __eq__(self, other): """Define which CUDS objects are treated as equal. Same Ontology class and same UUID. Args: other (Cuds): Instance to check. Returns: bool: True if they share the uid and class, False otherwise """ return isinstance(other, Cuds) and other.oclass == self.oclass \ and self.uid == other.uid def __getstate__(self): """Get the state for pickling or copying. Returns: Dict[str, Any]: The state of the object. Does not contain session. Contains the string of the OntologyClass. """ state = {k: v for k, v in self.__dict__.items() if k not in {"_session", "_graph"}} state["_graph"] = list(self.get_triples(include_neighbor_types=True)) return state def __setstate__(self, state): """Set the state for pickling or copying. Args: state (Dict[str, Any]): The state of the object. Does not contain session. Contains the string of the OntologyClass. """ state["_session"] = None g = rdflib.Graph() for triple in state["_graph"]: g.add(triple) state["_graph"] = g self.__dict__ = state
def test_contains(self): """Test containment.""" r = QueryResult(CoreSession(), iter(range(10))) self.assertIn(1, r) self.assertIn(9, r) self.assertNotIn(11, r)
def setUp(self): """Set up the testcases: Reset the session.""" from osp.core.cuds import Cuds from osp.core.session import CoreSession Cuds._session = CoreSession()