synapse 2.212.0__py311-none-any.whl → 2.214.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 (75) hide show
  1. synapse/cortex.py +37 -6
  2. synapse/daemon.py +6 -6
  3. synapse/exc.py +13 -1
  4. synapse/lib/aha.py +5 -0
  5. synapse/lib/ast.py +2 -6
  6. synapse/lib/boss.py +47 -2
  7. synapse/lib/cell.py +199 -6
  8. synapse/lib/certdir.py +44 -1
  9. synapse/lib/cmd.py +24 -0
  10. synapse/lib/coro.py +8 -2
  11. synapse/lib/drive.py +7 -2
  12. synapse/lib/link.py +11 -3
  13. synapse/lib/schemas.py +1 -1
  14. synapse/lib/scrape.py +3 -1
  15. synapse/lib/snap.py +89 -80
  16. synapse/lib/storm.py +2 -1
  17. synapse/lib/stormlib/imap.py +3 -2
  18. synapse/lib/stormlib/spooled.py +4 -0
  19. synapse/lib/stormtypes.py +18 -0
  20. synapse/lib/task.py +1 -0
  21. synapse/lib/types.py +36 -8
  22. synapse/lib/version.py +2 -2
  23. synapse/models/inet.py +5 -0
  24. synapse/telepath.py +4 -2
  25. synapse/tests/files/testpkg_build_docs/docs/bar.rst +15 -0
  26. synapse/tests/files/testpkg_build_docs/docs/foo.rst +4 -0
  27. synapse/tests/files/testpkg_build_docs/storm/commands/testcmd.storm +0 -0
  28. synapse/tests/files/testpkg_build_docs/storm/modules/apimod.storm +0 -0
  29. synapse/tests/files/testpkg_build_docs/storm/modules/testmod.storm +0 -0
  30. synapse/tests/files/testpkg_build_docs/storm/testcmd.storm +5 -0
  31. synapse/tests/files/testpkg_build_docs/testpkg.yaml +69 -0
  32. synapse/tests/test_cortex.py +20 -1
  33. synapse/tests/test_daemon.py +1 -1
  34. synapse/tests/test_exc.py +6 -0
  35. synapse/tests/test_lib_ast.py +69 -14
  36. synapse/tests/test_lib_boss.py +8 -0
  37. synapse/tests/test_lib_cell.py +119 -8
  38. synapse/tests/test_lib_certdir.py +8 -0
  39. synapse/tests/test_lib_coro.py +5 -0
  40. synapse/tests/test_lib_httpapi.py +10 -2
  41. synapse/tests/test_lib_link.py +1 -1
  42. synapse/tests/test_lib_scrape.py +6 -0
  43. synapse/tests/test_lib_storm.py +123 -1
  44. synapse/tests/test_lib_stormlib_spooled.py +31 -0
  45. synapse/tests/test_lib_stormtypes.py +11 -0
  46. synapse/tests/test_lib_types.py +137 -45
  47. synapse/tests/test_model_crypto.py +8 -0
  48. synapse/tests/test_model_inet.py +7 -0
  49. synapse/tests/test_telepath.py +50 -5
  50. synapse/tests/test_tools_axon.py +304 -0
  51. synapse/tests/test_tools_cortex_layer.py +419 -0
  52. synapse/tests/test_tools_demote.py +114 -0
  53. synapse/tests/test_tools_pkgs_gendocs.py +100 -0
  54. synapse/tests/test_tools_shutdown.py +95 -0
  55. synapse/tests/test_utils.py +22 -1
  56. synapse/tests/utils.py +44 -29
  57. synapse/tools/aha/easycert.py +2 -0
  58. synapse/tools/aha/enroll.py +3 -0
  59. synapse/tools/axon/__init__.py +0 -0
  60. synapse/tools/axon/dump.py +155 -0
  61. synapse/tools/axon/load.py +89 -0
  62. synapse/tools/cortex/__init__.py +0 -0
  63. synapse/tools/cortex/layer/__init__.py +0 -0
  64. synapse/tools/cortex/layer/dump.py +184 -0
  65. synapse/tools/cortex/layer/load.py +129 -0
  66. synapse/tools/demote.py +52 -0
  67. synapse/tools/healthcheck.py +1 -1
  68. synapse/tools/pkgs/gendocs.py +176 -0
  69. synapse/tools/pkgs/pandoc_filter.py +79 -0
  70. synapse/tools/shutdown.py +52 -0
  71. {synapse-2.212.0.dist-info → synapse-2.214.0.dist-info}/METADATA +1 -1
  72. {synapse-2.212.0.dist-info → synapse-2.214.0.dist-info}/RECORD +75 -52
  73. {synapse-2.212.0.dist-info → synapse-2.214.0.dist-info}/WHEEL +0 -0
  74. {synapse-2.212.0.dist-info → synapse-2.214.0.dist-info}/licenses/LICENSE +0 -0
  75. {synapse-2.212.0.dist-info → synapse-2.214.0.dist-info}/top_level.txt +0 -0
