synapse 2.154.1__py311-none-any.whl → 2.156.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 (74) hide show
  1. synapse/cmds/cortex.py +2 -14
  2. synapse/common.py +13 -36
  3. synapse/cortex.py +15 -508
  4. synapse/lib/ast.py +215 -22
  5. synapse/lib/cell.py +35 -8
  6. synapse/lib/certdir.py +11 -0
  7. synapse/lib/cmdr.py +0 -5
  8. synapse/lib/gis.py +2 -2
  9. synapse/lib/httpapi.py +14 -43
  10. synapse/lib/layer.py +64 -201
  11. synapse/lib/lmdbslab.py +11 -0
  12. synapse/lib/node.py +1 -3
  13. synapse/lib/parser.py +10 -0
  14. synapse/lib/slabseqn.py +2 -1
  15. synapse/lib/snap.py +121 -21
  16. synapse/lib/spooled.py +9 -0
  17. synapse/lib/storm.lark +23 -6
  18. synapse/lib/storm.py +16 -339
  19. synapse/lib/storm_format.py +5 -0
  20. synapse/lib/stormhttp.py +10 -1
  21. synapse/lib/stormlib/gen.py +1 -2
  22. synapse/lib/stormlib/gis.py +41 -0
  23. synapse/lib/stormlib/graph.py +2 -1
  24. synapse/lib/stormlib/stats.py +21 -2
  25. synapse/lib/stormlib/storm.py +16 -1
  26. synapse/lib/stormtypes.py +244 -16
  27. synapse/lib/types.py +16 -2
  28. synapse/lib/version.py +2 -2
  29. synapse/lib/view.py +118 -25
  30. synapse/models/base.py +2 -2
  31. synapse/models/inet.py +60 -30
  32. synapse/models/infotech.py +130 -8
  33. synapse/models/orgs.py +3 -0
  34. synapse/models/proj.py +3 -0
  35. synapse/models/risk.py +24 -6
  36. synapse/models/syn.py +0 -38
  37. synapse/tests/test_cmds_cortex.py +1 -1
  38. synapse/tests/test_cortex.py +70 -338
  39. synapse/tests/test_lib_agenda.py +19 -54
  40. synapse/tests/test_lib_aha.py +97 -0
  41. synapse/tests/test_lib_ast.py +596 -0
  42. synapse/tests/test_lib_grammar.py +30 -10
  43. synapse/tests/test_lib_httpapi.py +33 -49
  44. synapse/tests/test_lib_layer.py +19 -234
  45. synapse/tests/test_lib_lmdbslab.py +22 -0
  46. synapse/tests/test_lib_snap.py +9 -0
  47. synapse/tests/test_lib_spooled.py +4 -0
  48. synapse/tests/test_lib_storm.py +16 -309
  49. synapse/tests/test_lib_stormlib_gis.py +21 -0
  50. synapse/tests/test_lib_stormlib_stats.py +107 -20
  51. synapse/tests/test_lib_stormlib_storm.py +25 -0
  52. synapse/tests/test_lib_stormtypes.py +253 -8
  53. synapse/tests/test_lib_types.py +40 -0
  54. synapse/tests/test_lib_view.py +6 -13
  55. synapse/tests/test_model_base.py +1 -1
  56. synapse/tests/test_model_inet.py +15 -0
  57. synapse/tests/test_model_infotech.py +110 -0
  58. synapse/tests/test_model_orgs.py +10 -0
  59. synapse/tests/test_model_person.py +0 -3
  60. synapse/tests/test_model_proj.py +2 -1
  61. synapse/tests/test_model_risk.py +24 -0
  62. synapse/tests/test_model_syn.py +20 -34
  63. synapse/tests/test_tools_csvtool.py +2 -1
  64. synapse/tests/test_tools_feed.py +4 -30
  65. synapse/tools/csvtool.py +2 -1
  66. {synapse-2.154.1.dist-info → synapse-2.156.0.dist-info}/METADATA +9 -9
  67. {synapse-2.154.1.dist-info → synapse-2.156.0.dist-info}/RECORD +70 -72
  68. {synapse-2.154.1.dist-info → synapse-2.156.0.dist-info}/WHEEL +1 -1
  69. synapse/cmds/cron.py +0 -726
  70. synapse/cmds/trigger.py +0 -319
  71. synapse/tests/test_cmds_cron.py +0 -453
  72. synapse/tests/test_cmds_trigger.py +0 -176
  73. {synapse-2.154.1.dist-info → synapse-2.156.0.dist-info}/LICENSE +0 -0
  74. {synapse-2.154.1.dist-info → synapse-2.156.0.dist-info}/top_level.txt +0 -0
