synapse 2.165.0__py311-none-any.whl → 2.166.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 (51) hide show
  1. synapse/cmds/cortex.py +1 -6
  2. synapse/common.py +6 -0
  3. synapse/cortex.py +73 -56
  4. synapse/datamodel.py +32 -0
  5. synapse/lib/agenda.py +81 -51
  6. synapse/lib/ast.py +21 -23
  7. synapse/lib/base.py +0 -6
  8. synapse/lib/cell.py +13 -22
  9. synapse/lib/httpapi.py +1 -0
  10. synapse/lib/nexus.py +3 -2
  11. synapse/lib/schemas.py +2 -0
  12. synapse/lib/snap.py +50 -0
  13. synapse/lib/storm.py +19 -17
  14. synapse/lib/stormlib/aha.py +4 -1
  15. synapse/lib/stormlib/auth.py +11 -4
  16. synapse/lib/stormlib/cache.py +202 -0
  17. synapse/lib/stormlib/cortex.py +69 -7
  18. synapse/lib/stormlib/spooled.py +109 -0
  19. synapse/lib/stormtypes.py +43 -15
  20. synapse/lib/trigger.py +10 -12
  21. synapse/lib/types.py +1 -1
  22. synapse/lib/version.py +2 -2
  23. synapse/lib/view.py +12 -0
  24. synapse/models/inet.py +74 -2
  25. synapse/models/orgs.py +52 -8
  26. synapse/models/person.py +30 -11
  27. synapse/models/risk.py +44 -3
  28. synapse/telepath.py +114 -32
  29. synapse/tests/test_cortex.py +40 -6
  30. synapse/tests/test_datamodel.py +22 -0
  31. synapse/tests/test_lib_agenda.py +8 -1
  32. synapse/tests/test_lib_aha.py +18 -4
  33. synapse/tests/test_lib_storm.py +95 -4
  34. synapse/tests/test_lib_stormlib_cache.py +272 -0
  35. synapse/tests/test_lib_stormlib_cortex.py +71 -0
  36. synapse/tests/test_lib_stormlib_spooled.py +190 -0
  37. synapse/tests/test_lib_stormtypes.py +27 -4
  38. synapse/tests/test_model_inet.py +67 -0
  39. synapse/tests/test_model_risk.py +6 -0
  40. synapse/tests/test_telepath.py +30 -7
  41. synapse/tests/test_tools_modrole.py +81 -0
  42. synapse/tests/test_tools_moduser.py +105 -0
  43. synapse/tools/modrole.py +59 -7
  44. synapse/tools/moduser.py +78 -10
  45. {synapse-2.165.0.dist-info → synapse-2.166.0.dist-info}/METADATA +2 -2
  46. {synapse-2.165.0.dist-info → synapse-2.166.0.dist-info}/RECORD +49 -47
  47. synapse/lib/provenance.py +0 -111
  48. synapse/tests/test_lib_provenance.py +0 -37
  49. {synapse-2.165.0.dist-info → synapse-2.166.0.dist-info}/LICENSE +0 -0
  50. {synapse-2.165.0.dist-info → synapse-2.166.0.dist-info}/WHEEL +0 -0
  51. {synapse-2.165.0.dist-info → synapse-2.166.0.dist-info}/top_level.txt +0 -0
synapse/lib/cell.py CHANGED
@@ -1560,32 +1560,22 @@ class Cell(s_nexus.Pusher, s_telepath.Aware):
1560
1560
 
1561
1561
  self.ahasvcname = f'{ahaname}.{ahanetw}'
1562
1562
 
1563
- async def onlink(proxy):
1564
- while not proxy.isfini:
1565
- info = await self.getAhaInfo()
1563
+ async def _runAhaRegLoop():
1564
+
1565
+ while not self.isfini:
1566
1566
  try:
1567
+ proxy = await self.ahaclient.proxy()
1568
+ info = await self.getAhaInfo()
1567
1569
  await proxy.addAhaSvc(ahaname, info, network=ahanetw)