synapse/lib/certdir.py CHANGED
@@ -1,7 +1,6 @@
1
1
  import io
2
2
  import os
3
3
  import ssl
4
- import time
5
4
  import shutil
6
5
  import socket
7
6
  import logging
@@ -397,6 +396,28 @@ class CertDir:
397
396
  '''
398
397
  return self._genPkeyCsr(name, 'hosts', outp=outp)
399
398
 
399
+ def delHostCsr(self, name: str, outp: OutPutOrNone = None) -> bool:
400
+ '''
401
+ Delete an existing host CSR.
402
+
403
+ Args:
404
+ name: The name of the host CSR.
405
+ outp: The output buffer.
406
+
407
+ Returns:
408
+ bool: True if the CSR is deleted, False if it did not exist.
409
+ '''
410
+ path = self.getHostCsrPath(name)
411
+ if path is None:
412
+ return False
413
+ try:
414
+ os.unlink(path)
415
+ except Exception as e: # pragma: no cover
416
+ raise s_exc.SynErr(mesg=f'Failed to delete CSR {path} - {e}') from e
417
+ if outp:
418
+ outp.printf(f'Deleted CSR at {path}')
419
+ return True
420
+
400
421
  def genUserCert(self,
401
422
  name: str,
402
423
  signas: StrOrNone = None,
@@ -697,6 +718,28 @@ class CertDir:
697
718
  '''
698
719
  return self._genPkeyCsr(name, 'users', outp=outp)
699
720
 
721
+ def delUserCsr(self, name: str, outp: OutPutOrNone = None) -> bool:
722
+ '''
723
+ Delete an existing user CSR.
724
+
725
+ Args:
726
+ name: The name of the user CSR.
727
+ outp: The output buffer.
728
+
729
+ Returns:
730
+ bool: True if the CSR is deleted, False if it did not exist.
731
+ '''
732
+ path = self.getUserCsrPath(name)
733
+ if path is None:
734
+ return False
735
+ try:
736
+ os.unlink(path)
737
+ except Exception as e: # pragma: no cover
738
+ raise s_exc.SynErr(mesg=f'Failed to delete CSR {path} - {e}') from e
739
+ if outp:
740
+ outp.printf(f'Deleted CSR at {path}')
741
+ return True
742
+
700
743
  def getCaCert(self, name: str) -> CertOrNone:
701
744
  '''
702
745
  Loads the X509 object for a given CA.
synapse/lib/cmd.py CHANGED
@@ -1,6 +1,11 @@
1
+ import sys
2
+ import asyncio
1
3
  import argparse
2
4
 
3
5
  import synapse.exc as s_exc
6
+ import synapse.common as s_common
7
+
8
+ import synapse.lib.coro as s_coro
4
9
  import synapse.lib.output as s_output
5
10
 
6
11
  class Parser(argparse.ArgumentParser):
@@ -25,6 +30,7 @@ class Parser(argparse.ArgumentParser):
25
30
 
26
31
  if message is not None:
27
32
  self.outp.printf(message)
33
+
28
34
  raise s_exc.ParserExit(mesg=message, status=status)
29
35
 
30
36
  def _print_message(self, text, fd=None):
@@ -32,3 +38,21 @@ class Parser(argparse.ArgumentParser):
32
38
  Note: this overrides an existing method in ArgumentParser
33
39
  '''
34
40
  self.outp.printf(text)