synapse/lib/layer.py CHANGED
@@ -170,19 +170,6 @@ class LayerApi(s_cell.CellApi):
170
170
  async for item in self.layr.syncNodeEdits2(offs, wait=wait):
171
171
  yield item
172
172
 
173
- async def splices(self, offs=None, size=None):
174
- '''
175
- This API is deprecated.
176
-
177
- Yield (offs, splice) tuples from the nodeedit log starting from the given offset.
178
-
179
- Nodeedits will be flattened into splices before being yielded.
180
- '''
181
- s_common.deprdate('Layer.splices() telepath API', s_common._splicedepr)
182
- await self._reqUserAllowed(self.liftperm)
183
- async for item in self.layr.splices(offs=offs, size=size):
184
- yield item
185
-
186
173
  async def getEditIndx(self):
187
174
  '''
188
175
  Returns what will be the *next* nodeedit log index.
@@ -2023,17 +2010,14 @@ class Layer(s_nexus.Pusher):
2023
2010
  ret['totalsize'] = await self.getLayerSize()
2024
2011
  return ret
2025
2012
 
2026
- async def truncate(self):
2027
- self._reqNotReadOnly()
2028
- return await self._push('layer:truncate')
2029
-
2030
2013
  @s_nexus.Pusher.onPush('layer:truncate')
2031
2014
  async def _truncate(self):
2032
2015
  '''
2033
2016
  Nuke all the contents in the layer, leaving an empty layer
2034
2017
  NOTE: This internal API is deprecated but is kept for Nexus event backward compatibility
2035
2018
  '''
2036
- s_common.deprdate('Layer.truncate() API', s_common._splicedepr)
2019
+ # TODO: Remove this in 3.0.0
2020
+ s_common.deprecated('layer:truncate Nexus handler', curv='2.156.0')
2037
2021
 
2038
2022
  self.dirty.clear()
2039
2023
  self.buidcache.clear()
@@ -2044,8 +2028,6 @@ class Layer(s_nexus.Pusher):
2044
2028
 
2045
2029
  await self._initLayerStorage()
2046
2030
 
2047
- # async def wipe(self, meta): ...
2048
-
2049
2031
  async def iterWipeNodeEdits(self):
2050
2032
 
2051
2033
  await self._saveDirtySodes()
@@ -2914,6 +2896,7 @@ class Layer(s_nexus.Pusher):
2914
2896
  abrv = self.tagabrv.bytsToAbrv(tagname.encode())
2915
2897
  if formname is not None:
2916
2898
  abrv += self.getPropAbrv(formname, None)
2899
+ return self.layrslab.count(abrv, db=self.bytag)
2917
2900
 
2918
2901
  except s_exc.NoSuchAbrv:
2919
2902
  return 0
@@ -2931,6 +2914,44 @@ class Layer(s_nexus.Pusher):
2931
2914
 
2932
2915
  return await self.layrslab.countByPref(abrv, db=self.byprop, maxsize=maxsize)
2933
2916
 
2917
+ def getPropValuCount(self, formname, propname, stortype, valu):
2918
+ try:
2919
+ abrv = self.getPropAbrv(formname, propname)
2920
+ except s_exc.NoSuchAbrv:
2921
+ return 0
2922
+
2923
+ if stortype & 0x8000:
2924
+ stortype = STOR_TYPE_MSGP
2925
+
2926
+ count = 0
2927
+ for indx in self.getStorIndx(stortype, valu):
2928
+ count += self.layrslab.count(abrv + indx, db=self.byprop)
2929
+
2930
+ return count
2931
+
2932
+ async def getPropArrayCount(self, formname, propname=None):
2933
+ '''
2934
+ Return the number of invidiual value rows in the layer for the given array form/prop.
2935
+ '''
2936
+ try:
2937
+ abrv = self.getPropAbrv(formname, propname)
2938
+ except s_exc.NoSuchAbrv:
2939
+ return 0
2940
+
2941
+ return await self.layrslab.countByPref(abrv, db=self.byarray)
2942
+
2943
+ def getPropArrayValuCount(self, formname, propname, stortype, valu):
2944
+ try:
2945
+ abrv = self.getPropAbrv(formname, propname)
2946
+ except s_exc.NoSuchAbrv:
2947
+ return 0
2948
+
2949
+ count = 0
2950
+ for indx in self.getStorIndx(stortype, valu):
2951
+ count += self.layrslab.count(abrv + indx, db=self.byarray)
2952
+
2953
+ return count
2954
+
2934
2955
  async def getUnivPropCount(self, propname, maxsize=None):
2935
2956
  '''
