synapse 2.192.0__py311-none-any.whl → 2.194.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 (77) hide show
  1. synapse/common.py +15 -0
  2. synapse/cortex.py +19 -25
  3. synapse/datamodel.py +6 -3
  4. synapse/exc.py +6 -1
  5. synapse/lib/agenda.py +17 -6
  6. synapse/lib/ast.py +242 -97
  7. synapse/lib/auth.py +1 -0
  8. synapse/lib/cell.py +31 -85
  9. synapse/lib/cli.py +20 -11
  10. synapse/lib/parser.py +5 -1
  11. synapse/lib/snap.py +44 -15
  12. synapse/lib/storm.lark +16 -1
  13. synapse/lib/storm.py +40 -21
  14. synapse/lib/storm_format.py +1 -0
  15. synapse/lib/stormctrl.py +88 -6
  16. synapse/lib/stormlib/cache.py +6 -2
  17. synapse/lib/stormlib/json.py +5 -2
  18. synapse/lib/stormlib/scrape.py +1 -1
  19. synapse/lib/stormlib/stix.py +8 -8
  20. synapse/lib/stormtypes.py +32 -5
  21. synapse/lib/version.py +2 -2
  22. synapse/lib/view.py +20 -3
  23. synapse/models/geopol.py +1 -0
  24. synapse/models/geospace.py +1 -0
  25. synapse/models/inet.py +20 -1
  26. synapse/models/infotech.py +24 -6
  27. synapse/models/orgs.py +7 -2
  28. synapse/models/person.py +15 -4
  29. synapse/models/risk.py +19 -2
  30. synapse/models/telco.py +10 -3
  31. synapse/tests/test_axon.py +6 -6
  32. synapse/tests/test_cortex.py +133 -14
  33. synapse/tests/test_exc.py +4 -0
  34. synapse/tests/test_lib_agenda.py +282 -2
  35. synapse/tests/test_lib_aha.py +13 -6
  36. synapse/tests/test_lib_ast.py +301 -10
  37. synapse/tests/test_lib_auth.py +6 -7
  38. synapse/tests/test_lib_cell.py +71 -1
  39. synapse/tests/test_lib_grammar.py +14 -0
  40. synapse/tests/test_lib_layer.py +1 -1
  41. synapse/tests/test_lib_lmdbslab.py +3 -3
  42. synapse/tests/test_lib_storm.py +273 -55
  43. synapse/tests/test_lib_stormctrl.py +65 -0
  44. synapse/tests/test_lib_stormhttp.py +5 -5
  45. synapse/tests/test_lib_stormlib_auth.py +5 -5
  46. synapse/tests/test_lib_stormlib_cache.py +38 -6
  47. synapse/tests/test_lib_stormlib_json.py +20 -0
  48. synapse/tests/test_lib_stormlib_modelext.py +3 -3
  49. synapse/tests/test_lib_stormlib_scrape.py +6 -6
  50. synapse/tests/test_lib_stormlib_spooled.py +1 -1
  51. synapse/tests/test_lib_stormlib_xml.py +5 -5
  52. synapse/tests/test_lib_stormtypes.py +54 -57
  53. synapse/tests/test_lib_view.py +1 -1
  54. synapse/tests/test_model_base.py +1 -2
  55. synapse/tests/test_model_geopol.py +4 -0
  56. synapse/tests/test_model_geospace.py +6 -0
  57. synapse/tests/test_model_inet.py +43 -5
  58. synapse/tests/test_model_infotech.py +10 -1
  59. synapse/tests/test_model_orgs.py +17 -2
  60. synapse/tests/test_model_person.py +23 -1
  61. synapse/tests/test_model_risk.py +13 -0
  62. synapse/tests/test_tools_healthcheck.py +4 -4
  63. synapse/tests/test_tools_storm.py +95 -0
  64. synapse/tests/test_utils.py +17 -18
  65. synapse/tests/test_utils_getrefs.py +1 -1
  66. synapse/tests/utils.py +0 -35
  67. synapse/tools/changelog.py +6 -4
  68. synapse/tools/storm.py +1 -1
  69. synapse/utils/getrefs.py +14 -3
  70. synapse/vendor/cpython/lib/http/__init__.py +0 -0
  71. synapse/vendor/cpython/lib/http/cookies.py +59 -0
  72. synapse/vendor/cpython/lib/test/test_http_cookies.py +49 -0
  73. {synapse-2.192.0.dist-info → synapse-2.194.0.dist-info}/METADATA +6 -6
  74. {synapse-2.192.0.dist-info → synapse-2.194.0.dist-info}/RECORD +77 -73
  75. {synapse-2.192.0.dist-info → synapse-2.194.0.dist-info}/WHEEL +1 -1
  76. {synapse-2.192.0.dist-info → synapse-2.194.0.dist-info}/LICENSE +0 -0
  77. {synapse-2.192.0.dist-info → synapse-2.194.0.dist-info}/top_level.txt +0 -0