41
+
42
+ async def wrapmain(func): # pragma: no cover
43
+
44
+ try:
45
+ return await func(sys.argv[1:])
46
+
47
+ except s_exc.ParserExit:
48
+ return 1
49
+
50
+ except Exception as e:
51
+ print(f'ERROR: {s_exc.reprexc(e)}')
52
+ return 1
53
+
54
+ finally:
55
+ await s_coro.await_bg_tasks(timeout=10)
56
+
57
+ def exitmain(func): # pragma: no cover
58
+ sys.exit(asyncio.run(wrapmain(func)))
synapse/lib/coro.py CHANGED
@@ -175,10 +175,16 @@ def create_task(coro):
175
175
 
176
176
  return task
177
177
 
178
- async def await_bg_tasks():
178
+ async def await_bg_tasks(timeout=None):
179
+
179
180
  if not bgtasks:
180
181
  return []
181
- return await asyncio.gather(*tuple(bgtasks), return_exceptions=True)
182
+
183
+ coro = asyncio.gather(*tuple(bgtasks), return_exceptions=True)
184
+ try:
185
+ return await s_common.wait_for(coro, timeout)
186
+ except (asyncio.CancelledError, asyncio.TimeoutError):
187
+ return []
182
188
 
183
189
  class GenrHelp:
184
190
 
synapse/lib/drive.py CHANGED
@@ -69,6 +69,9 @@ class Drive(s_base.Base):
69
69
 
70
70
  def getItemInfo(self, iden, typename=None):
71
71
  info = self._getItemInfo(s_common.uhex(iden))
72
+ if not info:
73
+ return
74
+
72
75
  if typename is not None:
73
76
  self._reqInfoType(info, typename)
74
77
  return info
@@ -210,7 +213,7 @@ class Drive(s_base.Base):
210
213
 
211
214
  def _setItemPerm(self, bidn, perm):
212
215
  info = self._reqItemInfo(bidn)
213
- info['perm'] = perm
216
+ info['permissions'] = perm
214
217
  s_schemas.reqValidDriveInfo(info)
215
218
  self.slab.put(LKEY_INFO + bidn, s_msgpack.en(info), db=self.dbname)
216
219
  return info
@@ -286,7 +289,7 @@ class Drive(s_base.Base):
286
289
  info['kids'] = 0
287
290
  info['parent'] = pariden
288
291
 
289
- info.setdefault('perm', {'users': {}, 'roles': {}})
292
+ info.setdefault('permissions', {'users': {}, 'roles': {}})
290
293
  info.setdefault('version', (0, 0, 0))
291
294
 
292
295
  s_schemas.reqValidDriveInfo(info)
@@ -447,6 +450,8 @@ class Drive(s_base.Base):
447
450
 
448
451
  if vers is None:
449
452
  info = self._getItemInfo(bidn)
453
+ if info is None:
454
+ return None
450
455
  vers = info.get('version')
451
456
 
452
457
  versindx = getVersIndx(vers)
synapse/lib/link.py CHANGED
@@ -61,7 +61,15 @@ async def unixconnect(path):
61
61
  '''
62
62
  Connect to a PF_UNIX server listening on the given path.
63
63
  '''
64
- reader, writer = await asyncio.open_unix_connection(path=path)
64
+ try:
65
+ reader, writer = await asyncio.open_unix_connection(path=path)
66
+ except ConnectionRefusedError as e:
67
+ mesg = f'Cell path is not listening: {path}'
68
+ raise s_exc.LinkErr(mesg=mesg) from e
69
+ except FileNotFoundError as e:
70
+ mesg = f'Cell path does not exist: {path}'
71
+ raise s_exc.NoSuchPath(mesg=mesg) from e
72
+
65
73
  info = {'path': path, 'unix': True}
66
74
  return await Link.anit(reader, writer, info=info)
67
75
 
@@ -321,9 +329,9 @@ class Link(s_base.Base):
321
329
  raise
322
330
 
323
331
  except Exception as e:
324
- mesg = f'rx error {e} link={self.getAddrInfo()}'
332
+ mesg = f'rx closed unexpectedly {e} link={self.getAddrInfo()}'
325
333
  if isinstance(e, (BrokenPipeError, ConnectionResetError)):