2936
2957
  Return the number of universal property rows in the layer for the given prop.
@@ -2942,6 +2963,29 @@ class Layer(s_nexus.Pusher):
2942
2963
 
2943
2964
  return await self.layrslab.countByPref(abrv, db=self.byprop, maxsize=maxsize)
2944
2965
 
2966
+ async def getTagPropCount(self, form, tag, prop):
2967
+ '''
2968
+ Return the number of property rows in the layer for the given form/tag/prop.
2969
+ '''
2970
+ try:
2971
+ abrv = self.getTagPropAbrv(form, tag, prop)
2972
+ except s_exc.NoSuchAbrv:
2973
+ return 0
2974
+
2975
+ return await self.layrslab.countByPref(abrv, db=self.bytagprop)
2976
+
2977
+ def getTagPropValuCount(self, form, tag, prop, stortype, valu):
2978
+ try:
2979
+ abrv = self.getTagPropAbrv(form, tag, prop)
2980
+ except s_exc.NoSuchAbrv:
2981
+ return 0
2982
+
2983
+ count = 0
2984
+ for indx in self.getStorIndx(stortype, valu):
2985
+ count += self.layrslab.count(abrv + indx, db=self.bytagprop)
2986
+
2987
+ return count
2988
+
2945
2989
  async def liftByTag(self, tag, form=None, reverse=False):
2946
2990
 
2947
2991
  try:
@@ -4151,76 +4195,6 @@ class Layer(s_nexus.Pusher):
4151
4195
  yield buid, s_msgpack.un(byts)
4152
4196
  await asyncio.sleep(0)
4153
4197
 
4154
- async def splices(self, offs=None, size=None):
4155
- '''
4156
- This API is deprecated.
4157
-
4158
- Yield (offs, splice) tuples from the nodeedit log starting from the given offset.
4159
-
4160
- Nodeedits will be flattened into splices before being yielded.
4161
- '''
4162
- s_common.deprdate('Layer.splices() API', s_common._splicedepr)
4163
- if not self.logedits:
4164
- return
4165
-
4166
- if offs is None:
4167
- offs = (0, 0, 0)
4168
-
4169
- if size is not None:
4170
-
4171
- count = 0
4172
- async for offset, nodeedits, meta in self.iterNodeEditLog(offs[0]):
4173
- async for splice in self.makeSplices(offset, nodeedits, meta):
4174
-
4175
- if splice[0] < offs:
4176
- continue
4177
-
4178
- if count >= size:
4179
- return
4180
-
4181
- yield splice
4182
- count = count + 1
4183
- else:
4184
- async for offset, nodeedits, meta in self.iterNodeEditLog(offs[0]):
4185
- async for splice in self.makeSplices(offset, nodeedits, meta):
4186
-
4187
- if splice[0] < offs:
4188
- continue
4189
-
4190
- yield splice
4191
-
4192
- async def splicesBack(self, offs=None, size=None):
4193
-
4194
- s_common.deprdate('Layer.splicesBack() API', s_common._splicedepr)
4195
- if not self.logedits:
4196
- return
4197
-
4198
- if offs is None:
4199
- offs = (await self.getEditIndx(), 0, 0)
4200
-
4201
- if size is not None:
4202
-
4203
- count = 0
4204
- async for offset, nodeedits, meta in self.iterNodeEditLogBack(offs[0]):
4205
- async for splice in self.makeSplices(offset, nodeedits, meta, reverse=True):
4206
-
4207
- if splice[0] > offs:
4208
- continue
4209
-
4210
- if count >= size:
4211
- return
4212
-
4213
- yield splice
4214
- count += 1
4215
- else:
4216
- async for offset, nodeedits, meta in self.iterNodeEditLogBack(offs[0]):
4217
- async for splice in self.makeSplices(offset, nodeedits, meta, reverse=True):
4218
-
4219
- if splice[0] > offs:
4220
- continue
4221
-
4222
- yield splice
4223
-
4224
4198
  async def iterNodeEditLog(self, offs=0):
