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.
- synapse/cmds/cortex.py +1 -6
- synapse/common.py +6 -0
- synapse/cortex.py +73 -56
- synapse/datamodel.py +32 -0
- synapse/lib/agenda.py +81 -51
- synapse/lib/ast.py +21 -23
- synapse/lib/base.py +0 -6
- synapse/lib/cell.py +13 -22
- synapse/lib/httpapi.py +1 -0
- synapse/lib/nexus.py +3 -2
- synapse/lib/schemas.py +2 -0
- synapse/lib/snap.py +50 -0
- synapse/lib/storm.py +19 -17
- synapse/lib/stormlib/aha.py +4 -1
- synapse/lib/stormlib/auth.py +11 -4
- synapse/lib/stormlib/cache.py +202 -0
- synapse/lib/stormlib/cortex.py +69 -7
- synapse/lib/stormlib/spooled.py +109 -0
- synapse/lib/stormtypes.py +43 -15
- synapse/lib/trigger.py +10 -12
- synapse/lib/types.py +1 -1
- synapse/lib/version.py +2 -2
- synapse/lib/view.py +12 -0
- synapse/models/inet.py +74 -2
- synapse/models/orgs.py +52 -8
- synapse/models/person.py +30 -11
- synapse/models/risk.py +44 -3
- synapse/telepath.py +114 -32
- synapse/tests/test_cortex.py +40 -6
- synapse/tests/test_datamodel.py +22 -0
- synapse/tests/test_lib_agenda.py +8 -1
- synapse/tests/test_lib_aha.py +18 -4
- synapse/tests/test_lib_storm.py +95 -4
- synapse/tests/test_lib_stormlib_cache.py +272 -0
- synapse/tests/test_lib_stormlib_cortex.py +71 -0
- synapse/tests/test_lib_stormlib_spooled.py +190 -0
- synapse/tests/test_lib_stormtypes.py +27 -4
- synapse/tests/test_model_inet.py +67 -0
- synapse/tests/test_model_risk.py +6 -0
- synapse/tests/test_telepath.py +30 -7
- synapse/tests/test_tools_modrole.py +81 -0
- synapse/tests/test_tools_moduser.py +105 -0
- synapse/tools/modrole.py +59 -7
- synapse/tools/moduser.py +78 -10
- {synapse-2.165.0.dist-info → synapse-2.166.0.dist-info}/METADATA +2 -2
- {synapse-2.165.0.dist-info → synapse-2.166.0.dist-info}/RECORD +49 -47
- synapse/lib/provenance.py +0 -111
- synapse/tests/test_lib_provenance.py +0 -37
- {synapse-2.165.0.dist-info → synapse-2.166.0.dist-info}/LICENSE +0 -0
- {synapse-2.165.0.dist-info → synapse-2.166.0.dist-info}/WHEEL +0 -0
- {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
|
|
1564
|
-
|
|
1565
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
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
|
-
|
|
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
|
|
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
|
-
|
|
2316
|
-
|
|
2317
|
+
async with contextlib.aclosing(self.query.iterNodePaths(self, genr=genr)) as nodegenr:
|
|
2318
|
+
nodegenr, empty = await s_ast.pullone(nodegenr)
|
|
2317
2319
|
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
+
if empty:
|
|
2321
|
+
return
|
|
2320
2322
|
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
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
|
-
|
|
2329
|
-
|
|
2330
|
+
subgraph = s_ast.SubGraph(rules)
|
|
2331
|
+
nodegenr = subgraph.run(self, nodegenr)
|
|
2330
2332
|
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
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.'
|
synapse/lib/stormlib/aha.py
CHANGED
|
@@ -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
|
}
|
synapse/lib/stormlib/auth.py
CHANGED
|
@@ -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
|
-
|
|
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()
|