Skip to content

Commit

Permalink
feat(*): add Model object class
Browse files Browse the repository at this point in the history
- Add Model class to objects.py #42
- edit get_label method of Entity class so that it returns the label provided in the object class's mapspec rather than from the object class's name
- add test for get_label method to test_002objects.py
- bump package version to 0.2.2
- make_model_changelog bugfixes to prevent duplicate relationship creation if terms or value sets are shared by multiple models and to add nanoid to tags before generating cypher so that multiple relationships aren't created from taggable entities to each generated tag
- formatting
  • Loading branch information
nelsonwmoore committed Jul 20, 2023
1 parent 6fe7313 commit d995d9e
Show file tree
Hide file tree
Showing 6 changed files with 200 additions and 132 deletions.
4 changes: 2 additions & 2 deletions python/pyproject.toml
Original file line number Diff line number Diff line change
@@ -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 = "[email protected]"},
Expand All @@ -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 <[email protected]>",
Expand Down
6 changes: 6 additions & 0 deletions python/scripts/make_model_changelog.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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),
Expand All @@ -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)

Expand Down
36 changes: 24 additions & 12 deletions python/src/bento_meta/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand All @@ -32,6 +34,7 @@ class Entity(object):
exceptions when attempts are made to access attributes that are not
declared.
"""

pvt_attr = [
"pvt",
"neoid",
Expand All @@ -41,7 +44,7 @@ class Entity(object):
"mapspec",
"belongs",
]
defaults = {},
defaults = ({},)
attspec_ = {
"_id": "simple",
"nanoid": "simple",
Expand All @@ -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()},
Expand Down Expand Up @@ -143,19 +151,18 @@ def default(cls, propname):
return cls.defaults[propname]
else:
return None


# @classmethod
def get_by_id(self, id):
"""Get an object from the db with the id attribute (not the Neo4j id). Returns a new object.
: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
Expand Down Expand Up @@ -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):
"""
Expand All @@ -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

Expand All @@ -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
Expand Down
82 changes: 57 additions & 25 deletions python/src/bento_meta/objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,12 @@
"""
import sys

sys.path.append("..")
from copy import deepcopy

from bento_meta.entity import Entity

# from pdb import set_trace


Expand Down Expand Up @@ -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_)
Expand All @@ -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",
Expand Down Expand Up @@ -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 = []
Expand All @@ -121,6 +125,7 @@ def values(self):

class Edge(Entity):
"""Subclass that models a relationship between model nodes."""

defaults = {
"multiplicity": "many_to_many",
}
Expand Down Expand Up @@ -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)

Expand All @@ -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."""
Expand Down Expand Up @@ -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_)
Expand All @@ -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",
Expand All @@ -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_)
Expand Down Expand Up @@ -267,20 +273,19 @@ 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_)

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",
Expand All @@ -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",
Expand All @@ -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_)

Expand All @@ -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",
Expand All @@ -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)
2 changes: 1 addition & 1 deletion python/tests/samples/test_changelog.ini
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
[changelog]
changeset_id = 6593
changeset_id = 6855

Loading

0 comments on commit d995d9e

Please sign in to comment.