4225
4199
  '''
4226
4200
  Iterate the node edit log and yield (offs, edits, meta) tuples.
@@ -4312,117 +4286,6 @@ class Layer(s_nexus.Pusher):
4312
4286
  if count % 1000 == 0:
4313
4287
  yield (curoff, (None, None, EDIT_PROGRESS, (), ()))
4314
4288
 
4315
- async def makeSplices(self, offs, nodeedits, meta, reverse=False):
4316
- '''
4317
- Flatten a set of nodeedits into splices.
4318
- '''
4319
- if meta is None:
4320
- meta = {}
4321
-
4322
- user = meta.get('user')
4323
- time = meta.get('time')
4324
- prov = meta.get('prov')
4325
-
4326
- if reverse:
4327
- nodegenr = reversed(list(enumerate(nodeedits)))
4328
- else:
4329
- nodegenr = enumerate(nodeedits)
4330
-
4331
- for nodeoffs, (buid, form, edits) in nodegenr:
4332
-
4333
- formvalu = None
4334
-
4335
- if reverse:
4336
- editgenr = reversed(list(enumerate(edits)))
4337
- else:
4338
- editgenr = enumerate(edits)
4339
-
4340
- for editoffs, (edit, info, _) in editgenr:
4341
-
4342
- if edit in (EDIT_NODEDATA_SET, EDIT_NODEDATA_DEL, EDIT_EDGE_ADD, EDIT_EDGE_DEL):
4343
- continue
4344
-
4345
- spliceoffs = (offs, nodeoffs, editoffs)
4346
-
4347
- props = {
4348
- 'time': time,
4349
- 'user': user,
4350
- }
4351
-
4352
- if prov is not None:
4353
- props['prov'] = prov
4354
-
4355
- if edit == EDIT_NODE_ADD:
4356
- formvalu, stortype = info
4357
- props['ndef'] = (form, formvalu)
4358
-
4359
- yield (spliceoffs, ('node:add', props))
4360
- continue
4361
-
4362
- if edit == EDIT_NODE_DEL:
4363
- formvalu, stortype = info
4364
- props['ndef'] = (form, formvalu)
4365
-
4366
- yield (spliceoffs, ('node:del', props))
4367
- continue
4368
-
4369
- if formvalu is None:
4370
- formvalu = await self.getNodeValu(buid)
4371
-
4372
- props['ndef'] = (form, formvalu)
4373
-
4374
- if edit == EDIT_PROP_SET:
4375
- prop, valu, oldv, stortype = info
4376
- props['prop'] = prop
4377
- props['valu'] = valu
4378
- props['oldv'] = oldv
4379
-
4380
- yield (spliceoffs, ('prop:set', props))
4381
- continue
4382
-
4383
- if edit == EDIT_PROP_DEL:
4384
- prop, valu, stortype = info
4385
- props['prop'] = prop
4386
- props['valu'] = valu
4387
-
4388
- yield (spliceoffs, ('prop:del', props))
4389
- continue
4390
-
4391
- if edit == EDIT_TAG_SET:
4392
- tag, valu, oldv = info
4393
- props['tag'] = tag
4394
- props['valu'] = valu
4395
- props['oldv'] = oldv
4396
-
4397
- yield (spliceoffs, ('tag:add', props))
4398
- continue
4399
-
4400
- if edit == EDIT_TAG_DEL:
4401
- tag, valu = info
4402
- props['tag'] = tag
4403
- props['valu'] = valu
4404
-
4405
- yield (spliceoffs, ('tag:del', props))
4406
- continue
4407
-
4408
- if edit == EDIT_TAGPROP_SET:
4409
- tag, prop, valu, oldv, stortype = info
4410
- props['tag'] = tag
4411
- props['prop'] = prop
4412
- props['valu'] = valu
4413
- props['oldv'] = oldv
4414
-
4415
- yield (spliceoffs, ('tag:prop:set', props))
4416
- continue
4417
-
4418
- if edit == EDIT_TAGPROP_DEL:
4419
- tag, prop, valu, stortype = info
4420
- props['tag'] = tag
4421
- props['prop'] = prop
4422
- props['valu'] = valu
4423
-
4424
- yield (spliceoffs, ('tag:prop:del', props))
4425
-
4426
4289
  @contextlib.asynccontextmanager
4427
4290
  async def getNodeEditWindow(self):
4428
4291
  if not self.logedits:
synapse/lib/lmdbslab.py CHANGED
@@ -1196,6 +1196,17 @@ class Slab(s_base.Base):
1196
1196
  with self.xact.cursor(db=realdb) as curs:
1197
1197
  return curs.set_key_dup(lkey, lval)
1198
1198
 
1199
+ def count(self, lkey, db=None):
1200
+ realdb, dupsort = self.dbnames[db]
1201
+ with self.xact.cursor(db=realdb) as curs:
1202
+ if not curs.set_key(lkey):
1203
+ return 0
1204
+
1205
+ if not dupsort:
1206
+ return 1
1207
+
1208
+ return curs.count()
1209
+
1199
1210
  def prefexists(self, byts, db=None):
1200
1211
  '''
