def add_terms(self, prop, *terms): """Add a list of :class:`Term` and/or strings to a :class:`Property` with a value domain of ``value_set`` :param Property prop: :class:`Property` to modify :param list terms: A list of :class:`Term` instances and/or str :class:`Term` instances are created for strings; `Term.value` is set to the string. """ if not isinstance(prop, Property): raise ArgError("arg1 must be Property") if not re.match("value_set|enum", prop.value_domain): raise AttributeError( "Property value domain is not value_set or enum, can't add terms" ) if not prop.value_set: warn("Creating ValueSet object for Property " + prop.handle) prop.value_set = ValueSet({"prop": prop, "_id": str(uuid4())}) prop.value_set.handle = self.handle + prop.value_set._id[0:8] for t in terms: if isinstance(t, str): warn("Creating Term object for string '{term}'".format(term=t)) t = Term({"value": t}) elif not isinstance(t, Term): raise ArgError( "encountered arg that was not a str or Term object") prop.value_set.terms[t.value] = t
def add_edge(self, edge=None): """Add an :class:`Edge` to the model. :param Edge edge: A :class:`Edge` instance The model attribute of ``edge`` is set to `Model.handle` """ if not edge: raise ArgError("arg must be Edge, dict, or graph.Node") if isinstance(edge, (dict, neo4j.graph.Node)): edge = Edge(edge) if not edge.src or not edge.dst: raise ArgError("edge must have both src and dst set") if not edge.model: edge.model = self.handle if not self.contains(edge.src): warn("Edge source node not yet in model; adding it") self.add_node(edge.src) if not self.contains(edge.dst): warn("Edge destination node not yet in model; adding it") self.add_node(edge.dst) for p in edge.props.values(): self.add_prop(edge, p) self.edges[edge.triplet] = edge return edge
def add_prop(self, ent, prop=None): """Add a :class:`Property` to the model. :param Node|Edge ent: Attach ``prop`` to this entity :param Property prop: A :class:`Property` instance The model attribute of ``prop`` is set to `Model.handle`. Within a model, :class:`Property` entities are unique with respect to their handle (but can be reused). This method will look for an existing property within the model with the given handle, and add an item to Model.props pointing to it if found. """ if not isinstance(ent, (Node, Edge)): raise ArgError("arg 1 must be Node or Edge") if not prop: raise ArgError("arg 2 must be Property, dict, or graph.Node") if isinstance(prop, (dict, neo4j.graph.Node)): handle = prop["handle"] pkeys = [x for x in self.props if handle in x] if pkeys: prop = self.props[pkeys.pop()] else: prop = Property(prop) if not prop.model: prop.model = self.handle key = [ent.handle] if isinstance(ent, Node) else list(ent.triplet) key.append(prop.handle) ent.props[getattr(prop, type(prop).mapspec()["key"])] = prop self.props[tuple(key)] = prop return prop
def rm_node(self, node): """Remove a :class:`Node` from the Model instance. :param Node node: Node to be removed Note: A node can't be removed if it is participating in an edge (i.e., if the node is some edge's src or dst attribute) *Clarify what happens in the Model object, in the database when versioning is off, in the database when versioning is on* """ if not isinstance(node, Node): raise ArgError("arg must be a Node object") if not self.contains(node): warn("node '{node}' not contained in model '{model}'".format( node=node.handle, model=self.handle)) return if self.edges_by_src(node) or self.edges_by_dst(node): raise ValueError( "can't remove node '{node}', it is participating in edges". format(node=node.handle)) for p in node.props: try: del self.props[(node.handle, p.handle)] except: pass del self.nodes[node.handle] self.removed_entities.append(node) return node
def load_yaml(self): """Load YAML files or open file handles specified in constructor""" yloader = yaml.loader.Loader for f in self.files: if isinstance(f, str): if re.match("(?:file|https?)://", f): response = requests.get(f) if not response.ok: raise ArgError( "Fetching url {} returned code {}".format( response.url, response.status_code)) response.encoding = "utf8" f = response.text else: f = open(f, "r") try: yml = yaml.load(f, Loader=yloader) self.schema.update(yml) except ConstructorError as ce: print("YAML constructor failed in '{fn}':\n{e}".format( fn=f.name, e=ce)) raise ce except ParserError as pe: print("YAML parser failed in '{fn}':\n{e}".format(fn=f.name, e=pe)) raise pe except Exception: raise
def rm_edge(self, edge): """Remove an :class:`Edge` instance from the Model instance. :param Edge edge: Edge to be removed *Clarify what happens in the Model object, in the database when versioning is off, in the database when versioning is on* """ if not isinstance(edge, Edge): raise ArgError("arg must be an Edge object") if not self.contains(edge): warn("edge '{edge}' not contained in model '{model}'".format( edge=edge.triplet, model=self.handle)) return for p in edge.props: try: k = list(edge.triplet) k.append(p.handle) del self.props[tuple(k)] except: pass del self.edges[edge.triplet] edge.src = None edge.dst = None self.removed_entities.append(edge) return edge
def edges_by(self, key, item): if key not in ["src", "dst", "type"]: raise ArgError("arg 'key' must be one of src|dst|type") if isinstance(item, Node): idx = 1 if key == "src" else 2 return [self.edges[x] for x in self.edges if x[idx] == item.handle] else: return [self.edges[x] for x in self.edges if x[0] == item]
def edges_by_type(self, edge_handle): """Get all :class:`Edge` that have a given edge type (i.e., handle) :param str edge_handle: The edge type :return: list of :class:`Edge` """ if not isinstance(edge_handle, str): raise ArgError("arg must be str") return self.edges_by("type", edge_handle)
def edges_out(self, node): """Get all :class:`Edge` that have a given :class:`Node` as their src attribute :param Node node: The node :return: list of :class:`Edge` """ if not isinstance(node, Node): raise ArgError("arg must be Node") return [self.edges[i] for i in self.edges if i[1] == node.handle] pass
def mdb(self, value): if isinstance(value, MDB): self._mdb = value for cls in (Node, Property, Edge, Term, ValueSet, Concept, Predicate, Origin, Tag): cls.object_map = ObjectMap(cls=cls, drv=value.driver) elif not value: self._mdb = None for cls in (Node, Property, Edge, Term, ValueSet, Concept, Origin, Tag): cls.object_map = None else: raise ArgError("mdb= arg must be a bento_meta.mdb.MDB object")
def assign_edge_end(self, edge=None, end=None, node=None): """Move the src or dst of an :class:`Edge` to a different :class:`Node`. :param Edge edge: Edge to manipulate :param str end: Edge end to change (src|dst) :param Node node: Node to be connected Note: Both ``node`` and ``edge`` must be present in the Model instance (via :meth:`add_node` and :meth:`add_edge`) """ if not isinstance(edge, Edge): raise ArgError("edge= must an Edge object") if not isinstance(node, Node): raise ArgError("node= must a Node object") if end not in ["src", "dst"]: raise ArgError("end= must be one of 'src' or 'dst'") if not self.contains(edge) or not self.contains(node): warn("model must contain both edge and node") return del self.edges[edge.triplet] setattr(edge, end, node) self.edges[edge.triplet] = edge return edge
def drv(self, value): if isinstance(value, (BoltDriver, Neo4jDriver)): self._drv = value for cls in (Node, Property, Edge, Term, ValueSet, Concept, Origin, Tag): cls.object_map = ObjectMap(cls=cls, drv=value) elif not value: self._drv = None for cls in (Node, Property, Edge, Term, ValueSet, Concept, Origin, Tag): cls.object_map = None else: raise ArgError( "drv= arg must be Neo4jDriver or BoltDriver (returned from GraphDatabase.driver())" )
def __init__(self, *yaml_files, handle=None): """Create a :class:`Model` from MDF YAML files. :param str|file|url *yaml_files: MDF filenames or file objects, in desired merge order :param str handle: Handle (name) for the resulting Model :attribute model: the :class:`bento_meta.model.Model` created""" if not handle or not isinstance(handle, str): raise ArgError("arg handle= must be a str - name for model") self.handle = handle self.files = yaml_files self.schema = om.MergedOptions() self._model = None if self.files: self.load_yaml() self.create_model() else: warn("No MDF files provided to constructor") pass
def add_node(self, node=None): """Add a :class:`Node` to the model. :param Node node: A :class:`Node` instance The model attribute of ``node`` is set to `Model.handle` """ if not node: raise ArgError("arg must be Node, dict, or graph.Node") if isinstance(node, (dict, neo4j.graph.Node)): node = Node(node) if not node.model: node.model = self.handle for p in node.props.values(): self.add_prop(node, p) self.nodes[node.handle] = node return node
def __init__(self, handle=None, mdb=None): """Model constructor. :param str handle: A string name for the model. Corresponds to the model property in MDB database nodes. :param bento_meta.mdb.MDB mdb: An MDB object containing the db connection (see :class:`bento_meta.mdb.MDB`) """ if not handle: raise ArgError("model requires arg 'handle' set") self.handle = handle self._mdb = None self.nodes = {} self.edges = { } # keys are (edge.handle, src.handle, dst.handle) tuples self.props = {} # keys are ({edge|node}.handle, prop.handle) tuples self.terms = {} self.removed_entities = [] if mdb: self.mdb = mdb
def __init__(self, handle=None, drv=None): """Model constructor. :param str handle: A string name for the model. Corresponds to the model property in MDB database nodes. :param neo4j.Driver drv: A `neo4j.Driver` object describing the db connection (see :class:`neo4j.GraphDatabase`) """ if not handle: raise ArgError("model requires arg 'handle' set") self.handle = handle self._drv = None self.nodes = {} self.edges = { } # keys are (edge.handle, src.handle, dst.handle) tuples self.props = {} # keys are ({edge|node}.handle, prop.handle) tuples self.terms = {} self.removed_entities = [] if drv: self.drv = drv else: self.drv = None
def rm_prop(self, prop): """Remove a :class:`Property` instance from the Model instance. :param Property prop: Property to be removed *Clarify what happens in the Model object, in the database when versioning is off, in the database when versioning is on* """ if not isinstance(prop, Property): raise ArgError("arg must be a Property object") if not self.contains(prop): warn("prop '{prop}' not contained in model '{model}'".format( prop=prop.handle, model=self.handle)) return for okey in prop.belongs: owner = prop.belongs[okey] (i, att, key) = okey getattr(owner, att)[key] == None k = [owner.handle] if isinstance(owner, Node) else list( owner.triplet) k.append(key) del self.props[tuple(k)] self.removed_entities.append(prop) pass
def rm_term(self, term): """Not implemented.""" if not isinstance(term, Term): raise ArgError("arg must be a Term object") pass