synapse/lib/ast.py CHANGED
@@ -28,6 +28,16 @@ import synapse.lib.stormtypes as s_stormtypes
28
28
 
29
29
  from synapse.lib.stormtypes import tobool, toint, toprim, tostr, tonumber, tocmprvalu, undef
30
30
 
31
+ SET_ALWAYS = 0
32
+ SET_UNSET = 1
33
+ SET_NEVER = 2
34
+
35
+ COND_EDIT_SET = {
36
+ 'always': SET_ALWAYS,
37
+ 'unset': SET_UNSET,
38
+ 'never': SET_NEVER,
39
+ }
40
+
31
41
  logger = logging.getLogger(__name__)
32
42
 
33
43
  def parseNumber(x):
@@ -60,7 +70,7 @@ class AstNode:
60
70
 
61
71
  def addExcInfo(self, exc):
62
72
  if 'highlight' not in exc.errinfo:
63
- exc.errinfo['highlight'] = self.getPosInfo()
73
+ exc.set('highlight', self.getPosInfo())
64
74
  return exc
65
75
 
66
76
  def repr(self):
@@ -124,27 +134,27 @@ class AstNode:
124
134
  pass
125
135
 
126
136
  def hasAstClass(self, clss):
127
- hasast = self.hasast.get(clss)
128
- if hasast is not None:
137
+ if (hasast := self.hasast.get(clss)) is not None:
129
138
  return hasast
130
139
 
131
- retn = False
140
+ retn = self._hasAstClass(clss)
141
+ self.hasast[clss] = retn
142
+ return retn
143
+
144
+ def _hasAstClass(self, clss):
132
145
 
133
146
  for kid in self.kids:
134
147
 
135
148
  if isinstance(kid, clss):
136
- retn = True
137
- break
149
+ return True
138
150
 
139
- if isinstance(kid, (EditPropSet, Function, CmdOper)):
151
+ if isinstance(kid, (Edit, Function, CmdOper, SetVarOper, SetItemOper, VarListSetOper, Value, N1Walk, LiftOper)):
140
152
  continue
141
153
 
142
154
  if kid.hasAstClass(clss):
143
- retn = True
144
- break
155
+ return True
145
156
 
146
- self.hasast[clss] = retn
147
- return retn
157
+ return False
148
158
 
149
159
  def optimize(self):
150
160
  [k.optimize() for k in self.kids]
@@ -179,6 +189,12 @@ class AstNode:
179
189
 
180
190
  todo.extend(nkid.kids)
181
191
 
192
+ def reqNotReadOnly(self, runt, mesg=None):
193
+ if runt.readonly:
194
+ if mesg is None:
195
+ mesg = 'Storm runtime is in readonly mode, cannot create or edit nodes and other graph data.'
196
+ raise self.addExcInfo(s_exc.IsReadOnly(mesg=mesg))
197
+
182
198
  def hasVarName(self, name):
183
199
  return any(k.hasVarName(name) for k in self.kids)
184
200
 
@@ -238,9 +254,8 @@ class Lookup(Query):
238
254
 
239
255
  async def run(self, runt, genr):
240
256
 
241
- if runt.readonly and self.autoadd:
242
- mesg = 'Autoadd may not be executed in readonly Storm runtime.'
243
- raise self.addExcInfo(s_exc.IsReadOnly(mesg=mesg))
257
+ if self.autoadd:
258
+ self.reqNotReadOnly(runt)
244
259
 
245
260
  async def getnode(form, valu):
246
261
  try:
@@ -915,6 +930,9 @@ class TryCatch(AstNode):
915
930
 
916
931
  class CatchBlock(AstNode):
917
932
 
933
+ def _hasAstClass(self, clss):
934
+ return self.kids[1].hasAstClass(clss)
935
+
918
936
  async def run(self, runt, genr):
919
937
  async for item in self.kids[2].run(runt, genr):
920
938
  yield item
@@ -948,6 +966,9 @@ class CatchBlock(AstNode):
948
966
 
949
967
  class ForLoop(Oper):
950
968
 
969
+ def _hasAstClass(self, clss):
970
+ return self.kids[2].hasAstClass(clss)
971
+
951
972
  def getRuntVars(self, runt):
952
973
 
953
974
  runtsafe = self.kids[1].isRuntSafe(runt)
@@ -983,6 +1004,14 @@ class ForLoop(Oper):
983
1004
  valu = ()
984
1005
 
985
1006
  async with contextlib.aclosing(s_coro.agen(valu)) as agen:
1007
+
1008
+ try:
1009
+ agen, _ = await pullone(agen)
1010
+ except TypeError:
1011
+ styp = await s_stormtypes.totype(valu, basetypes=True)
1012
+ mesg = f"'{styp}' object is not iterable: {s_common.trimText(repr(valu))}"
1013
+ raise self.kids[1].addExcInfo(s_exc.StormRuntimeError(mesg=mesg, type=styp)) from None
1014
+
986
1015
  async for item in agen:
987
1016
 
988
1017
  if isinstance(name, (list, tuple)):
@@ -1020,13 +1049,13 @@ class ForLoop(Oper):
1020
1049
  yield item
1021
1050
 
1022
1051
  except s_stormctrl.StormBreak as e:
1023
- if e.item is not None:
1024
- yield e.item
1052
+ if (eitem := e.get('item')) is not None:
1053
+ yield eitem
1025
1054
  break
1026
1055
 
1027
1056
  except s_stormctrl.StormContinue as e:
1028
- if e.item is not None:
1029
- yield e.item
1057
+ if (eitem := e.get('item')) is not None:
1058
+ yield eitem
1030
1059
  continue
1031
1060
 
1032
1061
  finally:
@@ -1049,6 +1078,13 @@ class ForLoop(Oper):
1049
1078
  valu = ()
1050
1079
 
1051
1080
  async with contextlib.aclosing(s_coro.agen(valu)) as agen:
1081
+ try:
1082
+ agen, _ = await pullone(agen)
1083
+ except TypeError:
1084
+ styp = await s_stormtypes.totype(valu, basetypes=True)
1085
+ mesg = f"'{styp}' object is not iterable: {s_common.trimText(repr(valu))}"
1086
+ raise self.kids[1].addExcInfo(s_exc.StormRuntimeError(mesg=mesg, type=styp)) from None
1087
+
1052
1088
  async for item in agen:
1053
1089
 
1054
1090
  if isinstance(name, (list, tuple)):
@@ -1079,13 +1115,13 @@ class ForLoop(Oper):
1079
1115
  yield jtem
1080
1116
 
1081
1117
  except s_stormctrl.StormBreak as e:
1082
- if e.item is not None:
1083
- yield e.item
1118
+ if (eitem := e.get('item')) is not None:
1119
+ yield eitem
1084
1120
  break
1085
1121
 
1086
1122
  except s_stormctrl.StormContinue as e:
1087
- if e.item is not None:
1088
- yield e.item
1123
+ if (eitem := e.get('item')) is not None:
1124
+ yield eitem
1089
1125
  continue
1090
1126
 
1091
1127
  finally:
@@ -1094,6 +1130,9 @@ class ForLoop(Oper):
1094
1130
 
1095
1131
  class WhileLoop(Oper):
1096
1132
 
1133
+ def _hasAstClass(self, clss):
1134
+ return self.kids[1].hasAstClass(clss)
1135
+
1097
1136
  async def run(self, runt, genr):
1098
1137
  subq = self.kids[1]
1099
1138
  node = None
@@ -1109,13 +1148,13 @@ class WhileLoop(Oper):
1109
1148
  await asyncio.sleep(0)
1110
1149
 
1111
1150
  except s_stormctrl.StormBreak as e:
1112
- if e.item is not None:
1113
- yield e.item
1151
+ if (eitem := e.get('item')) is not None:
1152
+ yield eitem
1114
1153
  break
1115
1154
 
1116
1155
  except s_stormctrl.StormContinue as e:
1117
- if e.item is not None:
1118
- yield e.item
1156
+ if (eitem := e.get('item')) is not None:
1157
+ yield eitem
1119
1158
  continue
1120
1159
 
1121
1160
  finally:
@@ -1133,13 +1172,13 @@ class WhileLoop(Oper):
1133
1172
  await asyncio.sleep(0)
1134
1173
 
1135
1174
  except s_stormctrl.StormBreak as e:
1136
- if e.item is not None:
1137
- yield e.item
1175
+ if (eitem := e.get('item')) is not None:
1176
+ yield eitem
1138
1177
  break
1139
1178
 
1140
1179
  except s_stormctrl.StormContinue as e:
1141
- if e.item is not None:
1142
- yield e.item
1180
+ if (eitem := e.get('item')) is not None:
1181
+ yield eitem
1143
1182
  continue
1144
1183
 
1145
1184
  finally:
@@ -1147,20 +1186,21 @@ class WhileLoop(Oper):
1147
1186
  await asyncio.sleep(0)
1148
1187
 
1149
1188
  async def pullone(genr):
1150
- gotone = None
1151
- async for gotone in genr:
1152
- break
1189
+ empty = False
1190
+ try:
1191
+ gotone = await genr.__anext__()
1192
+ except StopAsyncIteration:
1193
+ empty = True
1153
1194
 