1201
1212
  Returns True if a prefix exists in the db.
synapse/lib/node.py CHANGED
@@ -613,17 +613,15 @@ class Node:
613
613
  * delete all the tags (bottom up)
614
614
  * fire onDelTag() handlers
615
615
  * delete tag properties from storage
616
- * log tag:del splices
617
616
 
618
617
  * delete all secondary properties
619
618
  * fire onDelProp handler
620
619
  * delete secondary property from storage
621
- * log prop:del splices
622
620
 
623
621
  * delete the primary property
624
622
  * fire onDel handlers for the node
625
623
  * delete primary property from storage
626
- * log node:del splices
624
+
627
625
  '''
628
626
 
629
627
  formname, formvalu = self.ndef
synapse/lib/parser.py CHANGED
@@ -113,10 +113,13 @@ terminalEnglishMap = {
113
113
  '_EDGEN1INIT': '-(',
114
114
  '_EDGEN2INIT': '<(',
115
115
  '_EDGEN2FINI': ')-',
116
+ '_EDGEN1JOINFINI': ')+>',
117
+ '_EDGEN2JOININIT': '<+(',
116
118
  '_ELSE': 'else',
117
119
  '_EMBEDQUERYSTART': '${',
118
120
  '_EXPRCOLONNOSPACE': ':',
119
121
  '_EMIT': 'emit',
122
+ '_EMPTY': 'empty',
120
123
  '_FINI': 'fini',
121
124
  '_HASH': '#',
122
125
  '_HASHSPACE': '#',
@@ -131,6 +134,8 @@ terminalEnglishMap = {
131
134
  '_RIGHTJOIN': '-+>',
132
135
  '_RIGHTPIVOT': '->',
133
136
  '_STOP': 'stop',
137
+ '_WALKNJOINN1': '--+>',
138
+ '_WALKNJOINN2': '<+--',
134
139
  '_WALKNPIVON1': '-->',
135
140
  '_WALKNPIVON2': '<--',
136
141
  '$END': 'end of input',
@@ -640,6 +645,7 @@ ruleClassMap = {
640
645
  'editparens': s_ast.EditParens,
641
646
  'emit': s_ast.Emit,
642
647
  'initblock': s_ast.InitBlock,
648
+ 'emptyblock': s_ast.EmptyBlock,
643
649
  'finiblock': s_ast.FiniBlock,
644
650
  'formname': s_ast.FormName,
645
651
  'editpropdel': lambda astinfo, kids: s_ast.EditPropDel(astinfo, kids[1:]),
@@ -690,8 +696,12 @@ ruleClassMap = {
690
696
  'liftbyformtagprop': s_ast.LiftFormTagProp,
691
697
  'looklist': s_ast.LookList,
692
698
  'lookup': s_ast.Lookup,
699
+ 'n1join': lambda astinfo, kids: s_ast.N1Walk(astinfo, kids, isjoin=True),
700
+ 'n2join': lambda astinfo, kids: s_ast.N2Walk(astinfo, kids, isjoin=True),
693
701
  'n1walk': s_ast.N1Walk,
694
702
  'n2walk': s_ast.N2Walk,
703
+ 'n1walknjoin': lambda astinfo, kids: s_ast.N1WalkNPivo(astinfo, kids, isjoin=True),
704
+ 'n2walknjoin': lambda astinfo, kids: s_ast.N2WalkNPivo(astinfo, kids, isjoin=True),
695
705
  'n1walknpivo': s_ast.N1WalkNPivo,
696
706
  'n2walknpivo': s_ast.N2WalkNPivo,
697
707
  'notcond': s_ast.NotCond,
synapse/lib/slabseqn.py CHANGED
@@ -123,6 +123,7 @@ class SlabSeqn:
123
123
 
124
124
  size = 0
125
125
  tick = s_common.now()
126
+ abstick = s_common.mononow()
126
127
 
127
128
  for item in items:
128
129
 
@@ -136,7 +137,7 @@ class SlabSeqn:
136
137
  rows.append((lkey, byts))
137
138
 
138
139
  retn = self.slab.putmulti(rows, append=True, db=self.db)
139
- took = s_common.now() - tick
140
+ took = s_common.mononow() - abstick
140
141
 
141
142
  assert retn, "Not adding the largest indices"
142
143
 
synapse/lib/snap.py CHANGED
@@ -301,11 +301,7 @@ class ProtoNode:
301
301
  if self.node is not None:
302
302
  return self.node.get(name)
303
303
 
304
- async def set(self, name, valu, norminfo=None):
305
-
306
- prop = self.form.props.get(name)
307
- if prop is None:
308
- return False
304
+ async def _set(self, prop, valu, norminfo=None):
309
305
 
310
306
  if prop.locked:
311
307
  mesg = f'Prop {prop.full} is locked due to deprecation.'
@@ -339,7 +335,7 @@ class ProtoNode:
339
335
  await self.ctx.snap._raiseOnStrict(s_exc.IsDeprLocked, mesg)
340
336
  return False
341
337
 
342
- curv = self.get(name)
338
+ curv = self.get(prop.name)
343
339
  if curv == valu:
344
340
  return False
345
341
 
@@ -351,7 +347,20 @@ class ProtoNode:
351
347
  if self.node is not None:
352
348
  await self.ctx.snap.core._callPropSetHook(self.node, prop, valu)
353
349
 
354
- self.props[name] = valu
350
+ self.props[prop.name] = valu
351
+
352
+ return valu, norminfo
353
+
354
+ async def set(self, name, valu, norminfo=None):
355
+ prop = self.form.props.get(name)
356
+ if prop is None:
357
+ return False
358
+
359
+ retn = await self._set(prop, valu, norminfo=norminfo)
360
+ if retn is False:
361
+ return False
362
+
363
+ (valu, norminfo) = retn
355
364
 
356
365
  propform = self.ctx.snap.core.model.form(prop.type.name)
357
366
  if propform is not None:
@@ -372,6 +381,37 @@ class ProtoNode:
372
381
 
373
382
  return True
374
383
 
384
+ async def getSetOps(self, name, valu, norminfo=None):
385
+ prop = self.form.props.get(name)
386
+ if prop is None:
387
+ return ()
388
+
389
+ retn = await self._set(prop, valu, norminfo=norminfo)
390
+ if retn is False:
391
+ return ()
392
+
393
+ (valu, norminfo) = retn
394
+ ops = []
395
+
396
+ propform = self.ctx.snap.core.model.form(prop.type.name)
397
+ if propform is not None:
398
+ ops.append(self.ctx.getAddNodeOps(propform.name, valu, norminfo=norminfo))
399
+
400
+ # TODO can we mandate any subs are returned pre-normalized?
401
+ propsubs = norminfo.get('subs')
402
+ if propsubs is not None:
403
+ for subname, subvalu in propsubs.items():
404
+ full = f'{prop.name}:{subname}'
405
+ if self.form.props.get(full) is not None:
406
+ ops.append(self.getSetOps(full, subvalu))
407
+
408
+ propadds = norminfo.get('adds')
409
+ if propadds is not None:
410
+ for addname, addvalu, addinfo in propadds:
411
+ ops.append(self.ctx.getAddNodeOps(addname, addvalu, norminfo=addinfo))
412
+
413
+ return ops
414
+
375
415
  class SnapEditor:
376
416
  '''
