diff --git a/python/pyproject.toml b/python/pyproject.toml index e5953fe..c7be753 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "bento-meta" -version = "0.2.1" +version = "0.2.2" description = "Python drivers for Bento Metamodel Database" authors = [ { name="Mark A. Jensen", email = "mark.jensen@nih.gov"}, @@ -20,7 +20,7 @@ classifiers = [ [tool.poetry] name = "bento-meta" -version = "0.2.1" +version = "0.2.2" description = "Python drivers for Bento Metamodel Database" authors = [ "Mark A. Jensen ", diff --git a/python/scripts/make_model_changelog.py b/python/scripts/make_model_changelog.py index b6971a3..bd224fe 100644 --- a/python/scripts/make_model_changelog.py +++ b/python/scripts/make_model_changelog.py @@ -10,6 +10,7 @@ import click from bento_mdf.mdf import MDF from bento_meta.entity import Entity +from bento_meta.mdb.mdb import make_nanoid from bento_meta.model import Model from bento_meta.objects import Concept, Term, ValueSet from bento_meta.util.cypher.clauses import Create, Match, Merge, OnCreateSet, Statement @@ -111,6 +112,10 @@ def generate_cypher_to_add_relationship( """Generates cypher statement to create relationship from src to dst entity""" cypher_src = cypherize_entity(src) cypher_dst = cypherize_entity(dst) + # remove _commit attr from Term and VS ents + for cypher_ent in (cypher_src, cypher_dst): + if isinstance(cypher_ent, (Term, ValueSet)) and "_commit" in cypher_ent.props: + cypher_ent.props.pop("_commit") cypher_stmts["add_rels"].append( Statement( Match(cypher_src, cypher_dst), @@ -124,6 +129,7 @@ def process_tags(entity: Entity, cypher_stmts) -> None: if not entity.tags: return for tag in entity.tags.values(): + tag.nanoid = make_nanoid() generate_cypher_to_add_entity(tag, cypher_stmts) generate_cypher_to_add_relationship(entity, "has_tag", tag, cypher_stmts) diff --git a/python/src/bento_meta/entity.py b/python/src/bento_meta/entity.py index 49b14bc..c8d4e4e 100644 --- a/python/src/bento_meta/entity.py +++ b/python/src/bento_meta/entity.py @@ -7,13 +7,15 @@ * the `CollValue` class to manage collection-valued attributes, and * the `ArgError` exception. """ +from collections import UserDict + # from pdb import set_trace from warnings import warn -from collections import UserDict class ArgError(Exception): """Exception for method argument errors""" + pass @@ -32,6 +34,7 @@ class Entity(object): exceptions when attempts are made to access attributes that are not declared. """ + pvt_attr = [ "pvt", "neoid", @@ -41,7 +44,7 @@ class Entity(object): "mapspec", "belongs", ] - defaults = {}, + defaults = ({},) attspec_ = { "_id": "simple", "nanoid": "simple", @@ -57,9 +60,14 @@ class Entity(object): mapspec_ = { "label": None, "key": "_id", - "property": {"_id": "id", "desc": "desc", - "_from": "_from", "_to": "_to", - "_commit": "_commit", "nanoid": "nanoid"}, + "property": { + "_id": "id", + "desc": "desc", + "_from": "_from", + "_to": "_to", + "_commit": "_commit", + "nanoid": "nanoid", + }, "relationship": { "_next": {"rel": ":_next>", "end_cls": set()}, "_prev": {"rel": ":_prev>", "end_cls": set()}, @@ -143,7 +151,6 @@ def default(cls, propname): return cls.defaults[propname] else: return None - # @classmethod def get_by_id(self, id): @@ -151,11 +158,11 @@ def get_by_id(self, id): :param string id: value of id for desired object """ if self.object_map: - print(' > now in entity.get_by_id where self is {}'.format(self)) - print(' > and class is {}'.format(self.__class__)) + print(" > now in entity.get_by_id where self is {}".format(self)) + print(" > and class is {}".format(self.__class__)) return self.object_map.get_by_id(self, id) else: - print(' _NO_ cls.object_map detected') + print(" _NO_ cls.object_map detected") pass @property @@ -508,7 +515,7 @@ def str_for_obj(thing): def get_label(self) -> str: """returns type of entity as label""" - return self.__class__.__name__.lower() + return self.mapspec_["label"] def get_attr_dict(self): """ @@ -519,8 +526,12 @@ def get_attr_dict(self): """ attr_dict = {} for key, val in vars(self).items(): - if (val and val is not None and key != "pvt" and - isinstance(val, (str, int, float, complex, bool))): + if ( + val + and val is not None + and key != "pvt" + and isinstance(val, (str, int, float, complex, bool)) + ): attr_dict[key] = str(val) return attr_dict @@ -537,6 +548,7 @@ class CollValue(UserDict): :param owner: `Entity` object of which this collection is an attribute :param owner_key: the attribute name of this collection on the owner """ + def __init__(self, init=None, *, owner, owner_key): self.__dict__["__owner"] = owner self.__dict__["__owner_key"] = owner_key diff --git a/python/src/bento_meta/objects.py b/python/src/bento_meta/objects.py index b835479..14be94e 100644 --- a/python/src/bento_meta/objects.py +++ b/python/src/bento_meta/objects.py @@ -7,9 +7,12 @@ """ import sys + sys.path.append("..") from copy import deepcopy + from bento_meta.entity import Entity + # from pdb import set_trace @@ -50,7 +53,7 @@ class Node(Entity): "relationship": { "concept": {"rel": ":has_concept>", "end_cls": "Concept"}, "props": {"rel": ":has_property>", "end_cls": "Property"}, - "tags": {"rel": ":has_tag>", "end_cls":"Tag"} + "tags": {"rel": ":has_tag>", "end_cls": "Tag"}, }, } (attspec, _mapspec) = mergespec("Node", attspec_, mapspec_) @@ -61,6 +64,7 @@ def __init__(self, init=None): class Property(Entity): """Subclass that models a property of a node or relationship (edge).""" + pvt_attr = Entity.pvt_attr + ["value_types"] attspec_ = { "handle": "simple", @@ -90,12 +94,12 @@ class Property(Entity): "relationship": { "concept": {"rel": ":has_concept>", "end_cls": "Concept"}, "value_set": {"rel": ":has_value_set>", "end_cls": "ValueSet"}, - "tags": {"rel": ":has_tag>", "end_cls":"Tag"} + "tags": {"rel": ":has_tag>", "end_cls": "Tag"}, }, } (attspec, _mapspec) = mergespec("Property", attspec_, mapspec_) - defaults = { "value_domain":"TBD" } - + defaults = {"value_domain": "TBD"} + def __init__(self, init=None): super().__init__(init=init) self.value_types = [] @@ -121,6 +125,7 @@ def values(self): class Edge(Entity): """Subclass that models a relationship between model nodes.""" + defaults = { "multiplicity": "many_to_many", } @@ -150,11 +155,11 @@ class Edge(Entity): "dst": {"rel": ":has_dst>", "end_cls": "Node"}, "concept": {"rel": ":has_concept>", "end_cls": "Concept"}, "props": {"rel": ":has_property>", "end_cls": "Property"}, - "tags": {"rel": ":has_tag>", "end_cls":"Tag"} + "tags": {"rel": ":has_tag>", "end_cls": "Tag"}, }, } (attspec, _mapspec) = mergespec("Edge", attspec_, mapspec_) - + def __init__(self, init=None): super().__init__(init=init) @@ -166,10 +171,6 @@ def triplet(self): if self.handle and self.src and self.dst: return (self.handle, self.src.handle, self.dst.handle) - def get_label(self) -> str: - """returns type of entity as label""" - return "relationship" - class Term(Entity): """Subclass that models a term from a terminology.""" @@ -204,7 +205,7 @@ class Term(Entity): "relationship": { "concept": {"rel": ":represents>", "end_cls": "Concept"}, "origin": {"rel": ":has_origin>", "end_cls": "Origin"}, - "tags": {"rel": ":has_tag>", "end_cls":"Tag"} + "tags": {"rel": ":has_tag>", "end_cls": "Tag"}, }, } (attspec, _mapspec) = mergespec("Term", attspec_, mapspec_) @@ -220,6 +221,7 @@ class ValueSet(Entity): """Subclass that models an enumerated set of :class:`Property` values. Essentially a container for :class:`Term` instances. """ + attspec_ = { "handle": "simple", "nanoid": "simple", @@ -230,12 +232,16 @@ class ValueSet(Entity): } mapspec_ = { "label": "value_set", - "property": {"handle": "handle", "url": "url", "nanoid": "nanoid",}, + "property": { + "handle": "handle", + "url": "url", + "nanoid": "nanoid", + }, "relationship": { "prop": {"rel": "<:has_value_set", "end_cls": "Property"}, "terms": {"rel": ":has_term>", "end_cls": "Term"}, "origin": {"rel": ":has_origin>", "end_cls": "Origin"}, - "tags": {"rel": ":has_tag>", "end_cls":"Tag"} + "tags": {"rel": ":has_tag>", "end_cls": "Tag"}, }, } (attspec, _mapspec) = mergespec("ValueSet", attspec_, mapspec_) @@ -267,7 +273,7 @@ class Concept(Entity): "label": "concept", "relationship": { "terms": {"rel": "<:represents", "end_cls": "Term"}, - "tags": {"rel": ":has_tag>", "end_cls":"Tag"} + "tags": {"rel": ":has_tag>", "end_cls": "Tag"}, }, } (attspec, _mapspec) = mergespec("Concept", attspec_, mapspec_) @@ -275,12 +281,11 @@ class Concept(Entity): def __init__(self, init=None): super().__init__(init=init) + class Predicate(Entity): """Subclass that models a semantic link between concepts.""" - attspec_ = { - "handle": "simple", - "subject": "object", - "object": "object"} + + attspec_ = {"handle": "simple", "subject": "object", "object": "object"} mapspec_ = { "label": "predicate", "key": "handle", @@ -290,19 +295,24 @@ class Predicate(Entity): "relationship": { "subject": {"rel": ":has_subject>", "end_cls": "Concept"}, "object": {"rel": ":has_object>", "end_cls": "Concept"}, - "tags": {"rel": ":has_tag>", "end_cls":"Tag"} + "tags": {"rel": ":has_tag>", "end_cls": "Tag"}, }, } (attspec, _mapspec) = mergespec("Predicate", attspec_, mapspec_) def __init__(self, init=None): super().__init__(init=init) - + class Origin(Entity): """Subclass that models a :class:`Term` 's authoritative source.""" - attspec_ = {"url": "simple", "is_external": "simple", "name": "simple", "nanoid": "simple",} + attspec_ = { + "url": "simple", + "is_external": "simple", + "name": "simple", + "nanoid": "simple", + } mapspec_ = { "label": "origin", "key": "name", @@ -312,9 +322,7 @@ class Origin(Entity): "is_external": "is_external", "nanoid": "nanoid", }, - "relationship": { - "tags": {"rel": ":has_tag>", "end_cls":"Tag"} - } + "relationship": {"tags": {"rel": ":has_tag>", "end_cls": "Tag"}}, } (attspec, _mapspec) = mergespec("Origin", attspec_, mapspec_) @@ -325,7 +333,7 @@ def __init__(self, init=None): class Tag(Entity): """Subclass that allows simple key-value tagging of a model at arbitrary points.""" - attspec_ = {"key":"simple", "value": "simple"} + attspec_ = {"key": "simple", "value": "simple"} mapspec_ = { "label": "tag", "key": "key", @@ -335,3 +343,27 @@ class Tag(Entity): def __init__(self, init=None): super().__init__(init=init) + + +class Model(Entity): + """Subclass with information regarding data model.""" + + attspec_ = { + "handle": "simple", + "name": "simple", + "repository": "simple", + "nanoid": "simple", + } + mapspec_ = { + "label": "model", + "key": "handle", + "property": { + "handle": "handle", + "name": "name", + "repository": "repository", + }, + } + (attspec, _mapspec) = mergespec("Model", attspec_, mapspec_) + + def __init__(self, init=None): + super().__init__(init=init) diff --git a/python/tests/samples/test_changelog.ini b/python/tests/samples/test_changelog.ini index 9896818..5db2710 100644 --- a/python/tests/samples/test_changelog.ini +++ b/python/tests/samples/test_changelog.ini @@ -1,3 +1,3 @@ [changelog] -changeset_id = 6593 +changeset_id = 6855 diff --git a/python/tests/test_002objects.py b/python/tests/test_002objects.py index 7bb68fa..c2bee65 100644 --- a/python/tests/test_002objects.py +++ b/python/tests/test_002objects.py @@ -1,104 +1,122 @@ -import re import sys -from pdb import set_trace -sys.path.insert(0,'.') -sys.path.insert(0,'..') -import pytest + +sys.path.insert(0, ".") +sys.path.insert(0, "..") from bento_meta.entity import Entity -from bento_meta.objects import Node, Property, Edge, Term, ValueSet, Concept, Predicate, Origin, Tag +from bento_meta.objects import ( + Concept, + Edge, + Node, + Origin, + Predicate, + Property, + Tag, + Term, + ValueSet, +) + def test_create_objects(): - for cls in [Node,Property,Edge,Term,ValueSet,Concept,Origin,Tag]: - n = cls() - assert n - assert isinstance(n, Entity) + for cls in [Node, Property, Edge, Term, ValueSet, Concept, Origin, Tag]: + n = cls() + assert n + assert isinstance(n, Entity) + def test_init_and_link_objects(): - case = Node({"model":"test","handle":"case"}) - assert case - assert case.model == "test" - assert case.handle == "case" - sample = Node({"model":"test","handle":"sample"}) - assert sample - of_sample = Edge({"model":"test","handle":"of_sample"}) - assert of_sample - assert of_sample.model == "test" - assert of_sample.handle == "of_sample" - of_sample.src = sample - of_sample.dst = case - assert of_sample.dst == case - assert of_sample.src == sample - term = Term({"value":"sample"}) - concept = Concept(); - term.concept = concept - other_concept = Concept() - concept.terms["sample"]=term - sample.concept = concept - [o] = [x for x in term.belongs.values()] - assert o == concept - assert of_sample.src.concept.terms["sample"].value == "sample" - pred = Predicate({"subject":concept, "object":other_concept, "handle":"isa"}) - assert type(pred.subject) == Concept - assert type(pred.object) == Concept + case = Node({"model": "test", "handle": "case"}) + assert case + assert case.model == "test" + assert case.handle == "case" + sample = Node({"model": "test", "handle": "sample"}) + assert sample + of_sample = Edge({"model": "test", "handle": "of_sample"}) + assert of_sample + assert of_sample.model == "test" + assert of_sample.handle == "of_sample" + of_sample.src = sample + of_sample.dst = case + assert of_sample.dst == case + assert of_sample.src == sample + term = Term({"value": "sample"}) + concept = Concept() + term.concept = concept + other_concept = Concept() + concept.terms["sample"] = term + sample.concept = concept + [o] = [x for x in term.belongs.values()] + assert o == concept + assert of_sample.src.concept.terms["sample"].value == "sample" + pred = Predicate({"subject": concept, "object": other_concept, "handle": "isa"}) + assert type(pred.subject) == Concept + assert type(pred.object) == Concept + def test_tags_on_objects(): - nodeTag = Tag({"key":"name","value":"Neddy"}) - relnTag = Tag({"key":"name","value":"Robby"}) - conceptTag = Tag({"key":"name","value":"Catty"}) - conceptTag2 = Tag({"key":"aka","value":"Jehoshaphat"}) - termTag = Tag({"key":"name","value":"Termy"}) - propTag = Tag({"key":"name","value":"Puppy"}) - - case = Node({"model":"test","handle":"case"}) - of_sample = Edge({"model":"test","handle":"of_sample"}) - sample = Node({"model":"test","handle":"sample"}) - of_sample.src = sample - of_sample.dst = case - term = Term({"value":"sample"}) - concept = Concept(); - term.concept = concept - concept.terms["sample"]=term - sample.concept = concept - sample.props['this'] = Property({"that":"this"}) + nodeTag = Tag({"key": "name", "value": "Neddy"}) + relnTag = Tag({"key": "name", "value": "Robby"}) + conceptTag = Tag({"key": "name", "value": "Catty"}) + conceptTag2 = Tag({"key": "aka", "value": "Jehoshaphat"}) + termTag = Tag({"key": "name", "value": "Termy"}) + propTag = Tag({"key": "name", "value": "Puppy"}) - case.tags[nodeTag.key] = nodeTag - of_sample.tags[relnTag.key] = relnTag - term.tags[termTag.key] = termTag - concept.tags[conceptTag.key] = conceptTag - concept.tags[conceptTag2.key] = conceptTag2 - sample.props['this'].tags['name'] = propTag + case = Node({"model": "test", "handle": "case"}) + of_sample = Edge({"model": "test", "handle": "of_sample"}) + sample = Node({"model": "test", "handle": "sample"}) + of_sample.src = sample + of_sample.dst = case + term = Term({"value": "sample"}) + concept = Concept() + term.concept = concept + concept.terms["sample"] = term + sample.concept = concept + sample.props["this"] = Property({"that": "this"}) - names = [x.tags['name'].value for x in [case,of_sample,term,concept,sample.props['this']]]; - assert names == ["Neddy","Robby","Termy","Catty","Puppy"] - assert concept.tags['aka'].value == "Jehoshaphat" + case.tags[nodeTag.key] = nodeTag + of_sample.tags[relnTag.key] = relnTag + term.tags[termTag.key] = termTag + concept.tags[conceptTag.key] = conceptTag + concept.tags[conceptTag2.key] = conceptTag2 + sample.props["this"].tags["name"] = propTag + + names = [ + x.tags["name"].value + for x in [case, of_sample, term, concept, sample.props["this"]] + ] + assert names == ["Neddy", "Robby", "Termy", "Catty", "Puppy"] + assert concept.tags["aka"].value == "Jehoshaphat" -def test_some_object_methods(): - p = Property({"handle":"complaint"}) - assert p - t = Term({"value":"halitosis"}) - assert t - u = Term({"value":"ptomaine"}) - assert u - vs = ValueSet({"_id":"1"}) - assert vs - p.value_set = vs - p.value_types.append("glarp") - assert "glarp" in p.value_types - vs.terms['ptomaine'] = u - assert p.terms['ptomaine'].value == 'ptomaine' - p.terms['halitosis'] = t - assert vs.terms['halitosis'].value == 'halitosis' - vals = p.values - assert isinstance(vals,list) - assert 'ptomaine' in vals - assert 'halitosis' in vals - s = Node({'handle':"case"}) - assert s - d = Node({'handle':"cohort"}) - assert d - e = Edge({'handle':"member_of",'src':s,'dst':d}) - assert e - assert e.triplet == ('member_of','case','cohort') - - +def test_some_object_methods(): + p = Property({"handle": "complaint"}) + assert p + t = Term({"value": "halitosis"}) + assert t + u = Term({"value": "ptomaine"}) + assert u + vs = ValueSet({"_id": "1"}) + assert vs + p.value_set = vs + p.value_types.append("glarp") + assert "glarp" in p.value_types + vs.terms["ptomaine"] = u + assert p.terms["ptomaine"].value == "ptomaine" + p.terms["halitosis"] = t + assert vs.terms["halitosis"].value == "halitosis" + vals = p.values + assert isinstance(vals, list) + assert "ptomaine" in vals + assert "halitosis" in vals + s = Node({"handle": "case"}) + assert s + d = Node({"handle": "cohort"}) + assert d + e = Edge({"handle": "member_of", "src": s, "dst": d}) + assert e + assert e.triplet == ("member_of", "case", "cohort") + # test get_label() method + assert p.get_label() == "property" + assert t.get_label() == "term" + assert vs.get_label() == "value_set" + assert s.get_label() == "node" + assert e.get_label() == "relationship"