1154
1195
  async def pullgenr():
1155
-
1156
- if gotone is None:
1196
+ if empty:
1157
1197
  return
1158
1198
 
1159
1199
  yield gotone
1160
1200
  async for item in genr:
1161
1201
  yield item
1162
1202
 
1163
- return pullgenr(), gotone is None
1203
+ return pullgenr(), empty
1164
1204
 
1165
1205
  class CmdOper(Oper):
1166
1206
 
@@ -1269,15 +1309,17 @@ class SetItemOper(Oper):
1269
1309
  item = s_stormtypes.fromprim(await self.kids[0].compute(runt, path), basetypes=False)
1270
1310
 
1271
1311
  if runt.readonly and not getattr(item.setitem, '_storm_readonly', False):
1272
- mesg = 'Storm runtime is in readonly mode, cannot create or edit nodes and other graph data.'
1273
- raise self.kids[0].addExcInfo(s_exc.IsReadOnly(mesg=mesg))
1312
+ self.kids[0].reqNotReadOnly(runt)
1274
1313
 
1275
1314
  name = await self.kids[1].compute(runt, path)
1276
1315
  valu = await self.kids[2].compute(runt, path)
1277
1316
 
1278
1317
  # TODO: ditch this when storm goes full heavy object
1279
1318
  with s_scope.enter({'runt': runt}):
1280
- await item.setitem(name, valu)
1319
+ try:
1320
+ await item.setitem(name, valu)
1321
+ except s_exc.SynErr as e:
1322
+ raise self.kids[0].addExcInfo(e)
1281
1323
 
1282
1324
  yield node, path
1283
1325
 
@@ -1289,12 +1331,14 @@ class SetItemOper(Oper):
1289
1331
  valu = await self.kids[2].compute(runt, None)
1290
1332
 
1291
1333
  if runt.readonly and not getattr(item.setitem, '_storm_readonly', False):
1292
- mesg = 'Storm runtime is in readonly mode, cannot create or edit nodes and other graph data.'
1293
- raise self.kids[0].addExcInfo(s_exc.IsReadOnly(mesg=mesg))
1334
+ self.kids[0].reqNotReadOnly(runt)
1294
1335
 
1295
1336
  # TODO: ditch this when storm goes full heavy object
1296
1337
  with s_scope.enter({'runt': runt}):
1297
- await item.setitem(name, valu)
1338
+ try:
1339
+ await item.setitem(name, valu)
1340
+ except s_exc.SynErr as e:
1341
+ raise self.kids[0].addExcInfo(e)
1298
1342
 
1299
1343
  class VarListSetOper(Oper):
1300
1344
 
@@ -1366,6 +1410,14 @@ class VarEvalOper(Oper):
1366
1410
 
1367
1411
  class SwitchCase(Oper):
1368
1412
 
1413
+ def _hasAstClass(self, clss):
1414
+
1415
+ for kid in self.kids[1:]:
1416
+ if kid.hasAstClass(clss):
1417
+ return True
1418
+
1419
+ return False
1420
+
1369
1421
  def prepare(self):
1370
1422
  self.cases = {}
1371
1423
  self.defcase = None
@@ -3572,17 +3624,31 @@ class FuncCall(Value):
3572
3624
  raise self.addExcInfo(s_exc.StormRuntimeError(mesg=mesg))
3573
3625
 
3574
3626
  if runt.readonly and not getattr(func, '_storm_readonly', False):
3575
- mesg = f'Function ({func.__name__}) is not marked readonly safe.'
3627
+ funcname = getattr(func, '_storm_funcpath', func.__name__)
3628
+ mesg = f'{funcname}() is not marked readonly safe.'
3576
3629
  raise self.kids[0].addExcInfo(s_exc.IsReadOnly(mesg=mesg))
3577
3630
 
3578
3631
  argv = await self.kids[1].compute(runt, path)
3579
3632
  kwargs = {k: v for (k, v) in await self.kids[2].compute(runt, path)}
3580
3633
 
3581
3634
  with s_scope.enter({'runt': runt}):
3582
- retn = func(*argv, **kwargs)
3583
- if s_coro.iscoro(retn):
3584
- return await retn
3585
- return retn
3635
+ try:
3636
+ retn = func(*argv, **kwargs)
3637
+ if s_coro.iscoro(retn):
3638
+ return await retn
3639
+ return retn
3640
+
3641
+ except TypeError as e:
3642
+ mesg = str(e)
3643
+ if (funcpath := getattr(func, '_storm_funcpath', None)) is not None:
3644
+ mesg = f"{funcpath}(){mesg.split(')', 1)[1]}"
3645
+
3646
+ raise self.addExcInfo(s_exc.StormRuntimeError(mesg=mesg))
3647
+
3648
+ except s_exc.SynErr as e:
3649
+ if getattr(func, '_storm_runtime_lib_func', None) is not None:
3650
+ e.errinfo.pop('highlight', None)
3651
+ raise self.addExcInfo(e)
3586
3652
 
