synapse 2.197.0__py311-none-any.whl → 2.198.0__py311-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of synapse might be problematic. Click here for more details.

Files changed (44) hide show
  1. synapse/axon.py +3 -0
  2. synapse/common.py +3 -0
  3. synapse/cortex.py +1 -3
  4. synapse/lib/aha.py +3 -0
  5. synapse/lib/ast.py +277 -165
  6. synapse/lib/auth.py +39 -11
  7. synapse/lib/cell.py +22 -4
  8. synapse/lib/hive.py +2 -1
  9. synapse/lib/hiveauth.py +10 -1
  10. synapse/lib/jsonstor.py +6 -5
  11. synapse/lib/layer.py +6 -5
  12. synapse/lib/node.py +10 -4
  13. synapse/lib/parser.py +46 -21
  14. synapse/lib/schemas.py +13 -0
  15. synapse/lib/snap.py +68 -26
  16. synapse/lib/storm.lark +13 -11
  17. synapse/lib/storm.py +1 -1
  18. synapse/lib/storm_format.py +3 -2
  19. synapse/lib/stormtypes.py +13 -4
  20. synapse/lib/version.py +2 -2
  21. synapse/models/infotech.py +18 -0
  22. synapse/models/risk.py +9 -0
  23. synapse/models/syn.py +18 -2
  24. synapse/tests/files/stormpkg/badendpoints.yaml +7 -0
  25. synapse/tests/files/stormpkg/testpkg.yaml +8 -0
  26. synapse/tests/test_cortex.py +108 -0
  27. synapse/tests/test_lib_aha.py +12 -2
  28. synapse/tests/test_lib_ast.py +57 -0
  29. synapse/tests/test_lib_auth.py +143 -2
  30. synapse/tests/test_lib_grammar.py +54 -2
  31. synapse/tests/test_lib_lmdbslab.py +24 -0
  32. synapse/tests/test_lib_storm.py +20 -0
  33. synapse/tests/test_lib_stormlib_macro.py +3 -3
  34. synapse/tests/test_lib_stormtypes.py +14 -2
  35. synapse/tests/test_model_infotech.py +13 -0
  36. synapse/tests/test_model_risk.py +6 -0
  37. synapse/tests/test_model_syn.py +58 -0
  38. synapse/tests/test_tools_genpkg.py +10 -0
  39. synapse/tools/hive/load.py +1 -0
  40. {synapse-2.197.0.dist-info → synapse-2.198.0.dist-info}/METADATA +1 -1
  41. {synapse-2.197.0.dist-info → synapse-2.198.0.dist-info}/RECORD +44 -43
  42. {synapse-2.197.0.dist-info → synapse-2.198.0.dist-info}/LICENSE +0 -0
  43. {synapse-2.197.0.dist-info → synapse-2.198.0.dist-info}/WHEEL +0 -0
  44. {synapse-2.197.0.dist-info → synapse-2.198.0.dist-info}/top_level.txt +0 -0
synapse/axon.py CHANGED
@@ -1017,6 +1017,7 @@ class Axon(s_cell.Cell):
1017
1017
  '''
1018
1018
  for item in self.axonhist.carve(tick, tock=tock):
1019
1019
  yield item
1020
+ await asyncio.sleep(0)
1020
1021
 
1021
1022
  async def hashes(self, offs, wait=False, timeout=None):
1022
1023
  '''
@@ -1086,10 +1087,12 @@ class Axon(s_cell.Cell):
1086
1087
 
1087
1088
  async for byts in self._getBytsOffsSize(sha256, offs, size):
1088
1089
  yield byts
1090
+ await asyncio.sleep(0)
1089
1091
 
1090
1092
  else:
1091
1093
  async for byts in self._get(sha256):
1092
1094
  yield byts
1095
+ await asyncio.sleep(0)
1093
1096
 
1094
1097
  async def _get(self, sha256):
1095
1098
 
synapse/common.py CHANGED
@@ -1222,6 +1222,9 @@ def trimText(text: str, n: int = 256, placeholder: str = '...') -> str:
1222
1222
  assert n > plen
1223
1223
  return f'{text[:mlen]}{placeholder}'
1224
1224
 