1568
1570
  if self.isactive and ahalead is not None:
1569
1571
  await proxy.addAhaSvc(ahalead, info, network=ahanetw)
1572
+ except Exception as e:
1573
+ logger.exception(f'Error registering service {self.ahasvcname} with AHA: {e}')
1574
+ await self.waitfini(1)
1575
+ else:
1576
+ await proxy.waitfini()
1570
1577
 
1571
- return
1572
-
1573
- except asyncio.CancelledError: # pragma: no cover
1574
- raise
1575
-
1576
- except Exception:
1577
- logger.exception('Error in _initAhaService() onlink')
1578
-
1579
- await proxy.waitfini(1)
1580
-
1581
- async def fini():
1582
- await self.ahaclient.offlink(onlink)
1583
-
1584
- async def init():
1585
- await self.ahaclient.onlink(onlink)
1586
- self.onfini(fini)
1587
-
1588
- self.schedCoro(init())
1578
+ self.schedCoro(_runAhaRegLoop())
1589
1579
 
1590
1580
  async def initServiceRuntime(self):
1591
1581
  pass
@@ -3694,7 +3684,8 @@ class Cell(s_nexus.Pusher, s_telepath.Aware):
3694
3684
 
3695
3685
  await self.ahaclient.waitready()
3696
3686
 
3697
- mirrors = await self.ahaclient.getAhaSvcMirrors(self.ahasvcname)
3687
+ proxy = await self.ahaclient.proxy(timeout=5)
3688
+ mirrors = await proxy.getAhaSvcMirrors(self.ahasvcname)
3698
3689
  if mirrors is None:
3699
3690
  mesg = 'Service must be configured with AHA to enumerate mirror URLs'
3700
3691
  raise s_exc.NoSuchName(mesg=mesg, name=self.ahasvcname)
synapse/lib/httpapi.py CHANGED
@@ -1344,6 +1344,7 @@ class ExtApiHandler(StormHandler):
1344
1344
  varz['_http_request_info'] = info
1345
1345
 