3587
3653
  class DollarExpr(Value):
3588
3654
  '''
@@ -3985,9 +4051,7 @@ class EditParens(Edit):
3985
4051
 
3986
4052
  async def run(self, runt, genr):
3987
4053
 
3988
- if runt.readonly:
3989
- mesg = 'Storm runtime is in readonly mode, cannot create or edit nodes and other graph data.'
3990
- raise self.addExcInfo(s_exc.IsReadOnly(mesg=mesg))
4054
+ self.reqNotReadOnly(runt)
3991
4055
 
3992
4056
  nodeadd = self.kids[0]
3993
4057
  assert isinstance(nodeadd, EditNodeAdd)
@@ -4080,9 +4144,7 @@ class EditNodeAdd(Edit):
4080
4144
  # case 2: <query> [ foo:bar=($node, 20) ]
4081
4145
  # case 2: <query> $blah=:baz [ foo:bar=($blah, 20) ]
4082
4146
 
4083
- if runt.readonly:
4084
- mesg = 'Storm runtime is in readonly mode, cannot create or edit nodes and other graph data.'
4085
- raise self.addExcInfo(s_exc.IsReadOnly(mesg=mesg))
4147
+ self.reqNotReadOnly(runt)
4086
4148
 
4087
4149
  runtsafe = self.isRuntSafe(runt)
4088
4150
 
@@ -4090,7 +4152,6 @@ class EditNodeAdd(Edit):
4090
4152
 
4091
4153
  if not runtsafe:
4092
4154
 
4093
- first = True
4094
4155
  async for node, path in genr:
4095
4156
 
4096
4157
  # must reach back first to trigger sudo / etc
@@ -4148,13 +4209,79 @@ class EditNodeAdd(Edit):
4148
4209
  async for item in agen:
4149
4210
  yield item
4150
4211
 
4212
+ class CondSetOper(Oper):
4213
+ def __init__(self, astinfo, kids, errok=False):
4214
+ Value.__init__(self, astinfo, kids=kids)
4215
+ self.errok = errok
4216
+
4217
+ def prepare(self):
4218
+ self.isconst = False
4219
+ if isinstance(self.kids[0], Const):
4220
+ self.isconst = True
4221
+ self.valu = COND_EDIT_SET.get(self.kids[0].value())
4222
+
4223
+ async def compute(self, runt, path):
4224
+ if self.isconst:
4225
+ return self.valu
4226
+
4227
+ valu = await self.kids[0].compute(runt, path)
4228
+ if (retn := COND_EDIT_SET.get(valu)) is not None:
4229
+ return retn
4230
+
4231
+ mesg = f'Invalid conditional set operator ({valu}).'
4232
+ exc = s_exc.StormRuntimeError(mesg=mesg)
4233
+ raise self.addExcInfo(exc)
4234
+
4235
+ class EditCondPropSet(Edit):
4236
+
4237
+ async def run(self, runt, genr):
4238
+
4239
+ self.reqNotReadOnly(runt)
4240
+
4241
+ excignore = (s_exc.BadTypeValu,) if self.kids[1].errok else ()
4242
+ rval = self.kids[2]
4243
+
4244
+ async for node, path in genr:
4245
+
4246
+ propname = await self.kids[0].compute(runt, path)
4247
+ name = await tostr(propname)
4248
+
4249
+ prop = node.form.reqProp(name, extra=self.kids[0].addExcInfo)
4250
+
4251
+ oper = await self.kids[1].compute(runt, path)
4252
+ if oper == SET_NEVER or (oper == SET_UNSET and (oldv := node.get(name)) is not None):
4253
+ yield node, path
4254
+ await asyncio.sleep(0)
4255
+ continue
4256
+
4257
+ if not node.form.isrunt:
4258
+ # runt node property permissions are enforced by the callback
4259
+ runt.confirmPropSet(prop)
4260
+
4261
+ isndef = isinstance(prop.type, s_types.Ndef)
4262
+
4263
+ try:
4264
+ valu = await rval.compute(runt, path)
4265
+ valu = await s_stormtypes.tostor(valu, isndef=isndef)
4266
+
4267
+ if isinstance(prop.type, s_types.Ival) and oldv is not None:
4268
+ valu, _ = prop.type.norm(valu)
4269
+ valu = prop.type.merge(oldv, valu)
4270
+
4271
+ await node.set(name, valu)
4272
+
4273
+ except excignore:
4274
+ pass
4275
+
4276
+ yield node, path
4277
+
4278
+ await asyncio.sleep(0)
4279
+
4151
4280
  class EditPropSet(Edit):
4152
4281
 
4153
4282
  async def run(self, runt, genr):
4154
4283
 
4155
- if runt.readonly:
4156
- mesg = 'Storm runtime is in readonly mode, cannot create or edit nodes and other graph data.'
4157
- raise self.addExcInfo(s_exc.IsReadOnly(mesg=mesg))
4284
+ self.reqNotReadOnly(runt)
4158
4285
 
4159
4286
  oper = await self.kids[1].compute(runt, None)
4160
4287
  excignore = (s_exc.BadTypeValu,) if oper in ('?=', '?+=', '?-=') else ()
@@ -4199,7 +4326,7 @@ class EditPropSet(Edit):
4199
4326
 
4200
4327
  if not isarray:
4201
4328
  mesg = f'Property set using ({oper}) is only valid on arrays.'
4202
- exc = s_exc.StormRuntimeError(mesg)
4329
+ exc = s_exc.StormRuntimeError(mesg=mesg)
4203
4330
  raise self.kids[0].addExcInfo(exc)
4204
4331
 
4205
4332
  arry = node.get(name)
@@ -4247,9 +4374,7 @@ class EditPropDel(Edit):
4247
4374
 
4248
4375
  async def run(self, runt, genr):
4249
4376
 
4250
- if runt.readonly:
4251
- mesg = 'Storm runtime is in readonly mode, cannot create or edit nodes and other graph data.'
4252
- raise self.addExcInfo(s_exc.IsReadOnly(mesg=mesg))
4377
+ self.reqNotReadOnly(runt)
4253
4378
 
4254
4379
  async for node, path in genr:
4255
4380
  propname = await self.kids[0].compute(runt, path)
@@ -4275,9 +4400,7 @@ class EditUnivDel(Edit):
4275
4400
 
4276
4401
  async def run(self, runt, genr):
4277
4402
 
4278
- if runt.readonly:
4279
- mesg = 'Storm runtime is in readonly mode, cannot create or edit nodes and other graph data.'
4280
- raise self.addExcInfo(s_exc.IsReadOnly(mesg=mesg))
4403
+ self.reqNotReadOnly(runt)
4281
4404
 
4282
4405
  univprop = self.kids[0]
4283
4406
  assert isinstance(univprop, UnivProp)
@@ -4453,9 +4576,7 @@ class EditEdgeAdd(Edit):
4453
4576
 
4454
4577
  async def run(self, runt, genr):
4455
4578
 
4456
- if runt.readonly:
4457
- mesg = 'Storm runtime is in readonly mode, cannot create or edit nodes and other graph data.'
4458
- raise self.addExcInfo(s_exc.IsReadOnly(mesg=mesg))
4579
+ self.reqNotReadOnly(runt)
4459
4580
 
4460
4581
  # SubQuery -> Query
4461
4582
  query = self.kids[1].kids[0]
@@ -4518,9 +4639,7 @@ class EditEdgeDel(Edit):
4518
4639
 
4519
4640
  async def run(self, runt, genr):
4520
4641
 
4521
- if runt.readonly:
4522
- mesg = 'Storm runtime is in readonly mode, cannot create or edit nodes and other graph data.'
4523
- raise self.addExcInfo(s_exc.IsReadOnly(mesg=mesg))
4642
+ self.reqNotReadOnly(runt)
4524
4643
 
4525
4644
  query = self.kids[1].kids[0]
4526
4645
 
@@ -4576,9 +4695,7 @@ class EditTagAdd(Edit):
4576
4695
 
4577
4696
  async def run(self, runt, genr):
4578
4697
 
4579
- if runt.readonly:
4580
- mesg = 'Storm runtime is in readonly mode, cannot create or edit nodes and other graph data.'
4581
- raise self.addExcInfo(s_exc.IsReadOnly(mesg=mesg))
4698
+ self.reqNotReadOnly(runt)
4582
4699
 
4583
4700
  if len(self.kids) > 1 and isinstance(self.kids[0], Const) and (await self.kids[0].compute(runt, None)) == '?':
4584
4701
  oper_offset = 1
@@ -4622,9 +4739,7 @@ class EditTagDel(Edit):
4622
4739
 
4623
4740
  async def run(self, runt, genr):
4624
4741
 
4625
- if runt.readonly:
4626
- mesg = 'Storm runtime is in readonly mode, cannot create or edit nodes and other graph data.'
4627
- raise self.addExcInfo(s_exc.IsReadOnly(mesg=mesg))
4742
+ self.reqNotReadOnly(runt)
4628
4743
 
4629
4744
  async for node, path in genr:
4630
4745
 
@@ -4648,9 +4763,7 @@ class EditTagPropSet(Edit):
4648
4763
  '''
