示例#1
0
 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
示例#2
0
    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))
示例#3
0
文件: node.py 项目: wesinator/synapse
    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))
示例#4
0
文件: node.py 项目: enadjoe/synapse
    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)
示例#5
0
    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
示例#6
0
    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]
示例#7
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)
示例#8
0
    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]
示例#9
0
    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
示例#10
0
    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)
示例#11
0
    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]
示例#12
0
文件: node.py 项目: wesinator/synapse
    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)
示例#13
0
    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
示例#14
0
    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
示例#15
0
    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)
示例#16
0
    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)
示例#17
0
    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)
示例#18
0
    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)
示例#19
0
    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