1346
1346
  opts = {
1347
+ 'mirror': adef.get('pool', False),
1347
1348
  'readonly': adef.get('readonly'),
1348
1349
  'show': (
1349
1350
  'http:resp:body',
synapse/lib/nexus.py CHANGED
@@ -589,10 +589,11 @@ class NexsRoot(s_base.Base):
589
589
 
590
590
  try:
591
591
  await self.cell.ahaclient.waitready(timeout=5)
592
- ahainfo = await self.cell.ahaclient.getCellInfo()
592
+ proxy = await self.cell.ahaclient.proxy(timeout=5)
593
+ ahainfo = await proxy.getCellInfo()
593
594
  ahavers = ahainfo['synapse']['version']
594
595
  if self.cell.ahasvcname is not None and ahavers >= (2, 95, 0):
595
- await self.cell.ahaclient.modAhaSvcInfo(self.cell.ahasvcname, {'ready': status})
596
+ await proxy.modAhaSvcInfo(self.cell.ahasvcname, {'ready': status})
596
597
 
597
598
  except asyncio.CancelledError: # pragma: no cover TODO: remove once >= py 3.8 only
598
599
  raise
synapse/lib/schemas.py CHANGED
@@ -40,6 +40,7 @@ _HttpExtAPIConfSchema = {
40
40
  'name': {'type': 'string', 'default': ''},
41
41
  'desc': {'type': 'string', 'default': ''},
42
42
  'path': {'type': 'string', 'minlen': 1},
43
+ 'pool': {'type': 'boolean', 'default': False},
43
44
  'view': {'type': 'string', 'pattern': s_config.re_iden},
44
45
  'runas': {'type': 'string', 'pattern': '^(owner|user)$'},
45
46
  'owner': {'type': 'string', 'pattern': s_config.re_iden},
@@ -93,6 +94,7 @@ _CronJobSchema = {
93
94
  'iden': {'type': 'string', 'pattern': s_config.re_iden},
94
95
  'view': {'type': 'string', 'pattern': s_config.re_iden},
95
96
  'name': {'type': 'string'},
97
+ 'pool': {'type': 'boolean'},
96
98
  'doc': {'type': 'string'},
97
99
  'incunit': {
98
100
  'oneOf': [
synapse/lib/snap.py CHANGED
@@ -209,6 +209,27 @@ class ProtoNode:
209
209
  if tagnode is not s_common.novalu:
210
210
  return self.ctx.loadNode(tagnode)
211
211
 
212
+ # check for an :isnow tag redirection in our hierarchy...
213
+ toks = info.get('toks')
214
+ for i in range(len(toks)):
215
+
216
+ toktag = '.'.join(toks[:i + 1])
217
+ toknode = await self.ctx.snap.getTagNode(toktag)
218
+ if toknode is s_common.novalu:
219
+ continue
220
+
221
+ tokvalu = toknode.ndef[1]
222
+ if tokvalu == toktag:
223
+ continue
224
+
225
+ realnow = tokvalu + norm[len(toktag):]
226
+ tagnode = await self.ctx.snap.getTagNode(realnow)
227
+ if tagnode is not s_common.novalu:
228
+ return self.ctx.loadNode(tagnode)
229
+
230
+ norm, info = await self.ctx.snap.getTagNorm(realnow)
231
+ break
232
+
212
233
  return await self.ctx.addNode('syn:tag', norm, norminfo=info)
213
234
 
214
235
  def getTag(self, tag):
@@ -657,6 +678,33 @@ class Snap(s_base.Base):
657
678
  self.onfini(runt)
658
679
  return runt
659
680
 
681
+ async def _joinEmbedStor(self, storage, embeds):
682
+ for nodePath, relProps in embeds.items():
683
+ await asyncio.sleep(0)
684
+ iden = relProps.get('*')
685
+ if not iden:
686
+ continue
687
+
688
+ stor = await self.view.getStorNodes(s_common.uhex(iden))
689
+ for relProp in relProps.keys():
690
+ await asyncio.sleep(0)
691
+ if relProp == '*':
692
+ continue
693
+
694
+ for idx, layrstor in enumerate(stor):
695
+ await asyncio.sleep(0)
696
+ props = layrstor.get('props')
697
+ if not props:
698
+ continue
699
+
700
+ if relProp not in props:
701
+ continue
702
+
703
+ if 'embeds' not in storage[idx]:
704
+ storage[idx]['embeds'] = {}
705
+
706
+ storage[idx]['embeds'][f'{nodePath}::{relProp}'] = props[relProp]
707
+
660
708
  async def iterStormPodes(self, text, opts, user=None):
661
709
  '''
662
710
  Yield packed node tuples for the given storm query text.
@@ -699,6 +747,8 @@ class Snap(s_base.Base):
699
747
  embdef = embeds.get(node.form.name)
700
748
  if embdef is not None:
701
749
  pode[1]['embeds'] = await node.getEmbeds(embdef)
750
+ if show_storage:
751
+ await self._joinEmbedStor(pode[1]['storage'], pode[1]['embeds'])
702
752
 
703
753
  yield pode
704
754
 
synapse/lib/storm.py CHANGED
@@ -29,7 +29,6 @@ import synapse.lib.version as s_version
29
29
  import synapse.lib.hashitem as s_hashitem
30
30
  import synapse.lib.hiveauth as s_hiveauth
31
31
  import synapse.lib.stormctrl as s_stormctrl
32
- import synapse.lib.provenance as s_provenance
33
32
  import synapse.lib.stormtypes as s_stormtypes
34
33
 
35
34
  import synapse.lib.stormlib.graph as s_stormlib_graph
@@ -1208,6 +1207,8 @@ stormcmds = (
1208
1207
  'descr': addcrondescr,
1209
1208
  'cmdargs': (
1210
1209
  ('query', {'help': 'Query for the cron job to execute.'}),
1210
+ ('--pool', {'action': 'store_true', 'default': False,
1211
+ 'help': 'Allow the cron job to be run by a mirror from the query pool.'}),
1211
1212
  ('--minute', {'help': 'Minute value for job or recurrence period.'}),
1212
1213
  ('--name', {'help': 'An optional name for the cron job.'}),
1213
1214
  ('--doc', {'help': 'An optional doc string for the cron job.'}),
@@ -1227,6 +1228,7 @@ stormcmds = (
1227
1228
  minute=$cmdopts.minute,
1228
1229
  hour=$cmdopts.hour,
1229
1230
  day=$cmdopts.day,
1231
+ pool=$cmdopts.pool,
1230
1232
  month=$cmdopts.month,
1231
1233
  year=$cmdopts.year,
1232
1234
  hourly=$cmdopts.hourly,
@@ -1374,6 +1376,7 @@ stormcmds = (
1374
1376
  $lib.print('iden: {iden}', iden=$job.iden)
1375
1377
  $lib.print('user: {user}', user=$job.user)
1376
1378
  $lib.print('enabled: {enabled}', enabled=$job.enabled)
1379
+ $lib.print(`pool: {$job.pool}`)
1377
1380
  $lib.print('recurring: {isrecur}', isrecur=$job.isrecur)
1378
1381
  $lib.print('# starts: {startcount}', startcount=$job.startcount)
1379
1382
  $lib.print('# errors: {errcount}', errcount=$job.errcount)
@@ -2310,27 +2313,26 @@ class Runtime(s_base.Base):
2310
2313
 
2311
2314
  async def execute(self, genr=None):
2312
2315
  try:
2313
- with s_provenance.claim('storm', q=self.query.text, user=self.user.iden):
2314
2316
 
2315
- async with contextlib.aclosing(self.query.iterNodePaths(self, genr=genr)) as nodegenr:
2316
- nodegenr, empty = await s_ast.pullone(nodegenr)
2317
+ async with contextlib.aclosing(self.query.iterNodePaths(self, genr=genr)) as nodegenr:
2318
+ nodegenr, empty = await s_ast.pullone(nodegenr)
2317
2319
 
2318
- if empty:
2319
- return
2320
+ if empty:
2321
+ return
2320
2322
 
2321
- rules = self.opts.get('graph')
2322
- if rules not in (False, None):
2323
- if rules is True:
2324
- rules = {'degrees': None, 'refs': True}
2325
- elif isinstance(rules, str):
2326
- rules = await self.snap.core.getStormGraph(rules)
2323
+ rules = self.opts.get('graph')
2324
+ if rules not in (False, None):
2325
+ if rules is True:
2326
+ rules = {'degrees': None, 'refs': True}
2327
+ elif isinstance(rules, str):
2328
+ rules = await self.snap.core.getStormGraph(rules)
2327
2329
 
2328
- subgraph = s_ast.SubGraph(rules)
2329
- nodegenr = subgraph.run(self, nodegenr)
2330
+ subgraph = s_ast.SubGraph(rules)
2331
+ nodegenr = subgraph.run(self, nodegenr)
2330
2332
 
2331
- async for item in nodegenr:
2332
- self.tick()
2333
- yield item
2333
+ async for item in nodegenr:
2334
+ self.tick()
2335
+ yield item
2334
2336
 
2335
2337
  except RecursionError:
2336
2338
  mesg = 'Maximum Storm pipeline depth exceeded.'
@@ -35,6 +35,7 @@ class AhaPoolLib(s_stormtypes.Lib):
35
35
  if poolinfo is not None:
36
36
  return AhaPool(self.runt, poolinfo)
37
37
 
38
+ @s_stormtypes.stormfunc(readonly=True)
38
39
  async def list(self):
39
40
 
40
41
  self.runt.reqAdmin()
@@ -60,6 +61,9 @@ class AhaPool(s_stormtypes.StormType):
60
61
  'del': self._del,
61
62
  })
62
63
 
64
+ async def stormrepr(self):
65
+ return f'{self._storm_typename}: {self.poolinfo.get("name")}'
66
+
63
67
  async def _derefGet(self, name):
64
68
  return self.poolinfo.get(name)
65
69
 
@@ -97,7 +101,6 @@ stormcmds = (
97
101
  for $pool in $lib.aha.pool.list() {
98
102
  $count = ($count + 1)
99
103
  $lib.print(`Pool: {$pool.name}`)
100
- $lib.print($pool)
101
104
  for ($svcname, $svcinfo) in $pool.services {
102
105
  $lib.print(` {$svcname}`)
103
106
  }
@@ -1227,6 +1227,11 @@ class User(s_stormtypes.Prim):
1227
1227
  indx = await s_stormtypes.toint(indx, noneok=True)
1228
1228
  gateiden = await s_stormtypes.tostr(gateiden, noneok=True)
1229
1229
  self.runt.confirm(('auth', 'user', 'set', 'rules'), gateiden=gateiden)
1230
+ # TODO: Remove me in 3.0.0
1231
+ if gateiden == 'cortex':
1232
+ mesg = f'Adding rule on the "cortex" authgate. This authgate is not used ' \
1233
+ f'for permission checks and will be removed in Synapse v3.0.0.'
1234
+ await self.runt.snap.warn(mesg, log=False)
1230
1235
  await self.runt.snap.core.addUserRule(self.valu, rule, indx=indx, gateiden=gateiden)
1231
1236
 
1232
1237
  async def _methUserDelRule(self, rule, gateiden=None):
@@ -1480,6 +1485,11 @@ class Role(s_stormtypes.Prim):
1480
1485
  indx = await s_stormtypes.toint(indx, noneok=True)
1481
1486
  gateiden = await s_stormtypes.tostr(gateiden, noneok=True)
1482
1487
  self.runt.confirm(('auth', 'role', 'set', 'rules'), gateiden=gateiden)
1488
+ # TODO: Remove me in 3.0.0
1489
+ if gateiden == 'cortex':
1490
+ mesg = f'Adding rule on the "cortex" authgate. This authgate is not used ' \
1491
+ f'for permission checks and will be removed in Synapse v3.0.0.'
1492
+ await self.runt.snap.warn(mesg, log=False)
1483
1493
  await self.runt.snap.core.addRoleRule(self.valu, rule, indx=indx, gateiden=gateiden)
1484
1494
 
1485
1495
  async def _methRoleDelRule(self, rule, gateiden=None):
@@ -1558,10 +1568,7 @@ class LibAuth(s_stormtypes.Lib):
1558
1568
  @s_stormtypes.stormfunc(readonly=True)
1559
1569
  async def textFromRule(self, rule):
1560
1570
  rule = await s_stormtypes.toprim(rule)
1561
- text = '.'.join(rule[1])
1562
- if not rule[0]:
1563
- text = '!' + text
1564
- return text
1571
+ return s_common.reprauthrule(rule)
1565
1572
 
1566
1573
  @s_stormtypes.stormfunc(readonly=True)
1567
1574
  async def getPermDefs(self):
@@ -0,0 +1,202 @@
1
+ import asyncio
2
+
3
+ import synapse.exc as s_exc
4
+
5
+ import synapse.lib.ast as s_ast
6
+ import synapse.lib.cache as s_cache
7
+ import synapse.lib.stormctrl as s_stormctrl
8
+ import synapse.lib.stormtypes as s_stormtypes
9
+
10
+ CACHE_SIZE_MAX = 10_000
11
+ CACHE_SIZE_DEFAULT = 10_000
12
+
13
+ @s_stormtypes.registry.registerLib
14
+ class LibCache(s_stormtypes.Lib):
15
+ '''
16
+ A Storm Library for interacting with Cache Objects.
17
+ '''
18
+ _storm_locals = (
19
+ {'name': 'fixed', 'desc': '''
20
+ Get a new Fixed Cache object.
21
+
22
+ On a cache-miss when calling .get(), the callback Storm query is executed in a sub-runtime
23
+ in the current execution context. A special variable, $cache_key, will be set
24
+ to the key argument provided to .get().
25
+
26
+ The callback Storm query must contain a return statement, and if it does not return a value
27
+ when executed with the input, $lib.null will be set as the value.
28
+
29
+ The fixed cache uses FIFO to evict items once the maximum size is reached.
30
+
31
+ Examples:
32
+
33
+ // Use a callback query with a function that modifies the outer runtime,
34
+ // since it will run in the scope where it was defined.
35
+ $test = foo
36
+
37
+ function callback(key) {
38
+ $test = $key // this will modify $test in the outer runtime
39
+ return(`{$key}-val`)
40
+ }
41
+
42
+ $cache = $lib.cache.fixed(${ return($callback($cache_key)) })
43
+ $value = $cache.get(bar)
44
+ $lib.print($test) // this will equal "bar"
45
+
46
+ // Use a callback query that will not modify the outer runtime,
47
+ // except for variables accessible as references.
48
+ $test = foo
49
+ $tests = ([])
50
+
51
+ $cache = $lib.cache.fixed(${
52
+ $test = $cache_key // this will *not* modify $test in the outer runtime
53
+ $tests.append($cache_key) // this will modify $tests in the outer runtime
54
+ return(`{$cache_key}-val`)
55
+ })
56
+
57
+ $value = $cache.get(bar)
58
+ $lib.print($test) // this will equal "foo"
59
+ $lib.print($tests) // this will equal (foo,)
60
+ ''',
61
+ 'type': {'type': 'function', '_funcname': '_methFixedCache',
62
+ 'args': (
63
+ {'name': 'callback', 'type': ['str', 'storm:query'],
64
+ 'desc': 'A Storm query that will return a value for $cache_key on a cache miss.', },
65
+ {'name': 'size', 'type': 'int', 'default': CACHE_SIZE_DEFAULT,
66
+ 'desc': 'The maximum size of the cache.', },
67
+ ),
68
+ 'returns': {'type': 'cache:fixed', 'desc': 'A new ``cache:fixed`` object.'}}},
69
+ )
70
+ _storm_lib_path = ('cache',)
71
+
72
+ def getObjLocals(self):
73
+ return {
74
+ 'fixed': self._methFixedCache,
75
+ }
76
+
77
+ @s_stormtypes.stormfunc(readonly=True)
78
+ async def _methFixedCache(self, callback, size=CACHE_SIZE_DEFAULT):
79
+ size = await s_stormtypes.toint(size)
80
+ callback = await s_stormtypes.tostr(callback)
81
+
82
+ if size < 1 or size > CACHE_SIZE_MAX:
83
+ raise s_exc.BadArg(mesg=f'Cache size must be between 1-{CACHE_SIZE_MAX}')
84
+
85
+ try:
86
+ query = await self.runt.getStormQuery(callback)
87
+ except s_exc.BadSyntax as e:
88
+ raise s_exc.BadArg(mesg=f'Invalid callback query: {e.errinfo.get("mesg")}')
89
+
90
+ if not query.hasAstClass(s_ast.Return):
91
+ raise s_exc.BadArg(mesg='Callback query must return a value')
92
+
93
+ return FixedCache(self.runt, query, size=size)
94
+
95
+ @s_stormtypes.registry.registerType
96
+ class FixedCache(s_stormtypes.StormType):
97
+ '''
98
+ A StormLib API instance of a Storm Fixed Cache.
99
+ '''
100
+ _storm_locals = (
101
+ {'name': 'query', 'desc': 'Get the callback Storm query as string.',
102
+ 'type': {'type': 'gtor', '_gtorfunc': '_gtorQuery',
103
+ 'returns': {'type': 'str', 'desc': 'The callback Storm query text.', }}},
104
+ {'name': 'get', 'desc': 'Get an item from the cache by key.',
105
+ 'type': {'type': 'function', '_funcname': '_methGet',
106
+ 'args': (
107
+ {'name': 'key', 'type': 'any', 'desc': 'The key to lookup.'},
108
+ ),
109
+ 'returns': {'type': 'any',
110
+ 'desc': 'The value from the cache, or the callback query if it does not exist', }}},
111
+ {'name': 'pop', 'desc': 'Pop an item from the cache.',
112
+ 'type': {'type': 'function', '_funcname': '_methPop',
113
+ 'args': (
114
+ {'name': 'key', 'type': 'any', 'desc': 'The key to pop.'},
115
+ ),
116
+ 'returns': {'type': 'any',
117
+ 'desc': 'The value from the cache, or $lib.null if it does not exist', }}},
118
+ {'name': 'put', 'desc': 'Put an item into the cache.',
119
+ 'type': {'type': 'function', '_funcname': '_methPut',
120
+ 'args': (
121
+ {'name': 'key', 'type': 'any', 'desc': 'The key put in the cache.'},
122
+ {'name': 'value', 'type': 'any', 'desc': 'The value to assign to the key.'},
123
+ ),
124
+ 'returns': {'type': 'null', }}},
125
+ {'name': 'clear', 'desc': 'Clear all items from the cache.',
126
+ 'type': {'type': 'function', '_funcname': '_methClear',
127
+ 'returns': {'type': 'null', }}},
128
+ )
129
+ _storm_typename = 'cache:fixed'
130
+ _ismutable = False
131
+
132
+ def __init__(self, runt, query, size=CACHE_SIZE_DEFAULT):
133
+ s_stormtypes.StormType.__init__(self)
134
+ self.runt = runt
135
+ self.size = size
136
+ self.query = query
137
+ self.locls.update(self.getObjLocals())
138
+ self.gtors.update({
139
+ 'query': self._gtorQuery,
140
+ })
141
+
142
+ self.cache = s_cache.FixedCache(self._runCallback, size=size)
143
+
144
+ def __len__(self):
145
+ return len(self.cache)
146
+
147
+ async def stormrepr(self):
148
+ if len(qtext := self.query.text) > 100:
149
+ qtext = qtext[:100] + '...'
150
+ return f'{self._storm_typename}: size={self.size} query="{qtext}"'
151
+
152
+ def getObjLocals(self):
153
+ return {
154
+ 'pop': self._methPop,
155
+ 'put': self._methPut,
156
+ 'get': self._methGet,
157
+ 'clear': self._methClear,
158
+ }
159
+
160
+ async def _gtorQuery(self):
161
+ return self.query.text
162
+
163
+ async def _runCallback(self, key):
164
+
165
+ varz = self.runt.getScopeVars()
166
+ varz['cache_key'] = key
167
+
168
+ opts = {'vars': varz}
169
+ async with self.runt.getCmdRuntime(self.query, opts=opts) as runt:
170
+ try:
171
+ async for _ in runt.execute():
172
+ await asyncio.sleep(0)
173
+ except s_stormctrl.StormReturn as e:
174
+ return await s_stormtypes.toprim(e.item)
175
+ except s_stormctrl.StormCtrlFlow:
176
+ pass
177
+
178
+ async def _reqKey(self, key):
179
+ if s_stormtypes.ismutable(key):
180
+ mesg = 'Mutable values are not allowed as cache keys'
181
+ raise s_exc.BadArg(mesg=mesg, name=await s_stormtypes.torepr(key))
182
+ return await s_stormtypes.toprim(key)
183
+
184
+ @s_stormtypes.stormfunc(readonly=True)
185
+ async def _methPop(self, key):
186
+ key = await self._reqKey(key)
187
+ return self.cache.pop(key)
188
+
189
+ @s_stormtypes.stormfunc(readonly=True)
190
+ async def _methPut(self, key, value):
191
+ key = await self._reqKey(key)
192
+ val = await s_stormtypes.toprim(value)
193
+ self.cache.put(key, val)
194
+
195
+ @s_stormtypes.stormfunc(readonly=True)
196
+ async def _methGet(self, key):
197
+ key = await self._reqKey(key)
198
+ return await self.cache.aget(key)
199
+
200
+ @s_stormtypes.stormfunc(readonly=True)
201
+ async def _methClear(self):
202
+ self.cache.clear()