4649
4764
  async def run(self, runt, genr):
4650
4765
 
4651
- if runt.readonly:
4652
- mesg = 'Storm runtime is in readonly mode, cannot create or edit nodes and other graph data.'
4653
- raise self.addExcInfo(s_exc.IsReadOnly(mesg=mesg))
4766
+ self.reqNotReadOnly(runt)
4654
4767
 
4655
4768
  oper = await self.kids[1].compute(runt, None)
4656
4769
  excignore = s_exc.BadTypeValu if oper == '?=' else ()
@@ -4684,9 +4797,7 @@ class EditTagPropDel(Edit):
4684
4797
  '''
4685
4798
  async def run(self, runt, genr):
4686
4799
 
4687
- if runt.readonly:
4688
- mesg = 'Storm runtime is in readonly mode, cannot create or edit nodes and other graph data.'
4689
- raise self.addExcInfo(s_exc.IsReadOnly(mesg=mesg))
4800
+ self.reqNotReadOnly(runt)
4690
4801
 
4691
4802
  async for node, path in genr:
4692
4803
 
@@ -4711,9 +4822,9 @@ class BreakOper(AstNode):
4711
4822
  yield _
4712
4823
 
4713
4824
  async for node, path in genr:
4714
- raise s_stormctrl.StormBreak(item=(node, path))
4825
+ raise self.addExcInfo(s_stormctrl.StormBreak(item=(node, path)))
4715
4826
 
4716
- raise s_stormctrl.StormBreak()
4827
+ raise self.addExcInfo(s_stormctrl.StormBreak())
4717
4828
 
4718
4829
  class ContinueOper(AstNode):
4719
4830
 
@@ -4724,15 +4835,31 @@ class ContinueOper(AstNode):
4724
4835
  yield _
4725
4836
 
4726
4837
  async for node, path in genr:
4727
- raise s_stormctrl.StormContinue(item=(node, path))
4838
+ raise self.addExcInfo(s_stormctrl.StormContinue(item=(node, path)))
4728
4839
 
4729
- raise s_stormctrl.StormContinue()
4840
+ raise self.addExcInfo(s_stormctrl.StormContinue())
4730
4841
 
4731
4842
  class IfClause(AstNode):
4732
4843
  pass
4733
4844
 
4734
4845
  class IfStmt(Oper):
4735
4846
 
4847
+ def _hasAstClass(self, clss):
4848
+
4849
+ clauses = self.kids
4850
+
4851
+ if not isinstance(clauses[-1], IfClause):
4852
+ if clauses[-1].hasAstClass(clss):
4853
+ return True
4854
+
4855
+ clauses = clauses[:-1]
4856
+
4857
+ for clause in clauses:
4858
+ if clause.kids[1].hasAstClass(clss):
4859
+ return True
4860
+
4861
+ return False
4862
+
4736
4863
  def prepare(self):
4737
4864
  if isinstance(self.kids[-1], IfClause):
4738
4865
  self.elsequery = None
@@ -4817,20 +4944,26 @@ class Emit(Oper):
4817
4944
  count = 0
4818
4945
  async for node, path in genr:
4819
4946
  count += 1
4820
- await runt.emit(await self.kids[0].compute(runt, path))
4947
+ try:
4948
+ await runt.emit(await self.kids[0].compute(runt, path))
4949
+ except s_exc.StormRuntimeError as e:
4950
+ raise self.addExcInfo(e)
4821
4951
  yield node, path
4822
4952
 
4823
4953
  # no items in pipeline and runtsafe. execute once.
4824
4954
  if count == 0 and self.isRuntSafe(runt):
4825
- await runt.emit(await self.kids[0].compute(runt, None))
4955
+ try:
4956
+ await runt.emit(await self.kids[0].compute(runt, None))
4957
+ except s_exc.StormRuntimeError as e:
4958
+ raise self.addExcInfo(e)
4826
4959
 
4827
4960
  class Stop(Oper):
4828
4961
 
4829
4962
  async def run(self, runt, genr):
4830
4963
  for _ in (): yield _
4831
4964
  async for node, path in genr:
4832
- raise s_stormctrl.StormStop()
4833
- raise s_stormctrl.StormStop()
4965
+ raise self.addExcInfo(s_stormctrl.StormStop())
4966
+ raise self.addExcInfo(s_stormctrl.StormStop())
4834
4967
 
4835
4968
  class FuncArgs(AstNode):
4836
4969
  '''
