From 33118e695555275fcb366148b347eea04c72e6a1 Mon Sep 17 00:00:00 2001 From: Johannes Roos Date: Mon, 19 Aug 2024 22:57:25 +0200 Subject: [PATCH] added age <3 --- core/age.py | 99 +++++++++++++++++++++++++++++++ core/models.py | 33 +++++++++++ core/mutations/entity.py | 11 +++- core/mutations/entity_kind.py | 12 ++++ core/mutations/entity_relation.py | 18 +++++- core/mutations/ontology.py | 24 +++++++- core/queries/entity_graph.py | 7 ++- mikro_server/settings.py | 8 +++ 8 files changed, 203 insertions(+), 9 deletions(-) create mode 100644 core/age.py diff --git a/core/age.py b/core/age.py new file mode 100644 index 0000000..23e9f4d --- /dev/null +++ b/core/age.py @@ -0,0 +1,99 @@ +from contextlib import contextmanager +from django.db import connections +from core import models + + +@contextmanager +def graph_cursor(): + with connections["default"].cursor() as cursor: + cursor.execute("LOAD 'age';") + cursor.execute('SET search_path = ag_catalog, "$user", public') + yield cursor + + +def create_age_ontology(name: str): + with graph_cursor() as cursor: + cursor.execute( + "SELECT EXISTS(SELECT 1 FROM ag_catalog.ag_graph WHERE name = %s);", + [name] + ) + exists = cursor.fetchone()[0] + if exists: + return exists + else: + cursor.execute( + "SELECT create_graph(%s);", + [name] + ) + print(cursor.fetchone()) + + +def create_age_entity_kind(graph_name, kind_name): + with graph_cursor() as cursor: + try: + cursor.execute( + "SELECT create_vlabel(%s, %s);", + (graph_name, kind_name) + ) + print(cursor.fetchone()) + except Exception as e: + print(e) + + +def create_age_relation_kind(graph_name, kind_name): + with graph_cursor() as cursor: + try: + cursor.execute( + "SELECT create_elabel(%s, %s);", + (graph_name, kind_name) + ) + print(cursor.fetchone()) + except Exception as e: + print(e) + + +def create_age_entity(graph_name, kind_name, entity_id): + with graph_cursor() as cursor: + cursor.execute( + f""" + SELECT * + FROM cypher(%s, $$ + CREATE (n:{kind_name}) + RETURN n.id + $$) as (id agtype); + """, + (graph_name,) + ) + print(cursor.fetchone()) + + + +def create_age_relation(relation_kind: models.EntityRelationKind, left_id, right_id): + with graph_cursor() as cursor: + cursor.execute( + f""" + SELECT * + FROM cypher(%s, $$ + MATCH (a:{relation_kind.left_kind.age_name} {{id: %s}}) + MATCH (b:{relation_kind.right_kind.age_name} {{id: %s}}) + CREATE (a)-[:{relation_kind.age_name}]->(b) + $$) as (id agtype); + """, + (relation_kind.kind.ontology.age_name, left_id, right_id) + ) + print(cursor.fetchone()) + + +def select_all_entities(graph_name): + with graph_cursor() as cursor: + cursor.execute( + f""" + SELECT * + FROM cypher(%s, $$ + MATCH (n) + RETURN n.id + $$) as (id agtype); + """, + [graph_name] + ) + print(cursor.fetchall()) \ No newline at end of file diff --git a/core/models.py b/core/models.py index 70a497d..b96fb26 100644 --- a/core/models.py +++ b/core/models.py @@ -354,6 +354,25 @@ class ProtocolStep(models.Model): + + + +class Reagent(models.Model): + entity_kind = models.ForeignKey("EntityKind", on_delete=models.CASCADE, related_name="reagents", help_text="The associated entity") + protocol = models.ForeignKey( + Protocol, + on_delete=models.CASCADE, + null=True, + blank=True, + related_name="reagents", + ) + volume = models.FloatField( + help_text="The volume of the reagent in the protocol", + null=True, + blank=True, + ) + + class Specimen(models.Model): entity = models.ForeignKey("Entity", on_delete=models.CASCADE, related_name="specimens", help_text="The associated entity") protocol = models.ForeignKey( @@ -1066,6 +1085,11 @@ class Ontology(models.Model): def __str__(self) -> str: return self.name + + + @property + def age_name(self) -> str: + return self.name.replace(" ", "_").lower() @@ -1093,6 +1117,10 @@ class EntityRelationKind(models.Model): help_text="The kind of the relation", ) + @property + def age_name(self) -> str: + return self.kind.label.replace(" ", "_").lower() + "_relation" + def random_color(): @@ -1155,6 +1183,11 @@ def create_entity(self, group, name: str = None, instance_kind: str = None, met def rgb_color_string(self) -> str: return f"rgb({self.color[0]}, {self.color[1]}, {self.color[2]})" + + @property + def age_name(self) -> str: + return self.label.replace(" ", "_").lower() + class EntityGroup(models.Model): """An EntityGroup is a collection of Entities. diff --git a/core/mutations/entity.py b/core/mutations/entity.py index 0457cce..a312742 100644 --- a/core/mutations/entity.py +++ b/core/mutations/entity.py @@ -1,6 +1,6 @@ from kante.types import Info import strawberry -from core import types, models +from core import types, models, age import uuid @@ -35,17 +35,22 @@ def create_entity( input_kind = models.EntityKind.objects.get(id=input.kind) + id = uuid.uuid4().hex item, _ = models.Entity.objects.get_or_create( - name=input.name or uuid.uuid4().hex, + name=id, group=group, kind=input_kind, defaults=dict( - name=input.name or uuid.uuid4().hex, + name=id, instance_kind=input.instance_kind, ) ) + age.create_age_entity(input_kind.ontology.age_name, input_kind.age_name, id) + + + assert item.kind.id == input_kind.id, f"Entity kind mismatch {item.kind} vs {input_kind}" diff --git a/core/mutations/entity_kind.py b/core/mutations/entity_kind.py index 3923db4..5f04ac6 100644 --- a/core/mutations/entity_kind.py +++ b/core/mutations/entity_kind.py @@ -1,6 +1,7 @@ from kante.types import Info import strawberry from core import types, models +from core import age @strawberry.input @@ -35,6 +36,10 @@ def create_entity_kind( description="Default ontology for {}".format(user.username),) ) + age.create_age_ontology(ontology.age_name) + + + if input.color: assert len(input.color) == 3 or len(input.color) == 4, "Color must be a list of 3 or 4 values RGBA" @@ -46,6 +51,13 @@ def create_entity_kind( color=input.color or models.random_color(), ) ) + + age.create_age_entity_kind(ontology.age_name, item.age_name) + + + + + return item diff --git a/core/mutations/entity_relation.py b/core/mutations/entity_relation.py index 868f2be..40f60fc 100644 --- a/core/mutations/entity_relation.py +++ b/core/mutations/entity_relation.py @@ -1,6 +1,6 @@ from kante.types import Info import strawberry -from core import types, models +from core import types, models, age @strawberry.input @@ -32,6 +32,12 @@ def create_entity_relation_kind( right_kind=models.EntityKind.objects.get(id=input.right_kind), kind=models.EntityKind.objects.get(id=input.kind), ) + + + age.create_age_relation_kind(item.kind.ontology.age_name, item.age_name) + + + return item @@ -40,11 +46,19 @@ def create_entity_relation( info: Info, input: EntityRelationInput, ) -> types.EntityRelation: + + kind = models.EntityRelationKind.objects.get(id=input.kind) item, _ = models.EntityRelation.objects.get_or_create( left=models.Entity.objects.get(id=input.left), right=models.Entity.objects.get(id=input.right), - kind=models.EntityRelationKind.objects.get(id=input.kind), + kind=kind ) + + age.create_age_relation(kind, input.left, input.right) + + + + return item diff --git a/core/mutations/ontology.py b/core/mutations/ontology.py index be27abb..bb42a2c 100644 --- a/core/mutations/ontology.py +++ b/core/mutations/ontology.py @@ -1,6 +1,9 @@ from kante.types import Info import strawberry -from core import types, models +from core import types, models, age +from django.db import connections +from contextlib import contextmanager + @strawberry.input @@ -15,6 +18,9 @@ class DeleteOntologyInput: id: strawberry.ID +def to_snake_case(string): + return string.replace(" ", "_").lower() + def create_ontology( info: Info, @@ -22,11 +28,13 @@ def create_ontology( ) -> types.Ontology: item, _ = models.Ontology.objects.update_or_create( - name=input.name, + name=to_snake_case(input.name), defaults=dict( description=input.description or "", purl=input.purl) ) + + age.create_age_ontology(item.name) return item @@ -35,7 +43,17 @@ def delete_ontology( info: Info, input: DeleteOntologyInput, ) -> strawberry.ID: - item = models.Entity.objects.get(id=input.id) + item = models.Ontology.objects.get(id=input.id) + + + with graph_cursor() as cursor: + cursor.execute( + "SELECT delete_graph(%s);", + [item.name] + ) + print(cursor.fetchone()) + item.delete() + return input.id diff --git a/core/queries/entity_graph.py b/core/queries/entity_graph.py index cea379b..8861d03 100644 --- a/core/queries/entity_graph.py +++ b/core/queries/entity_graph.py @@ -1,5 +1,5 @@ import strawberry -from core import models +from core import models, age @strawberry.type @@ -52,6 +52,11 @@ def entity_graph(id: strawberry.ID) -> EntityGraph: entity = models.Entity.objects.get(id=id) + print(age.select_all_entities(entity.kind.ontology.age_name)) + + + + def parse_entity(entity: models.Entity, is_root=False): node = EntityNode(id=entity.id, subtitle=entity.name, metrics=[], label=entity.kind.label, is_root=is_root, color=entity.kind.rgb_color_string) diff --git a/mikro_server/settings.py b/mikro_server/settings.py index 3a68e3d..46317ac 100644 --- a/mikro_server/settings.py +++ b/mikro_server/settings.py @@ -148,6 +148,14 @@ "PASSWORD": conf.db.password, "HOST": conf.db.host, "PORT": conf.db.port, + }, + "graph": { + "ENGINE": conf.db.engine, + "NAME": "mikro_graph", + "USER": conf.db.username, + "PASSWORD": conf.db.password, + "HOST": "age", + "PORT": conf.db.port, } }