377
417
  A SnapEditor allows tracking node edits with subs/deps as a transaction.
@@ -394,17 +434,12 @@ class SnapEditor:
394
434
  nodeedits.append(nodeedit)
395
435
  return nodeedits
396
436
 
397
- async def addNode(self, formname, valu, props=None, norminfo=None):
437
+ async def _addNode(self, form, valu, props=None, norminfo=None):
398
438
 
399
439
  self.snap.core._checkMaxNodes()
400
440
 
401
- form = self.snap.core.model.form(formname)
402
- if form is None:
403
- mesg = f'No form named {formname} for valu={valu}.'
404
- return await self.snap._raiseOnStrict(s_exc.NoSuchForm, mesg)
405
-
406
441
  if form.isrunt:
407
- mesg = f'Cannot make runt nodes: {formname}.'
442
+ mesg = f'Cannot make runt nodes: {form.name}.'
408
443
  return await self.snap._raiseOnStrict(s_exc.IsRuntForm, mesg)
409
444
 
410
445
  if form.locked:
@@ -417,15 +452,73 @@ class SnapEditor:
417
452
  except s_exc.BadTypeValu as e:
418
453
  e.errinfo['form'] = form.name
419
454
  if self.snap.strict: raise e
420
- await self.snap.warn(f'addNode() BadTypeValu {formname}={valu} {e}')
455
+ await self.snap.warn(f'addNode() BadTypeValu {form.name}={valu} {e}')
421
456
  return None