@@ -4891,8 +5024,9 @@ class Function(AstNode):
4891
5024
 
4892
5025
  @s_stormtypes.stormfunc(readonly=True)
4893
5026
  async def realfunc(*args, **kwargs):
4894
- return await self.callfunc(runt, argdefs, args, kwargs)
5027
+ return await self.callfunc(runt, argdefs, args, kwargs, realfunc._storm_funcpath)
4895
5028
 
5029
+ realfunc._storm_funcpath = self.name
4896
5030
  await runt.setVar(self.name, realfunc)
4897
5031
 
4898
5032
  count = 0
@@ -4914,7 +5048,7 @@ class Function(AstNode):
4914
5048
  # var scope validation occurs in the sub-runtime
4915
5049
  pass
4916
5050
 
4917
- async def callfunc(self, runt, argdefs, args, kwargs):
5051
+ async def callfunc(self, runt, argdefs, args, kwargs, funcpath):
4918
5052
  '''
4919
5053
  Execute a function call using the given runtime.
4920
5054
 
@@ -4925,7 +5059,7 @@ class Function(AstNode):
4925
5059
 
4926
5060
  argcount = len(args) + len(kwargs)
4927
5061
  if argcount > len(argdefs):
4928
- mesg = f'{self.name}() takes {len(argdefs)} arguments but {argcount} were provided'
5062
+ mesg = f'{funcpath}() takes {len(argdefs)} arguments but {argcount} were provided'
4929
5063
  raise self.kids[1].addExcInfo(s_exc.StormRuntimeError(mesg=mesg))
4930
5064
 
4931
5065
  # Fill in the positional arguments
@@ -4939,7 +5073,7 @@ class Function(AstNode):
4939
5073
  valu = kwargs.pop(name, s_common.novalu)
4940
5074
  if valu is s_common.novalu:
4941
5075
  if defv is s_common.novalu:
4942
- mesg = f'{self.name}() missing required argument {name}'
5076
+ mesg = f'{funcpath}() missing required argument {name}'
4943
5077
  raise self.kids[1].addExcInfo(s_exc.StormRuntimeError(mesg=mesg))
4944
5078
  valu = defv
4945
5079
 
@@ -4950,11 +5084,11 @@ class Function(AstNode):
4950
5084
  # used a kwarg not defined.
4951
5085
  kwkeys = list(kwargs.keys())
4952
5086
  if kwkeys[0] in posnames:
4953
- mesg = f'{self.name}() got multiple values for parameter {kwkeys[0]}'
5087
+ mesg = f'{funcpath}() got multiple values for parameter {kwkeys[0]}'
4954
5088
  raise self.kids[1].addExcInfo(s_exc.StormRuntimeError(mesg=mesg))
4955
5089
 
4956
5090
  plural = 's' if len(kwargs) > 1 else ''
4957
- mesg = f'{self.name}() got unexpected keyword argument{plural}: {",".join(kwkeys)}'
5091
+ mesg = f'{funcpath}() got unexpected keyword argument{plural}: {",".join(kwkeys)}'
4958
5092
  raise self.kids[1].addExcInfo(s_exc.StormRuntimeError(mesg=mesg))
4959
5093
 
4960
5094
  assert len(mergargs) == len(argdefs)
@@ -4973,9 +5107,16 @@ class Function(AstNode):
4973
5107
  await asyncio.sleep(0)
4974
5108
 
4975
5109
  return None
4976
-
4977
5110
  except s_stormctrl.StormReturn as e:
4978
5111
  return e.item
5112
+ except s_stormctrl.StormLoopCtrl as e:
5113
+ mesg = f'function {self.name} - Loop control statement "{e.statement}" used outside of a loop.'
5114
+ raise self.addExcInfo(s_exc.StormRuntimeError(mesg=mesg, function=self.name,
5115
+ statement=e.statement)) from e
5116
+ except s_stormctrl.StormGenrCtrl as e:
5117
+ mesg = f'function {self.name} - Generator control statement "{e.statement}" used outside of a generator function.'
5118
+ raise self.addExcInfo(s_exc.StormRuntimeError(mesg=mesg, function=self.name,
5119
+ statement=e.statement)) from e
4979
5120
 
4980
5121
  async def genr():
4981
5122
  async with runt.getSubRuntime(self.kids[2], opts=opts) as subr:
@@ -4995,5 +5136,9 @@ class Function(AstNode):
4995
5136
  yield node, path
4996
5137
  except s_stormctrl.StormStop:
4997
5138
  return
5139
+ except s_stormctrl.StormLoopCtrl as e:
5140
+ mesg = f'function {self.name} - Loop control statement "{e.statement}" used outside of a loop.'
5141
+ raise self.addExcInfo(s_exc.StormRuntimeError(mesg=mesg, function=self.name,
5142
+ statement=e.statement)) from e
4998
5143
 
4999
5144
  return genr()