1225
+ def queryhash(text):
1226
+ return hashlib.md5(text.encode(errors='surrogatepass'), usedforsecurity=False).hexdigest()
1227
+
1225
1228
  def _patch_http_cookies():
1226
1229
  '''
1227
1230
  Patch stdlib http.cookies._unquote from the 3.11.10 implementation if
synapse/cortex.py CHANGED
@@ -4656,10 +4656,8 @@ class Cortex(s_oauth.OAuthMixin, s_cell.Cell): # type: ignore
4656
4656
  if not path:
4657
4657
  return await self.cellapi.anit(self, link, user)
4658
4658
 
4659
- # allow an admin to directly open the cortex hive
4660
- # (perhaps this should be a Cell() level pattern)
4661
4659
  if path[0] == 'hive' and user.isAdmin():
4662
- s_common.deprecated('Cortex /hive telepath path', curv='2.167.0')
4660
+ s_common.deprecated('Cortex /hive telepath path', curv='2.198.0', eolv='2.199.0')
4663
4661
  return await self.hiveapi.anit(self.hive, user)
4664
4662
 
4665
4663
  if path[0] == 'layer':
synapse/lib/aha.py CHANGED
@@ -1592,6 +1592,7 @@ class AhaCell(s_cell.Cell):
1592
1592
  self.slab.delete(iden, db='aha:provs')
1593
1593
  provinfo = s_msgpack.un(byts)
1594
1594
  logger.info(f'Deleted service provisioning service={provinfo.get("conf").get("aha:name")}, iden={iden.decode()}')
1595
+ await asyncio.sleep(0)
1595
1596
 
1596
1597
  @s_nexus.Pusher.onPushAuto('aha:enroll:clear')
1597
1598
  async def clearAhaUserEnrolls(self):
@@ -1599,6 +1600,7 @@ class AhaCell(s_cell.Cell):
1599
1600
  self.slab.delete(iden, db='aha:enrolls')
1600
1601
  userinfo = s_msgpack.un(byts)
1601
1602
  logger.info(f'Deleted user enrollment username={userinfo.get("name")}, iden={iden.decode()}')
1603
+ await asyncio.sleep(0)
1602
1604
 
1603
1605
  @s_nexus.Pusher.onPushAuto('aha:clone:clear')
1604
1606
  async def clearAhaClones(self):
@@ -1606,6 +1608,7 @@ class AhaCell(s_cell.Cell):
1606
1608
  self.slab.delete(lkey, db='aha:clones')
1607
1609
  cloninfo = s_msgpack.un(byts)
1608
1610
  logger.info(f'Deleted AHA clone enrollment username={cloninfo.get("host")}, iden={s_common.ehex(lkey)}')
1611
+ await asyncio.sleep(0)
1609
1612
 
1610
1613
  @s_nexus.Pusher.onPushAuto('aha:svc:prov:del')
1611
1614
  async def delAhaSvcProv(self, iden):
synapse/lib/ast.py CHANGED
@@ -62,7 +62,7 @@ class AstNode:
62
62
 
63
63
  def getPosInfo(self):
64
64
  return {
65
- 'hash': hashlib.md5(self.astinfo.text.encode(), usedforsecurity=False).hexdigest(),
65
+ 'hash': s_common.queryhash(self.astinfo.text),
66
66
  'lines': (self.astinfo.sline, self.astinfo.eline),
67
67
  'columns': (self.astinfo.scol, self.astinfo.ecol),
68
68
  'offsets': (self.astinfo.soff, self.astinfo.eoff),
@@ -699,7 +699,92 @@ class SubGraph:
699
699
  yield node, path
700
700
 
701
701
  class Oper(AstNode):
702
- pass
702
+
703
+ async def yieldFromValu(self, runt, valu, vkid):
704
+
705
+ viewiden = runt.snap.view.iden
706
+
707
+ # there is nothing in None... ;)
708
+ if valu is None:
709
+ return
710
+
711
+ # a little DWIM on what we get back...
712
+ # ( most common case will be stormtypes libs agenr -> iden|buid )
713
+ # buid list -> nodes
714
+ if isinstance(valu, bytes):
715
+ node = await runt.snap.getNodeByBuid(valu)
716
+ if node is not None:
717
+ yield node
718
+
719
+ return
720
+
721
+ # iden list -> nodes
722
+ if isinstance(valu, str):
723
+ try:
724
+ buid = s_common.uhex(valu)
725
+ except binascii.Error:
726
+ mesg = 'Yield string must be iden in hexdecimal. Got: %r' % (valu,)
727
+ raise vkid.addExcInfo(s_exc.BadLiftValu(mesg=mesg))
728
+
729
+ node = await runt.snap.getNodeByBuid(buid)
730
+ if node is not None:
731
+ yield node
732
+
733
+ return
734
+
735
+ if isinstance(valu, types.AsyncGeneratorType):
736
+ try:
737
+ async for item in valu:
738
+ async for node in self.yieldFromValu(runt, item, vkid):
739
+ yield node
740
+ finally:
741
+ await valu.aclose()
742
+ return
743
+
744
+ if isinstance(valu, types.GeneratorType):
745
+ try:
746
+ for item in valu:
747
+ async for node in self.yieldFromValu(runt, item, vkid):
748
+ yield node
749
+ finally:
750
+ valu.close()
751
+ return
752
+
753
+ if isinstance(valu, (list, tuple, set)):
754
+ for item in valu:
755
+ async for node in self.yieldFromValu(runt, item, vkid):
756
+ yield node
757
+ return
758
+
759
+ if isinstance(valu, s_stormtypes.Node):
760
+ valu = valu.valu
761
+ if valu.snap.view.iden != viewiden:
762
+ mesg = f'Node is not from the current view. Node {valu.iden()} is from {valu.snap.view.iden} expected {viewiden}'
763
+ raise vkid.addExcInfo(s_exc.BadLiftValu(mesg=mesg))
764
+ yield valu
765
+ return
766
+
767
+ if isinstance(valu, s_node.Node):
768
+ if valu.snap.view.iden != viewiden:
769
+ mesg = f'Node is not from the current view. Node {valu.iden()} is from {valu.snap.view.iden} expected {viewiden}'
770
+ raise vkid.addExcInfo(s_exc.BadLiftValu(mesg=mesg))
771
+ yield valu
772
+ return
773
+
774
+ if isinstance(valu, (s_stormtypes.List, s_stormtypes.Set)):
775
+ for item in valu.valu:
776
+ async for node in self.yieldFromValu(runt, item, vkid):
777
+ yield node
778
+ return
779
+
780
+ if isinstance(valu, s_stormtypes.Prim):
781
+ async with contextlib.aclosing(valu.nodes()) as genr:
782
+ async for node in genr:
783
+ if node.snap.view.iden != viewiden:
784
+ mesg = f'Node is not from the current view. Node {node.iden()} is from {node.snap.view.iden} expected {viewiden}'
785
+ raise vkid.addExcInfo(s_exc.BadLiftValu(mesg=mesg))
786
+ yield node
787
+ return
703
788
 
704
789
  class SubQuery(Oper):
705
790
 
@@ -1534,106 +1619,21 @@ class YieldValu(Oper):
1534
1619
  async def run(self, runt, genr):
1535
1620
 
1536
1621
  node = None
1622
+ vkid = self.kids[0]
1537
1623
 
1538
1624
  async for node, path in genr:
1539
- valu = await self.kids[0].compute(runt, path)
1540
- async with contextlib.aclosing(self.yieldFromValu(runt, valu)) as agen:
1625
+ valu = await vkid.compute(runt, path)
1626
+ async with contextlib.aclosing(self.yieldFromValu(runt, valu, vkid)) as agen:
1541
1627
  async for subn in agen:
1542
1628
  yield subn, runt.initPath(subn)
1543
1629
  yield node, path
1544
1630
 
1545
1631
  if node is None and self.kids[0].isRuntSafe(runt):
1546
- valu = await self.kids[0].compute(runt, None)
1547
- async with contextlib.aclosing(self.yieldFromValu(runt, valu)) as agen:
1632
+ valu = await vkid.compute(runt, None)
1633
+ async with contextlib.aclosing(self.yieldFromValu(runt, valu, vkid)) as agen:
1548
1634
  async for subn in agen:
1549
1635
  yield subn, runt.initPath(subn)
1550
1636
 
1551
- async def yieldFromValu(self, runt, valu):
1552
-
1553
- viewiden = runt.snap.view.iden
1554
-
1555
- # there is nothing in None... ;)
1556
- if valu is None:
1557
- return
1558
-
1559
- # a little DWIM on what we get back...
1560
- # ( most common case will be stormtypes libs agenr -> iden|buid )
1561
- # buid list -> nodes
1562
- if isinstance(valu, bytes):
1563
- node = await runt.snap.getNodeByBuid(valu)
1564
- if node is not None:
1565
- yield node
1566
-
1567
- return
1568
-
1569
- # iden list -> nodes
1570
- if isinstance(valu, str):
1571
- try:
1572
- buid = s_common.uhex(valu)
1573
- except binascii.Error:
1574
- mesg = 'Yield string must be iden in hexdecimal. Got: %r' % (valu,)
1575
- raise self.kids[0].addExcInfo(s_exc.BadLiftValu(mesg=mesg))
1576
-
1577
- node = await runt.snap.getNodeByBuid(buid)
1578
- if node is not None:
1579
- yield node
1580
-
1581
- return
1582
-
1583
- if isinstance(valu, types.AsyncGeneratorType):
1584
- try:
1585
- async for item in valu:
1586
- async for node in self.yieldFromValu(runt, item):
1587
- yield node
1588
- finally:
1589
- await valu.aclose()
1590
- return
1591
-
1592
- if isinstance(valu, types.GeneratorType):
1593
- try:
1594
- for item in valu:
1595
- async for node in self.yieldFromValu(runt, item):
1596
- yield node
1597
- finally:
1598
- valu.close()
1599
- return
1600
-
1601
- if isinstance(valu, (list, tuple, set)):
1602
- for item in valu:
1603
- async for node in self.yieldFromValu(runt, item):
1604
- yield node
1605
- return
1606
-
1607
- if isinstance(valu, s_stormtypes.Node):
1608
- valu = valu.valu
1609
- if valu.snap.view.iden != viewiden:
1610
- mesg = f'Node is not from the current view. Node {valu.iden()} is from {valu.snap.view.iden} expected {viewiden}'
1611
- raise s_exc.BadLiftValu(mesg=mesg)
1612
- yield valu
1613
- return
1614
-
1615
- if isinstance(valu, s_node.Node):
1616
- if valu.snap.view.iden != viewiden:
1617
- mesg = f'Node is not from the current view. Node {valu.iden()} is from {valu.snap.view.iden} expected {viewiden}'
1618
- raise s_exc.BadLiftValu(mesg=mesg)
1619
- yield valu
1620
- return
1621
-
1622
- if isinstance(valu, (s_stormtypes.List, s_stormtypes.Set)):
1623
- for item in valu.valu:
1624
- async for node in self.yieldFromValu(runt, item):
1625
- yield node
1626
- return
1627
-
1628
- if isinstance(valu, s_stormtypes.Prim):
1629
- async with contextlib.aclosing(valu.nodes()) as genr:
1630
- async for node in genr:
1631
- if node.snap.view.iden != viewiden:
1632
- mesg = f'Node is not from the current view. Node {node.iden()} is from {node.snap.view.iden} expected {viewiden}'
1633
- raise s_exc.BadLiftValu(mesg=mesg)
1634
- yield node
1635
- return
1636
-
1637
1637
  class LiftTag(LiftOper):
1638
1638
 
1639
1639
  async def lift(self, runt, path):
@@ -4370,6 +4370,83 @@ class EditPropSet(Edit):
4370
4370
 
4371
4371
  await asyncio.sleep(0)
4372
4372
 
4373
+ class EditPropSetMulti(Edit):
4374
+
4375
+ async def run(self, runt, genr):
4376
+
4377
+ self.reqNotReadOnly(runt)
4378
+
4379
+ rval = self.kids[2]
4380
+ oper = await self.kids[1].compute(runt, None)
4381
+
4382
+ isadd = '+' in oper
4383
+ excignore = (s_exc.BadTypeValu,) if '?' in oper else ()
4384
+
4385
+ async for node, path in genr:
4386
+
4387
+ propname = await self.kids[0].compute(runt, path)
4388
+ name = await tostr(propname)
4389
+
4390
+ prop = node.form.props.get(name)
4391
+ if prop is None:
4392
+ if (exc := await s_stormtypes.typeerr(propname, str)) is None:
4393
+ exc = s_exc.NoSuchProp.init(f'{node.form.name}:{name}')
4394
+
4395
+ raise self.kids[0].addExcInfo(exc)
4396
+
4397
+ runt.confirmPropSet(prop)
4398
+
4399
+ if not prop.type.isarray:
4400
+ mesg = f'Property set using ({oper}) is only valid on arrays.'
4401
+ exc = s_exc.StormRuntimeError(mesg=mesg)
4402
+ raise self.kids[0].addExcInfo(exc)
4403
+
4404
+ if isinstance(rval, SubQuery):
4405
+ valu = await rval.compute_array(runt, path)
4406
+ else:
4407
+ valu = await rval.compute(runt, path)
4408
+
4409
+ if valu is None:
4410
+ yield node, path
4411
+ await asyncio.sleep(0)
4412
+ continue
4413
+
4414
+ atyp = prop.type.arraytype
4415
+ isndef = isinstance(atyp, s_types.Ndef)
4416
+ valu = await s_stormtypes.tostor(valu, isndef=isndef)
4417
+
4418
+ if (arry := node.get(name)) is None:
4419
+ arry = ()
4420
+
4421
+ arry = list(arry)
4422
+
4423
+ try:
4424
+ for item in valu:
4425
+ await asyncio.sleep(0)
4426
+
4427
+ try:
4428
+ norm, info = atyp.norm(item)
4429
+ except excignore:
4430
+ continue
4431
+
4432
+ if isadd:
4433
+ arry.append(norm)
4434
+ else:
4435
+ try:
4436
+ arry.remove(norm)
4437
+ except ValueError:
4438
+ pass
4439
+
4440
+ except TypeError:
4441
+ styp = await s_stormtypes.totype(valu, basetypes=True)
4442
+ mesg = f"'{styp}' object is not iterable: {s_common.trimText(repr(valu))}"
4443
+ raise rval.addExcInfo(s_exc.StormRuntimeError(mesg=mesg, type=styp)) from None
4444
+
4445
+ await node.set(name, arry)
4446
+
4447
+ yield node, path
4448
+ await asyncio.sleep(0)
4449
+
4373
4450
  class EditPropDel(Edit):
4374
4451
 
4375
4452
  async def run(self, runt, genr):
@@ -4578,17 +4655,27 @@ class EditEdgeAdd(Edit):
4578
4655
 
4579
4656
  self.reqNotReadOnly(runt)
4580
4657
 
4581
- # SubQuery -> Query
4582
- query = self.kids[1].kids[0]
4658
+ constverb = False
4659
+ if self.kids[0].isconst:
4660
+ constverb = True
4661
+ verb = await tostr(await self.kids[0].compute(runt, None))
4662
+ runt.layerConfirm(('node', 'edge', 'add', verb))
4663
+ else:
4664
+ hits = set()
4665
+ def allowed(x):
4666
+ if x in hits:
4667
+ return
4583
4668
 
4584
- hits = set()
4669
+ runt.layerConfirm(('node', 'edge', 'add', x))
4670
+ hits.add(x)
4585
4671
 
4586
- def allowed(x):
4587
- if x in hits:
4588
- return
4672
+ isvar = False
4673
+ vkid = self.kids[1]
4589
4674
 
4590
- runt.layerConfirm(('node', 'edge', 'add', x))
4591
- hits.add(x)
4675
+ if not isinstance(vkid, SubQuery):
4676
+ isvar = True
4677
+ else:
4678
+ query = vkid.kids[0]
4592
4679
 
4593
4680
  async for node, path in genr:
4594
4681
 
@@ -4596,38 +4683,44 @@ class EditEdgeAdd(Edit):
4596
4683
  mesg = f'Edges cannot be used with runt nodes: {node.form.full}'
4597
4684
  raise self.addExcInfo(s_exc.IsRuntForm(mesg=mesg, form=node.form.full))
4598
4685
 
4599
- iden = node.iden()
4600
- verb = await tostr(await self.kids[0].compute(runt, path))
4601
-
4602
- allowed(verb)
4603
-
4604
- async with runt.getSubRuntime(query) as subr:
4605
-
4606
- if self.n2:
4607
- async for subn, subp in subr.execute():
4608
- if subn.form.isrunt:
4609
- mesg = f'Edges cannot be used with runt nodes: {subn.form.full}'
4610
- raise self.addExcInfo(s_exc.IsRuntForm(mesg=mesg, form=subn.form.full))
4611
-
4612
- await subn.addEdge(verb, iden)
4613
-
4614
- else:
4615
- async with node.snap.getEditor() as editor:
4616
- proto = editor.loadNode(node)
4686
+ if not constverb:
4687
+ verb = await tostr(await self.kids[0].compute(runt, path))
4688
+ allowed(verb)
4689
+
4690
+ if isvar:
4691
+ valu = await vkid.compute(runt, path)
4692
+ async with contextlib.aclosing(self.yieldFromValu(runt, valu, vkid)) as agen:
4693
+ if self.n2:
4694
+ iden = node.iden()
4695
+ async for subn in agen:
4696
+ await subn.addEdge(verb, iden, extra=self.addExcInfo)
4697
+ else:
4698
+ async with node.snap.getEditor() as editor:
4699
+ proto = editor.loadNode(node)
4700
+ async for subn in agen:
4701
+ if subn.form.isrunt:
4702
+ mesg = f'Edges cannot be used with runt nodes: {subn.form.full}'
4703
+ raise self.addExcInfo(s_exc.IsRuntForm(mesg=mesg, form=subn.form.full))
4704
+
4705
+ await proto.addEdge(verb, subn.iden())
4706
+ await asyncio.sleep(0)
4617
4707
 
4708
+ else:
4709
+ async with runt.getSubRuntime(query) as subr:
4710
+ if self.n2:
4711
+ iden = node.iden()
4618
4712
  async for subn, subp in subr.execute():
4619
- if subn.form.isrunt:
4620
- mesg = f'Edges cannot be used with runt nodes: {subn.form.full}'
4621
- raise self.addExcInfo(s_exc.IsRuntForm(mesg=mesg, form=subn.form.full))
4622
-
4623
- await proto.addEdge(verb, subn.iden())
4624
- await asyncio.sleep(0)
4625
-
4626
- if len(proto.edges) >= 1000:
4627
- nodeedits = editor.getNodeEdits()
4628
- if nodeedits:
4629
- await node.snap.applyNodeEdits(nodeedits)
4630
- proto.edges.clear()
4713
+ await subn.addEdge(verb, iden, extra=self.addExcInfo)
4714
+ else:
4715
+ async with node.snap.getEditor() as editor:
4716
+ proto = editor.loadNode(node)
4717
+ async for subn, subp in subr.execute():
4718
+ if subn.form.isrunt:
4719
+ mesg = f'Edges cannot be used with runt nodes: {subn.form.full}'
4720
+ raise self.addExcInfo(s_exc.IsRuntForm(mesg=mesg, form=subn.form.full))
4721
+
4722
+ await proto.addEdge(verb, subn.iden())
4723
+ await asyncio.sleep(0)
4631
4724
 
4632
4725
  yield node, path
4633
4726
 
@@ -4641,16 +4734,27 @@ class EditEdgeDel(Edit):
4641
4734
 
4642
4735
  self.reqNotReadOnly(runt)
4643
4736
 
4644
- query = self.kids[1].kids[0]
4737
+ isvar = False
4738
+ vkid = self.kids[1]
4645
4739
 
4646
- hits = set()
4740
+ if not isinstance(vkid, SubQuery):
4741
+ isvar = True
4742
+ else:
4743
+ query = vkid.kids[0]
4647
4744
 
4648
- def allowed(x):
4649
- if x in hits:
4650
- return
4745
+ constverb = False
4746
+ if self.kids[0].isconst:
4747
+ constverb = True
4748
+ verb = await tostr(await self.kids[0].compute(runt, None))
4749
+ runt.layerConfirm(('node', 'edge', 'del', verb))
4750
+ else:
4751
+ hits = set()
4752
+ def allowed(x):
4753
+ if x in hits:
4754
+ return
4651
4755
 
4652
- runt.layerConfirm(('node', 'edge', 'del', x))
4653
- hits.add(x)
4756
+ runt.layerConfirm(('node', 'edge', 'del', x))
4757
+ hits.add(x)
4654
4758
 
4655
4759
  async for node, path in genr:
4656
4760
 
@@ -4658,36 +4762,44 @@ class EditEdgeDel(Edit):
4658
4762
  mesg = f'Edges cannot be used with runt nodes: {node.form.full}'
4659
4763
  raise self.addExcInfo(s_exc.IsRuntForm(mesg=mesg, form=node.form.full))
4660
4764
 
4661
- iden = node.iden()
4662
- verb = await tostr(await self.kids[0].compute(runt, path))
4663
-
4664
- allowed(verb)
4665
-
4666
- async with runt.getSubRuntime(query) as subr:
4667
- if self.n2:
4668
- async for subn, subp in subr.execute():
4669
- if subn.form.isrunt:
4670
- mesg = f'Edges cannot be used with runt nodes: {subn.form.full}'
4671
- raise self.addExcInfo(s_exc.IsRuntForm(mesg=mesg, form=subn.form.full))
4672
- await subn.delEdge(verb, iden)
4673
-
4674
- else:
4675
- async with node.snap.getEditor() as editor:
4676
- proto = editor.loadNode(node)
4765
+ if not constverb:
4766
+ verb = await tostr(await self.kids[0].compute(runt, path))
4767
+ allowed(verb)
4768
+
4769
+ if isvar:
4770
+ valu = await vkid.compute(runt, path)
4771
+ async with contextlib.aclosing(self.yieldFromValu(runt, valu, vkid)) as agen:
4772
+ if self.n2:
4773
+ iden = node.iden()
4774
+ async for subn in agen:
4775
+ await subn.delEdge(verb, iden, extra=self.addExcInfo)
4776
+ else:
4777
+ async with node.snap.getEditor() as editor:
4778
+ proto = editor.loadNode(node)
4779
+ async for subn in agen:
4780
+ if subn.form.isrunt:
4781
+ mesg = f'Edges cannot be used with runt nodes: {subn.form.full}'
4782
+ raise self.addExcInfo(s_exc.IsRuntForm(mesg=mesg, form=subn.form.full))
4783
+
4784
+ await proto.delEdge(verb, subn.iden())
4785
+ await asyncio.sleep(0)
4677
4786
 
4787
+ else:
4788
+ async with runt.getSubRuntime(query) as subr:
4789
+ if self.n2:
4790
+ iden = node.iden()
4678
4791
  async for subn, subp in subr.execute():
4679
- if subn.form.isrunt:
4680
- mesg = f'Edges cannot be used with runt nodes: {subn.form.full}'
4681
- raise self.addExcInfo(s_exc.IsRuntForm(mesg=mesg, form=subn.form.full))
4682
-
4683
- await proto.delEdge(verb, subn.iden())
4684
- await asyncio.sleep(0)
4685
-
4686
- if len(proto.edgedels) >= 1000:
4687
- nodeedits = editor.getNodeEdits()
4688
- if nodeedits:
4689
- await node.snap.applyNodeEdits(nodeedits)
4690
- proto.edgedels.clear()
4792
+ await subn.delEdge(verb, iden, extra=self.addExcInfo)
4793
+ else:
4794
+ async with node.snap.getEditor() as editor:
4795
+ proto = editor.loadNode(node)
4796
+ async for subn, subp in subr.execute():
4797
+ if subn.form.isrunt:
4798
+ mesg = f'Edges cannot be used with runt nodes: {subn.form.full}'
4799
+ raise self.addExcInfo(s_exc.IsRuntForm(mesg=mesg, form=subn.form.full))
4800
+
4801
+ await proto.delEdge(verb, subn.iden())
4802
+ await asyncio.sleep(0)
4691
4803
 
4692
4804
  yield node, path
4693
4805