422
457
 
458
+ return valu, norminfo
459
+
460
+ async def addNode(self, formname, valu, props=None, norminfo=None):
461
+
462
+ form = self.snap.core.model.form(formname)
463
+ if form is None:
464
+ mesg = f'No form named {formname} for valu={valu}.'
465
+ return await self.snap._raiseOnStrict(s_exc.NoSuchForm, mesg)
466
+
467
+ retn = await self._addNode(form, valu, props=props, norminfo=norminfo)
468
+ if retn is None:
469
+ return None
470
+
471
+ valu, norminfo = retn
472
+
423
473
  protonode = await self._initProtoNode(form, valu, norminfo)
424
474
  if props is not None:
425
475
  [await protonode.set(p, v) for (p, v) in props.items()]
426
476
 
427
477
  return protonode
428
478
 
479
+ async def getAddNodeOps(self, formname, valu, props=None, norminfo=None):
480
+
481
+ form = self.snap.core.model.form(formname)
482
+ if form is None:
483
+ mesg = f'No form named {formname} for valu={valu}.'
484
+ await self.snap._raiseOnStrict(s_exc.NoSuchForm, mesg)
485
+ return()
486
+
487
+ retn = await self._addNode(form, valu, props=props, norminfo=norminfo)
488
+ if retn is None:
489
+ return ()
490
+
491
+ norm, norminfo = retn
492
+
493
+ ndef = (form.name, norm)
494
+
495
+ protonode = self.protonodes.get(ndef)
496
+ if protonode is not None:
497
+ return ()
498
+
499
+ buid = s_common.buid(ndef)
500
+ node = await self.snap.getNodeByBuid(buid)
501
+ if node is not None:
502
+ return ()
503
+
504
+ protonode = ProtoNode(self, buid, form, norm, node)
505
+
506
+ self.protonodes[ndef] = protonode
507
+
508
+ ops = []
509
+
510
+ subs = norminfo.get('subs')
511
+ if subs is not None:
512
+ for prop, valu in subs.items():
513
+ ops.append(protonode.getSetOps(prop, valu))
514
+
515
+ adds = norminfo.get('adds')
516
+ if adds is not None:
517
+ for addname, addvalu, addinfo in adds:
518
+ ops.append(self.getAddNodeOps(addname, addvalu, norminfo=addinfo))
519
+
520
+ return ops
521
+
429
522
  def loadNode(self, node):