326
- logger.warning(mesg)
334
+ logger.debug(mesg)
327
335
  else:
328
336
  logger.exception(mesg)
329
337
  await self.fini()
synapse/lib/schemas.py CHANGED
@@ -469,7 +469,7 @@ driveInfoSchema = {
469
469
  'parent': {'type': 'string', 'pattern': s_config.re_iden},
470
470
  'type': {'type': 'string', 'pattern': re_drivename},
471
471
  'name': {'type': 'string', 'pattern': re_drivename},
472
- 'perm': s_msgpack.deepcopy(easyPermSchema),
472
+ 'permissions': s_msgpack.deepcopy(easyPermSchema),
473
473
  'kids': {'type': 'number', 'minimum': 0},
474
474
  'created': {'type': 'number'},
475
475
  'creator': {'type': 'string', 'pattern': s_config.re_iden},
synapse/lib/scrape.py CHANGED
@@ -65,6 +65,8 @@ inverse_prefixs = {
65
65
  '<': '>',
66
66
  '{': '}',
67
67
  '(': ')',
68
+ '\u2018': '\u2019', # Unicode Left Single Quotation Mark and Right Single Quotation Mark
69
+ '\u201c': '\u201d', # Unicode Left Double Quotation Mark and Right Double Quotation Mark
68
70
  }
69
71
 
70
72
  cve_dashes = ''.join(('-',) + s_chop.unicode_dashes)
@@ -287,7 +289,7 @@ def url_scheme_check(match: regex.Match):
287
289
  scrape_types = [ # type: ignore
288
290
  ('file:path', linux_path_regex, {'callback': linux_path_check, 'flags': regex.VERBOSE}),
289
291
  ('file:path', windows_path_regex, {'callback': windows_path_check, 'flags': regex.VERBOSE}),
290
- ('inet:url', r'(?P<prefix>[\\{<\(\[]?)(?P<valu>[a-zA-Z][a-zA-Z0-9]*://(?(?=[,.]+[ \'\"\t\n\r\f\v])|[^ \'\"\t\n\r\f\v])+)',
292
+ ('inet:url', r'(?P<prefix>[‘“\\{<\(\[]?)(?P<valu>[a-zA-Z][a-zA-Z0-9]*://(?(?=[,.]+[ \'\"\t\n\r\f\v])|[^ \'\"\t\n\r\f\v])+)',
291
293
  {'callback': url_scheme_check}),
292
294
  ('inet:url', r'(["\'])?(?P<valu>\\[^\n]+?)(?(1)\1|\s)', {'callback': unc_path_check}),
293
295
  ('inet:email', r'(?=(?:[^a-z0-9_.+-]|^)(?P<valu>[a-z0-9_\.\-+]{1,256}@(?:[a-z0-9_-]{1,63}\.){1,10}(?:%s))(?:[^a-z0-9_.-]|[.\s]|$))' % tldcat, {}),
synapse/lib/snap.py CHANGED
@@ -1325,13 +1325,13 @@ class Snap(s_base.Base):
1325
1325
 
1326
1326
  if etyp == s_layer.EDIT_NODE_ADD:
1327
1327
  node.bylayer['ndef'] = wlyr.iden
1328
- callbacks.append((node.form.wasAdded, (node,), {}))
1329
- callbacks.append((self.view.runNodeAdd, (node,), {}))
1328
+ callbacks.append((node.form.wasAdded, (node,)))
1329
+ callbacks.append((self.view.runNodeAdd, (node,)))
1330
1330
  continue
1331
1331
 
1332
1332
  if etyp == s_layer.EDIT_NODE_DEL:
1333
- callbacks.append((node.form.wasDeleted, (node,), {}))
1334
- callbacks.append((self.view.runNodeDel, (node,), {}))
1333
+ callbacks.append((node.form.wasDeleted, (node,)))
1334
+ callbacks.append((self.view.runNodeDel, (node,)))
1335
1335
  continue
1336
1336
 
1337
1337
  if etyp == s_layer.EDIT_PROP_SET:
@@ -1346,8 +1346,8 @@ class Snap(s_base.Base):
1346
1346
  node.props[name] = valu
1347
1347
  node.bylayer['props'][name] = wlyr.iden
1348
1348
 
1349
- callbacks.append((prop.wasSet, (node, oldv), {}))
1350
- callbacks.append((self.view.runPropSet, (node, prop, oldv), {}))
1349
+ callbacks.append((prop.wasSet, (node, oldv)))
1350
+ callbacks.append((self.view.runPropSet, (node, prop, oldv)))
1351
1351
  continue
1352
1352
 
1353
1353
  if etyp == s_layer.EDIT_PROP_DEL:
@@ -1362,8 +1362,8 @@ class Snap(s_base.Base):
1362
1362
  node.props.pop(name, None)
1363
1363
  node.bylayer['props'].pop(name, None)
1364
1364
 
1365
- callbacks.append((prop.wasDel, (node, oldv), {}))
1366
- callbacks.append((self.view.runPropSet, (node, prop, oldv), {}))
1365
+ callbacks.append((prop.wasDel, (node, oldv)))
1366
+ callbacks.append((self.view.runPropSet, (node, prop, oldv)))
1367
1367
  continue
1368
1368
 
1369
1369
  if etyp == s_layer.EDIT_TAG_SET:
@@ -1373,8 +1373,7 @@ class Snap(s_base.Base):
1373
1373
  node.tags[tag] = valu
1374
1374
  node.bylayer['tags'][tag] = wlyr.iden
1375
1375
 
1376
- callbacks.append((self.view.runTagAdd, (node, tag, valu), {}))
1377
- callbacks.append((self.wlyr.fire, ('tag:add', ), {'tag': tag, 'node': node.iden()}))
1376
+ callbacks.append((self.view.runTagAdd, (node, tag, valu)))
1378
1377
  continue
1379
1378
 
1380
1379
  if etyp == s_layer.EDIT_TAG_DEL:
@@ -1384,8 +1383,7 @@ class Snap(s_base.Base):
1384
1383
  node.tags.pop(tag, None)
1385
1384
  node.bylayer['tags'].pop(tag, None)
1386
1385
 
1387
- callbacks.append((self.view.runTagDel, (node, tag, oldv), {}))
1388
- callbacks.append((self.wlyr.fire, ('tag:del', ), {'tag': tag, 'node': node.iden()}))
1386
+ callbacks.append((self.view.runTagDel, (node, tag, oldv)))
1389
1387
  continue
1390
1388
 
1391
1389
  if etyp == s_layer.EDIT_TAGPROP_SET:
@@ -1420,14 +1418,14 @@ class Snap(s_base.Base):
1420
1418
  if etyp == s_layer.EDIT_EDGE_ADD:
1421
1419
  verb, n2iden = parms
1422
1420
  n2 = await self.getNodeByBuid(s_common.uhex(n2iden))
1423
- callbacks.append((self.view.runEdgeAdd, (node, verb, n2), {}))
1421
+ callbacks.append((self.view.runEdgeAdd, (node, verb, n2)))
1424
1422
 
1425
1423
  if etyp == s_layer.EDIT_EDGE_DEL:
1426
1424
  verb, n2iden = parms
1427
1425
  n2 = await self.getNodeByBuid(s_common.uhex(n2iden))
1428
- callbacks.append((self.view.runEdgeDel, (node, verb, n2), {}))
1426
+ callbacks.append((self.view.runEdgeDel, (node, verb, n2)))
1429
1427
 
1430
- [await func(*args, **kwargs) for (func, args, kwargs) in callbacks]
1428
+ [await func(*args) for (func, args) in callbacks]
1431
1429
 
1432
1430
  if actualedits:
1433
1431
  await self.fire('node:edits', edits=actualedits)
@@ -1456,7 +1454,9 @@ class Snap(s_base.Base):
1456
1454
  if isinstance(valu, dict):
1457
1455
  form = self.core.model.reqForm(name)
1458
1456
  if isinstance(form.type, s_types.Guid):
1459
- return await self._addGuidNodeByDict(form, valu, props=props)
1457
+ norms, props = await self._normGuidNodeDict(form, valu, props=props)
1458
+ valu = await self._addGuidNodeByDict(form, norms, props)
1459
+ return await self.getNodeByNdef((name, valu))
1460
1460
 
1461
1461
  async with self.getEditor() as editor:
1462
1462
  protonode = await editor.addNode(name, valu, props=props, norminfo=norminfo)
@@ -1466,100 +1466,109 @@ class Snap(s_base.Base):
1466
1466
  # the newly constructed node is cached
1467
1467
  return await self.getNodeByBuid(protonode.buid)
1468
1468
 
1469
- async def _addGuidNodeByDict(self, form, vals, props=None):
1469
+ async def _addGuidNodeByDict(self, form, norms, props):
1470
1470
 
1471
- if props is None:
1472
- props = {}
1473
-
1474
- trycast = vals.pop('$try', False)
1475
- addprops = vals.pop('$props', None)
1476
-
1477
- if not vals:
1478
- mesg = f'No values provided for form {form.full}'
1479
- raise s_exc.BadTypeValu(mesg=mesg)
1480
-
1481
- for name, valu in list(props.items()):
1482
- try:
1483
- props[name] = form.reqProp(name).type.norm(valu)
1484
- except s_exc.BadTypeValu as e:
1485
- mesg = e.get('mesg')
1486
- e.update({
1487
- 'prop': name,
1488
- 'form': form.name,
1489
- 'mesg': f'Bad value for prop {form.name}:{name}: {mesg}',
1490
- })
1491
- raise e
1492
-
1493
- if addprops is not None:
1494
- for name, valu in addprops.items():
1495
- try:
1496
- props[name] = form.reqProp(name).type.norm(valu)
1497
- except s_exc.BadTypeValu as e:
1498
- mesg = e.get("mesg")
1499
- if not trycast:
1500
- e.update({
1501
- 'prop': name,
1502
- 'form': form.name,
1503
- 'mesg': f'Bad value for prop {form.name}:{name}: {mesg}'
1504
- })
1505
- raise e
1506
- await self.warn(f'Skipping bad value for prop {form.name}:{name}: {mesg}')
1471
+ for name, info in norms.items():
1472
+ if info[0].isform:
1473
+ valu = await self._addGuidNodeByDict(*info)
1474
+ norms[name] = (form.prop(name), valu, {})
1507
1475
 
1508
- norms, proplist = self._normGuidNodeDict(form, vals)
1476
+ for name, info in props.items():
1477
+ if info[0].isform:
1478
+ valu = await self._addGuidNodeByDict(*info)
1479
+ props[name] = (form.prop(name), valu, {})
1509
1480
 
1510
- iden = s_common.guid(proplist)
1511
- node = await self._getGuidNodeByNorms(form, iden, norms)
1481
+ node = await self._getGuidNodeByNorms(form, norms)
1512
1482
 
1513
1483
  async with self.getEditor() as editor:
1514
1484
 
1515
1485
  if node is not None:
1516
1486
  proto = editor.loadNode(node)
1517
1487
  else:
1518
- proto = await editor.addNode(form.name, iden)
1488
+ proplist = [(name, info[1]) for name, info in norms.items()]
1489
+ proplist.sort()
1490
+
1491
+ proto = await editor.addNode(form.name, proplist)
1519
1492
  for name, (prop, valu, info) in norms.items():
1520
1493
  await proto.set(name, valu, norminfo=info)
1521
1494
 
1522
1495
  # ensure the non-deconf props are set
1523
- for name, (valu, info) in props.items():
1496
+ for name, (prop, valu, info) in props.items():
1524
1497
  await proto.set(name, valu, norminfo=info)
1525
1498
 
1526
- return await self.getNodeByBuid(proto.buid)
1499
+ return proto.valu
1500
+
1501
+ async def _normGuidNodeDict(self, form, vals, props=None):
1502
+
1503
+ if props is None:
1504
+ props = {}
1505
+
1506
+ trycast = vals.pop('$try', False)
1507
+ addprops = vals.pop('$props', None)
1508
+
1509
+ if not vals:
1510
+ mesg = f'No values provided for form {form.full}'
1511
+ raise s_exc.BadTypeValu(mesg=mesg)
1512
+
1513
+ props |= await self._normGuidNodeProps(form, props)
1527
1514
 
1528
- def _normGuidNodeDict(self, form, props):
1515
+ if addprops:
1516
+ props |= await self._normGuidNodeProps(form, addprops, trycast=trycast)
1517
+
1518
+ norms = await self._normGuidNodeProps(form, vals)
1519
+
1520
+ return norms, props
1521
+
1522
+ async def _normGuidNodeProps(self, form, props, trycast=False):
1529
1523
 
1530
1524
  norms = {}
1531
- proplist = []
1532
1525
 
1533
- for name, valu in props.items():
1526
+ for name, valu in list(props.items()):
1527
+ prop = form.reqProp(name)
1528
+
1529
+ if isinstance(valu, dict) and isinstance(prop.type, s_types.Guid):
1530
+ pform = self.core.model.reqForm(prop.type.name)
1531
+ gnorm, gprop = await self._normGuidNodeDict(pform, valu)
1532
+ norms[name] = (pform, gnorm, gprop)
1533
+ continue
1534
1534
 
1535
1535
  try:
1536
- prop = form.reqProp(name)
1537
- norm, norminfo = prop.type.norm(valu)
1536
+ norms[name] = (prop, *prop.type.norm(valu))
1538
1537
 
1539
- norms[name] = (prop, norm, norminfo)
1540
- proplist.append((name, norm))
1541
1538
  except s_exc.BadTypeValu as e:
1542
- mesg = e.get('mesg')
1543
- e.update({
1544
- 'prop': name,
1545
- 'form': form.name,
1546
- 'mesg': f'Bad value for prop {form.name}:{name}: {mesg}',
1547
- })
1548
- raise e
1549
-
1550
- proplist.sort()
1539
+ if not trycast:
1540
+ if 'prop' not in e.errinfo:
1541
+ mesg = e.get('mesg')
1542
+ e.update({
1543
+ 'prop': name,
1544
+ 'form': form.name,
1545
+ 'mesg': f'Bad value for prop {form.name}:{name}: {mesg}',
1546
+ })
1547
+ raise e
1551
1548
 
1552
- return norms, proplist
1549
+ return norms
1553
1550
 
1554
1551
  async def _getGuidNodeByDict(self, form, props):
1555
- norms, proplist = self._normGuidNodeDict(form, props)
1556
- return await self._getGuidNodeByNorms(form, s_common.guid(proplist), norms)
1552
+ norms, _ = await self._normGuidNodeDict(form, props)
1553
+ return await self._getGuidNodeByNorms(form, norms)
1554
+
1555
+ async def _getGuidNodeByNorms(self, form, norms):
1557
1556
 
1558
- async def _getGuidNodeByNorms(self, form, iden, norms):
1557
+ proplist = []
1558
+ for name, info in norms.items():
1559
+ if info[0].isform:
1560
+ if (node := await self._getGuidNodeByNorms(*info[:2])) is None:
1561
+ return
1562
+ valu = node.ndef[1]
1563
+ norms[name] = (form.prop(name), valu, {})
1564
+ proplist.append((name, valu))
1565
+ else:
1566
+ proplist.append((name, info[1]))
1559
1567
 
1560
1568
  # check first for an exact match via our same deconf strategy
1569
+ proplist.sort()
1561
1570
 
1562
- node = await self.getNodeByNdef((form.full, iden))
1571
+ node = await self.getNodeByNdef((form.full, s_common.guid(proplist)))
1563
1572
  if node is not None:
1564
1573
 
1565
1574
  # ensure we still match the property deconf criteria
synapse/lib/storm.py CHANGED
@@ -1454,7 +1454,8 @@ class StormDmon(s_base.Base):
1454
1454
  viewiden = opts.get('view')
1455
1455
 
1456
1456
  info = {'iden': self.iden, 'name': self.ddef.get('name', 'storm dmon'), 'view': viewiden}
1457
- await self.core.boss.promote('storm:dmon', user=self.user, info=info)
1457
+
1458
+ await self.core.boss.promote('storm:dmon', user=self.user, info=info, background=True)
1458
1459
 
1459
1460
  def dmonPrint(evnt):
1460
1461
  self._runLogAdd(evnt)
@@ -4,6 +4,7 @@ import aioimaplib
4
4
 
5
5
  import synapse.exc as s_exc
6
6
  import synapse.common as s_common
7
+ import synapse.lib.coro as s_coro
7
8
  import synapse.lib.stormtypes as s_stormtypes
8
9
 
9
10
  async def run_imap_coro(coro):
@@ -88,8 +89,8 @@ class ImapLib(s_stormtypes.Lib):
88
89
  imap_cli = aioimaplib.IMAP4(host=host, port=port, timeout=timeout)
89
90
 
90
91
  async def fini():
91
- # call protocol.logout() so fini() doesn't hang
92
- await s_common.wait_for(imap_cli.protocol.logout(), 5)
92
+ # call protocol.logout() via a background task
93
+ s_coro.create_task(s_common.wait_for(imap_cli.protocol.logout(), 5))
93
94
 
94
95
  self.runt.snap.onfini(fini)
95
96
 
@@ -37,6 +37,7 @@ class LibSpooled(s_stormtypes.Lib):
37
37
  async def _methSet(self, *vals):
38
38
  core = self.runt.snap.core
39
39
  spool = await s_spooled.Set.anit(dirn=core.dirn, cell=core, size=1000)
40
+ self.runt.snap.onfini(spool)
40
41
 
41
42
  valu = list(vals)
42
43
  for item in valu:
@@ -107,3 +108,6 @@ class SpooledSet(s_stormtypes.Set):
107
108
 
108
109
  async def value(self):
109
110
  return set([x async for x in self.valu])
111
+
112
+ async def bool(self):
113
+ return bool(len(self.valu))
synapse/lib/stormtypes.py CHANGED
@@ -4731,6 +4731,9 @@ class Str(Prim):
4731
4731
  return str(self) == str(othr)
4732
4732
  return False
4733
4733
 
4734
+ async def bool(self):
4735
+ return bool(self.valu)
4736
+
4734
4737
  @stormfunc(readonly=True)
4735
4738
  async def _methStrFind(self, valu):
4736
4739
  text = await tostr(valu)
@@ -4970,6 +4973,9 @@ class Bytes(Prim):
4970
4973
  return self.valu == othr.valu
4971
4974
  return False
4972
4975
 
4976
+ async def bool(self):
4977
+ return bool(self.valu)
4978
+
4973
4979
  async def _storm_copy(self):
4974
4980
  item = await s_coro.ornot(self.value)
4975
4981
  return s_msgpack.deepcopy(item, use_list=True)
@@ -5042,6 +5048,9 @@ class Dict(Prim):
5042
5048
  def __len__(self):
5043
5049
  return len(self.valu)
5044
5050
 
5051
+ async def bool(self):
5052
+ return bool(self.valu)
5053
+
5045
5054
  async def _storm_copy(self):
5046
5055
  item = await s_coro.ornot(self.value)
5047
5056
  return s_msgpack.deepcopy(item, use_list=True)
@@ -5194,6 +5203,9 @@ class Set(Prim):
5194
5203
  def __len__(self):
5195
5204
  return len(self.valu)
5196
5205
 
5206
+ async def bool(self):
5207
+ return bool(self.valu)
5208
+
5197
5209
  async def _methSetSize(self):
5198
5210
  return len(self)
5199
5211
 
@@ -5384,6 +5396,9 @@ class List(Prim):
5384
5396
  def __len__(self):
5385
5397
  return len(self.valu)
5386
5398
 
5399
+ async def bool(self):
5400
+ return bool(self.valu)
5401
+
5387
5402
  @stormfunc(readonly=True)
5388
5403
  async def _methListHas(self, valu):
5389
5404
  if valu in self.valu:
@@ -5519,6 +5534,9 @@ class Bool(Prim):
5519
5534
  def __hash__(self):
5520
5535
  return hash((self._storm_typename, self.value()))
5521
5536
 
5537
+ async def bool(self):
5538
+ return bool(self.valu)
5539
+
5522
5540
  @registry.registerType
5523
5541
  class Number(Prim):
5524
5542
  '''
synapse/lib/task.py CHANGED
@@ -22,6 +22,7 @@ class Task(s_base.Base):
22
22
  info = {}
23
23
 
24
24
  self.boss = boss
25
+ self.background = False
25
26
 
26
27
  task._syn_task = self
27
28