async def runRuntPropDel(self, node, prop): func = self._runtPropDelFuncs.get(prop.full) if func is None: raise s_exc.IsRuntForm(mesg='No prop:del func set for runt property.', prop=prop.full, ndef=node.ndef) ret = await s_coro.ornot(func, node, prop) return ret
async def _getAddNodeEdits(self, name, valu, props=None): form = self.core.model.form(name) if form is None: raise s_exc.NoSuchForm(name=name) if form.isrunt: raise s_exc.IsRuntForm(mesg='Cannot make runt nodes.', form=form.full, prop=valu) if self.buidprefetch: norm, info = form.type.norm(valu) buid = s_common.buid((form.name, norm)) node = await self.getNodeByBuid(buid) if node is not None: if props is not None: return (node, await self.getNodeAdds(form, valu, props=props, addnode=False)) else: return (node, [(buid, form.name, [])]) return (None, await self.getNodeAdds(form, valu, props=props))
async def pop(self, name, init=False): ''' Remove a property from a node and return the value ''' prop = self.form.prop(name) if prop is None: if self.snap.strict: raise s_exc.NoSuchProp(name=name, form=self.form.name) await self.snap.warn(f'No Such Property: {name}') return False if self.form.isrunt: if prop.info.get('ro'): raise s_exc.IsRuntForm( mesg='Cannot delete read-only props on runt nodes', form=self.form.full, prop=name) return await self.snap.core.runRuntPropDel(self, prop) if not init: if prop.info.get('ro'): if self.snap.strict: raise s_exc.ReadOnlyProp(name=name) await self.snap.warn(f'Property is read-only: {name}') return False curv = self.props.pop(name, s_common.novalu) if curv is s_common.novalu: return False edits = ((s_layer.EDIT_PROP_DEL, (prop.name, None, prop.type.stortype), ()), ) await self.snap.applyNodeEdit((self.buid, self.form.name, edits))
async def addEdge(self, verb, n2iden): if self.form.isrunt: mesg = f'Edges cannot be used with runt nodes: {self.form.full}' raise s_exc.IsRuntForm(mesg=mesg, form=self.form.full) nodeedits = ((self.buid, self.form.name, ((s_layer.EDIT_EDGE_ADD, (verb, n2iden), ()), )), ) await self.snap.applyNodeEdits(nodeedits)
async def _getTagDelEdits(self, tag, init=False): path = s_chop.tagpath(tag) name = '.'.join(path) if self.form.isrunt: raise s_exc.IsRuntForm(mesg='Cannot delete tags from runt nodes.', form=self.form.full, tag=tag) curv = self.tags.get(name, s_common.novalu) if curv is s_common.novalu: return () pref = name + '.' todel = [(len(t), t) for t in self.tags.keys() if t.startswith(pref)] if len(path) > 1: parent = '.'.join(path[:-1]) # retrieve a list of prunable tags prune = await self.snap.core.getTagPrune(parent) if prune: tree = self._getTagTree() for prunetag in reversed(prune): node = tree for step in prunetag.split('.'): node = node[1].get(step) if node is None: break if node is not None and len(node[1]) == 1: todel.append((len(node[0]), node[0])) continue break todel.sort(reverse=True) # order matters... edits = [] for _, subtag in todel: edits.extend(self._getTagPropDel(subtag)) edits.append((s_layer.EDIT_TAG_DEL, (subtag, None), ())) edits.extend(self._getTagPropDel(name)) edits.append((s_layer.EDIT_TAG_DEL, (name, None), ())) return edits
async def addNode(self, name, valu, props=None): ''' Add a node by form name and value with optional props. Args: name (str): The form of node to add. valu (obj): The value for the node. props (dict): Optional secondary properties for the node. Notes: If a props dictionary is provided, it may be mutated during node construction. Returns: s_node.Node: A Node object. It may return None if the snap is unable to add or lift the node. ''' if self.readonly: mesg = 'The snapshot is in read-only mode.' raise s_exc.IsReadOnly(mesg=mesg) form = self.core.model.form(name) if form is None: raise s_exc.NoSuchForm(name=name) if form.isrunt: raise s_exc.IsRuntForm(mesg='Cannot make runt nodes.', form=form.full, prop=valu) try: if self.buidprefetch: norm, info = form.type.norm(valu) node = await self.getNodeByBuid( s_common.buid((form.name, norm))) if node is not None: # TODO implement node.setNodeProps() if props is not None: for p, v in props.items(): await node.set(p, v) return node adds = await self.getNodeAdds(form, valu, props=props) except asyncio.CancelledError: # pragma: no cover TODO: remove once >= py 3.8 only raise except Exception as e: if not self.strict: await self.warn(f'addNode: {e}') return None raise nodes = await self.applyNodeEdits(adds) assert len(nodes) >= 1 # Adds is top-down, so the first node is what we want return nodes[0]
async def addEdge(self, verb, n2iden): if self.form.isrunt: mesg = f'Edges cannot be used with runt nodes: {self.form.full}' raise s_exc.IsRuntForm(mesg=mesg, form=self.form.full) if not s_common.isbuidhex(n2iden): mesg = f'addEdge() got an invalid node iden: {n2iden}' raise s_exc.BadArg(mesg=mesg) nodeedits = ((self.buid, self.form.name, ((s_layer.EDIT_EDGE_ADD, (verb, n2iden), ()), )), ) await self.snap.applyNodeEdits(nodeedits)
async def delTag(self, tag, init=False): ''' Delete a tag from the node. ''' path = s_chop.tagpath(tag) name = '.'.join(path) if self.isrunt: raise s_exc.IsRuntForm(mesg='Cannot delete tags from runt nodes.', form=self.form.full, tag=tag) curv = self.tags.pop(name, s_common.novalu) if curv is s_common.novalu: return False pref = name + '.' tagprops = [x for x in self.tagprops.keys() if x[0] == name] subtags = [(len(t), t) for t in self.tags.keys() if t.startswith(pref)] subtags.sort(reverse=True) removed = [] for sublen, subtag in subtags: valu = self.tags.pop(subtag, None) removed.append((subtag, valu)) tagprops.extend( [x for x in self.tagprops.keys() if x[0] == subtag]) removed.append((name, curv)) info = {'univ': True} sops = [('prop:del', (self.buid, self.form.name, '#' + t, info)) for (t, v) in removed] sops.extend([('tag:prop:del', (self.buid, self.form.name, tag, prop, {})) for (tag, prop) in tagprops]) # fire all the splices splices = [ self.snap.splice('tag:del', ndef=self.ndef, tag=t, valu=v) for (t, v) in removed ] await self.snap.stor(sops, splices) # fire all the handlers / triggers [await self.snap.core.runTagDel(self, t, v) for (t, v) in removed]
async def pop(self, name, init=False): ''' Remove a property from a node and return the value ''' if self.form.isrunt: prop = self.form.prop(name) if prop.info.get('ro'): raise s_exc.IsRuntForm( mesg='Cannot delete read-only props on runt nodes', form=self.form.full, prop=name) return await self.snap.core.runRuntPropDel(self, prop) edits = await self._getPropDelEdits(name, init=init) if not edits: return False await self.snap.applyNodeEdit((self.buid, self.form.name, edits)) self.props.pop(name, None) return True
async def pop(self, name, init=False): ''' Remove a property from a node and return the value ''' prop = self.form.prop(name) if prop is None: if self.snap.strict: raise s_exc.NoSuchProp(name=name, form=self.form.name) await self.snap.warn(f'No Such Property: {name}') return False if self.isrunt: if prop.info.get('ro'): raise s_exc.IsRuntForm( mesg='Cannot delete read-only props on runt nodes', form=self.form.full, prop=name) return await self.snap.core.runRuntPropDel(self, prop) if not init: if prop.info.get('ro'): if self.snap.strict: raise s_exc.ReadOnlyProp(name=name) await self.snap.warn(f'Property is read-only: {name}') return False curv = self.props.pop(name, s_common.novalu) if curv is s_common.novalu: return False sops = prop.getDelOps(self.buid) splice = self.snap.splice('prop:del', ndef=self.ndef, prop=prop.name, valu=curv) await self.snap.stor(sops, [splice]) await prop.wasDel(self, curv)
async def addNode(self, name, valu, props=None): ''' Add a node by form name and value with optional props. Args: name (str): The form of node to add. valu (obj): The value for the node. props (dict): Optional secondary properties for the node. Notes: If a props dictionary is provided, it may be mutated during node construction. Returns: s_node.Node: A Node object. It may return None if the snap is unable to add or lift the node. ''' if self.readonly: mesg = 'The snapshot is in read-only mode.' raise s_exc.IsReadOnly(mesg=mesg) form = self.core.model.form(name) if form is None: raise s_exc.NoSuchForm(name=name) if form.isrunt: raise s_exc.IsRuntForm(mesg='Cannot make runt nodes.', form=form.full, prop=valu) try: adds = self.getNodeAdds(form, valu, props=props) except Exception as e: if not self.strict: await self.warn(f'addNode: {e}') return None raise # depth first, so the last one is our added node nodes = await self.applyNodeEdits(adds) return nodes[-1]
async def delTag(self, tag, init=False): ''' Delete a tag from the node. ''' path = s_chop.tagpath(tag) name = '.'.join(path) if self.form.isrunt: raise s_exc.IsRuntForm(mesg='Cannot delete tags from runt nodes.', form=self.form.full, tag=tag) curv = self.tags.get(name, s_common.novalu) if curv is s_common.novalu: return False pref = name + '.' subtags = [(len(t), t) for t in self.tags.keys() if t.startswith(pref)] subtags.sort(reverse=True) # order matters... edits = [] for _, subtag in subtags: edits.extend(self._getTagPropDel(subtag)) edits.append((s_layer.EDIT_TAG_DEL, (subtag, None), ())) edits.extend(self._getTagPropDel(name)) edits.append((s_layer.EDIT_TAG_DEL, (name, None), ())) nodeedit = (self.buid, self.form.name, edits) await self.snap.applyNodeEdit(nodeedit)
async def _addNodeFnibOps(self, fnib, editatom, props=None): ''' Add a node via (form, norm, info, buid) and add ops to editatom ''' form, norm, info, buid = fnib if form.isrunt: raise s_exc.IsRuntForm(mesg='Cannot make runt nodes.', form=form.full, prop=norm) if props is None: props = {} # Check if this buid is already under construction node = editatom.getNodeBeingMade(buid) if node is not None: return node # Check if this buid is already fully made node = await self.getNodeByBuid(buid) if node is not None: return node # Another editatom might have created in another task during the above call, so check again node = editatom.getNodeBeingMade(buid) if node is not None: return node if props is None: props = {} # lets build a node... node = s_node.Node(self, None) node.buid = buid node.form = form node.ndef = (form.name, norm) sops = form.getSetOps(buid, norm) editatom.sops.extend(sops) editatom.addNode(node) # update props with any subs from form value subs = info.get('subs') if subs is not None: for name, valu in subs.items(): if form.prop(name) is not None: props[name] = valu # update props with any defvals we are missing for name, valu in form.defvals.items(): props.setdefault(name, valu) # set all the properties with init=True for name, valu in props.items(): await node._setops(name, valu, editatom, init=True) # set our global properties tick = s_common.now() await node._setops('.created', tick, editatom, init=True) return None
async def _setops(self, name, valu, editatom, init=False): ''' Generate operations to set a property on a node. ''' prop = self.form.prop(name) if prop is None: if self.snap.strict: raise s_exc.NoSuchProp(name=name, form=self.form.name) await self.snap.warn(f'NoSuchProp: name={name}') return False if self.isrunt: if prop.info.get('ro'): raise s_exc.IsRuntForm( mesg='Cannot set read-only props on runt nodes', form=self.form.full, prop=name, valu=valu) return await self.snap.core.runRuntPropSet(self, prop, valu) curv = self.props.get(name) # normalize the property value... try: norm, info = prop.type.norm(valu) except Exception as e: mesg = f'Bad property value: {prop.full}={valu!r}' return await self.snap._raiseOnStrict(s_exc.BadPropValu, mesg, name=prop.name, valu=valu, emesg=str(e)) # do we already have the value? if curv == norm: return False if curv is not None and not init: if prop.info.get('ro'): if self.snap.strict: raise s_exc.ReadOnlyProp(name=prop.full) # not setting a set-once prop unless we are init... return False # check for type specific merging... norm = prop.type.merge(curv, norm) if curv == norm: return False sops = prop.getSetOps(self.buid, norm) editatom.sops.extend(sops) # self.props[prop.name] = norm editatom.npvs.append((self, prop, curv, norm)) # do we have any auto nodes to add? auto = self.snap.model.form(prop.type.name) if auto is not None: buid = s_common.buid((auto.name, norm)) await self.snap._addNodeFnibOps((auto, norm, info, buid), editatom) # does the type think we have special auto nodes to add? # ( used only for adds which do not meet the above block ) for autoname, autovalu in info.get('adds', ()): auto = self.snap.model.form(autoname) autonorm, autoinfo = auto.type.norm(autovalu) buid = s_common.buid((auto.name, autonorm)) await self.snap._addNodeFnibOps((auto, autovalu, autoinfo, buid), editatom) # do we need to set any sub props? subs = info.get('subs') if subs is not None: for subname, subvalu in subs.items(): full = prop.name + ':' + subname subprop = self.form.prop(full) if subprop is None: continue await self._setops(full, subvalu, editatom, init=init) return True
async def addTag(self, tag, valu=(None, None)): ''' Add a tag to a node. Args: tag (str): The tag to add to the node. valu: The optional tag value. If specified, this must be a value that norms as a valid time interval as an ival. Returns: None: This returns None. ''' if self.isrunt: raise s_exc.IsRuntForm(mesg='Cannot add tags to runt nodes.', form=self.form.full, tag=tag) path = s_chop.tagpath(tag) name = '.'.join(path) tagnode = await self.snap.addTagNode(name) # implement tag renames... isnow = tagnode.get('isnow') if isnow: await self.snap.warn(f'tag {name} is now {isnow}') name = isnow path = isnow.split('.') if isinstance(valu, list): valu = tuple(valu) if valu != (None, None): valu = self.snap.model.type('ival').norm(valu)[0] curv = self.tags.get(name) if curv == valu: return elif curv is None: tags = s_chop.tags(name) for tag in tags[:-1]: if self.tags.get(tag) is not None: continue await self._addTagRaw(tag, (None, None)) await self._addTagRaw(tags[-1], valu) return # merge values into one interval valu = s_time.ival(*valu, *curv) if valu == curv: return indx = self.snap.model.types['ival'].indx(valu) info = {'univ': True} await self._setTagProp(name, valu, indx, info)
async def delete(self, force=False): ''' Delete a node from the cortex. The following tear-down operations occur in order: * validate that you have permissions to delete the node * validate that you have permissions to delete all tags * validate that there are no remaining references to the node. * delete all the tags (bottom up) * fire onDelTag() handlers * delete tag properties from storage * log tag:del splices * delete all secondary properties * fire onDelProp handler * delete secondary property from storage * log prop:del splices * delete the primary property * fire onDel handlers for the node * delete primary property from storage * log node:del splices ''' formname, formvalu = self.ndef if self.form.isrunt: raise s_exc.IsRuntForm(mesg='Cannot delete runt nodes', form=formname, valu=formvalu) # top level tags will cause delete cascades tags = [t for t in self.tags.keys() if len(t.split('.')) == 1] # check for any nodes which reference us... if not force: # refuse to delete tag nodes with existing tags if self.form.name == 'syn:tag': async for _ in self.snap.nodesByTag(self.ndef[1]): # NOQA mesg = 'Nodes still have this tag.' return await self.snap._raiseOnStrict(s_exc.CantDelNode, mesg, form=formname, iden=self.iden()) async for refr in self.snap.nodesByPropTypeValu( formname, formvalu): if refr.buid == self.buid: continue mesg = 'Other nodes still refer to this node.' return await self.snap._raiseOnStrict(s_exc.CantDelNode, mesg, form=formname, iden=self.iden()) edits = [] for tag in tags: edits.extend(await self._getTagDelEdits(tag, init=True)) for name in self.props.keys(): edits.extend(await self._getPropDelEdits(name, init=True)) edits.append( (s_layer.EDIT_NODE_DEL, (formvalu, self.form.type.stortype), ()), ) await self.snap.applyNodeEdit((self.buid, formname, edits)) self.snap.livenodes.pop(self.buid, None)
async def delete(self, force=False): ''' Delete a node from the cortex. The following tear-down operations occur in order: * validate that you have permissions to delete the node * validate that you have permissions to delete all tags * validate that there are no remaining references to the node. * delete all the tags (bottom up) * fire onDelTag() handlers * delete tag properties from storage * log tag:del splices * delete all secondary properties * fire onDelProp handler * delete secondary property from storage * log prop:del splices * delete the primary property * fire onDel handlers for the node * delete primary property from storage * log node:del splices ''' formname, formvalu = self.ndef if self.isrunt: raise s_exc.IsRuntForm(mesg='Cannot delete runt nodes', form=formname, valu=formvalu) tags = [(len(t), t) for t in self.tags.keys()] # check for tag permissions # TODO # check for any nodes which reference us... if not force: # refuse to delete tag nodes with existing tags if self.form.name == 'syn:tag': async for _ in self.snap._getNodesByTag(self.ndef[1]): # NOQA mesg = 'Nodes still have this tag.' return await self.snap._raiseOnStrict(s_exc.CantDelNode, mesg, form=formname) async for refr in self.snap._getNodesByType(formname, formvalu, addform=False): if refr.buid == self.buid: continue mesg = 'Other nodes still refer to this node.' return await self.snap._raiseOnStrict(s_exc.CantDelNode, mesg, form=formname) for size, tag in sorted(tags, reverse=True): await self.delTag(tag, init=True) for name in list(self.props.keys()): await self.pop(name, init=True) sops = self.form.getDelOps(self.buid) splice = self.snap.splice('node:del', ndef=self.ndef) await self.snap.stor(sops, [splice]) self.snap.livenodes.pop(self.buid) self.snap.core.pokeFormCount(formname, -1) await self.form.wasDeleted(self)
async def addTag(self, tag, valu=(None, None)): ''' Add a tag to a node. Args: tag (str): The tag to add to the node. valu: The optional tag value. If specified, this must be a value that norms as a valid time interval as an ival. Returns: None: This returns None. ''' if self.form.isrunt: raise s_exc.IsRuntForm(mesg='Cannot add tags to runt nodes.', form=self.form.full, tag=tag) path = s_chop.tagpath(tag) name = '.'.join(path) if not await self.snap.core.isTagValid(name): mesg = f'The tag does not meet the regex for the tree.' raise s_exc.BadTag(mesg=mesg) tagnode = await self.snap.addTagNode(name) # implement tag renames... isnow = tagnode.get('isnow') if isnow: await self.snap.warn(f'tag {name} is now {isnow}') name = isnow path = isnow.split('.') if isinstance(valu, list): valu = tuple(valu) if valu != (None, None): valu = self.snap.core.model.type('ival').norm(valu)[0] curv = self.tags.get(name) if curv == valu: return edits = [] if curv is None: tags = s_chop.tags(name) for tag in tags[:-1]: if self.tags.get(tag) is not None: continue await self.snap.addTagNode(tag) edits.append( (s_layer.EDIT_TAG_SET, (tag, (None, None), None), ())) else: # merge values into one interval valu = s_time.ival(*valu, *curv) if valu == curv: return edits.append((s_layer.EDIT_TAG_SET, (name, valu, None), ())) nodeedit = (self.buid, self.form.name, edits) await self.snap.applyNodeEdit(nodeedit)
async def set(self, name, valu, init=False): ''' Set a property on the node. Args: name (str): The name of the property. valu (obj): The value of the property. init (bool): Set to True to disable read-only enforcement Returns: (bool): True if the property was changed. ''' if self.snap.readonly: mesg = 'Cannot set property in read-only mode.' raise s_exc.IsReadOnly(mesg=mesg) prop = self.form.prop(name) if prop is None: if self.snap.strict: mesg = f'No property named {name}.' raise s_exc.NoSuchProp(mesg=mesg, name=name, form=self.form.name) await self.snap.warn(f'NoSuchProp: name={name}') return False if self.form.isrunt: if prop.info.get('ro'): mesg = 'Cannot set read-only props on runt nodes' raise s_exc.IsRuntForm(mesg=mesg, form=self.form.full, prop=name, valu=valu) await self.snap.core.runRuntPropSet(self, prop, valu) return True curv = self.props.get(name) # normalize the property value... try: norm, info = prop.type.norm(valu) except Exception as e: mesg = f'Bad property value: {prop.full}={valu!r}' return await self.snap._raiseOnStrict(s_exc.BadTypeValu, mesg, name=prop.name, valu=valu, emesg=str(e)) # do we already have the value? if curv == norm: return False await self.snap.core._callPropSetHook(self, prop, norm) if curv is not None and not init: if prop.info.get('ro'): if self.snap.strict: raise s_exc.ReadOnlyProp(name=prop.full) # not setting a set-once prop unless we are init... return False # check for type specific merging... norm = prop.type.merge(curv, norm) if curv == norm: return False props = {prop.name: norm} nodeedits = await self.snap.getNodeAdds(self.form, self.ndef[1], props, addnode=False) await self.snap.applyNodeEdits(nodeedits) return True