430
523
  protonode = self.protonodes.get(node.ndef)
431
524
  if protonode is None:
@@ -448,14 +541,25 @@ class SnapEditor:
448
541
 
449
542
  self.protonodes[ndef] = protonode
450
543
 
544
+ ops = collections.deque()
545
+
451
546
  subs = norminfo.get('subs')
452
547
  if subs is not None:
453
- [await protonode.set(p, v) for (p, v) in subs.items()]
548
+ for prop, valu in subs.items():
549
+ ops.append(protonode.getSetOps(prop, valu))
550
+
551
+ while ops:
552
+ oset = ops.popleft()
553
+ ops.extend(await oset)
454
554
 
455
555
  adds = norminfo.get('adds')
456
556
  if adds is not None:
457
557
  for addname, addvalu, addinfo in adds:
458
- await self.addNode(addname, addvalu, norminfo=addinfo)
558
+ ops.append(self.getAddNodeOps(addname, addvalu, norminfo=addinfo))
559
+
560
+ while ops:
561
+ oset = ops.popleft()
562
+ ops.extend(await oset)
459
563
 
460
564
  return protonode
461
565
 
@@ -469,8 +573,6 @@ class Snap(s_base.Base):
469
573
 
470
574
  Transactions produce the following EventBus events:
471
575
 
472
- (...any splice...)
473
- ('log', {'level': 'mesg': })
474
576
  ('print', {}),
475
577
  '''
476
578
  tagcachesize = 1000
@@ -598,7 +700,6 @@ class Snap(s_base.Base):
598
700
 
599
701
  yield pode
600
702
 
601
- @s_coro.genrhelp
602
703
  async def storm(self, text, opts=None, user=None):
603
704
  '''
604
705
  Execute a storm query and yield (Node(), Path()) tuples.
@@ -616,7 +717,6 @@ class Snap(s_base.Base):
616
717
  async for x in runt.execute():
617
718
  yield x
618
719
 
619
- @s_coro.genrhelp
620
720
  async def eval(self, text, opts=None, user=None):
621
721
  '''
622
722
  Run a storm query and yield Node() objects.
synapse/lib/spooled.py CHANGED
@@ -143,6 +143,15 @@ class Dict(Spooled):
143
143
  self.len = len(self.realdict)
144
144
  self.realdict.clear()
145
145
 
146
+ def pop(self, key, defv=None):
147
+ if self.fallback:
148
+ ret = self.slab.pop(s_msgpack.en(key))
149
+ if ret is None:
150
+ return defv
151
+ self.len -= 1
152
+ return s_msgpack.un(ret)
153
+ return self.realdict.pop(key, defv)
154
+
146
155
  def has(self, key):
147
156
  if self.fallback:
148
157
  return self.slab.has(s_msgpack.en(key))