synapse 2.178.0__py311-none-any.whl → 2.180.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 (62) hide show
  1. synapse/cortex.py +166 -31
  2. synapse/datamodel.py +47 -1
  3. synapse/exc.py +1 -0
  4. synapse/lib/aha.py +2 -1
  5. synapse/lib/ast.py +110 -76
  6. synapse/lib/base.py +12 -3
  7. synapse/lib/cell.py +150 -11
  8. synapse/lib/coro.py +14 -0
  9. synapse/lib/drive.py +551 -0
  10. synapse/lib/layer.py +1 -1
  11. synapse/lib/lmdbslab.py +2 -0
  12. synapse/lib/modelrev.py +5 -1
  13. synapse/lib/node.py +14 -4
  14. synapse/lib/schemas.py +97 -0
  15. synapse/lib/snap.py +36 -11
  16. synapse/lib/storm.py +9 -5
  17. synapse/lib/stormhttp.py +1 -0
  18. synapse/lib/stormlib/modelext.py +29 -3
  19. synapse/lib/stormlib/stix.py +44 -17
  20. synapse/lib/stormlib/vault.py +2 -2
  21. synapse/lib/stormtypes.py +1 -1
  22. synapse/lib/types.py +9 -0
  23. synapse/lib/version.py +2 -2
  24. synapse/lookup/pe.py +303 -38
  25. synapse/models/auth.py +2 -0
  26. synapse/models/dns.py +24 -1
  27. synapse/models/geopol.py +3 -0
  28. synapse/models/geospace.py +4 -1
  29. synapse/models/inet.py +1 -0
  30. synapse/models/infotech.py +135 -92
  31. synapse/models/person.py +5 -2
  32. synapse/models/telco.py +3 -0
  33. synapse/tests/test_cortex.py +45 -1
  34. synapse/tests/test_lib_aha.py +17 -0
  35. synapse/tests/test_lib_ast.py +231 -0
  36. synapse/tests/test_lib_cell.py +225 -0
  37. synapse/tests/test_lib_coro.py +12 -0
  38. synapse/tests/test_lib_layer.py +22 -0
  39. synapse/tests/test_lib_modelrev.py +7 -0
  40. synapse/tests/test_lib_node.py +12 -1
  41. synapse/tests/test_lib_storm.py +32 -7
  42. synapse/tests/test_lib_stormhttp.py +40 -0
  43. synapse/tests/test_lib_stormlib_modelext.py +55 -3
  44. synapse/tests/test_lib_stormlib_stix.py +15 -0
  45. synapse/tests/test_lib_stormlib_vault.py +11 -1
  46. synapse/tests/test_lib_stormtypes.py +5 -0
  47. synapse/tests/test_lib_types.py +9 -0
  48. synapse/tests/test_model_dns.py +8 -0
  49. synapse/tests/test_model_geopol.py +2 -0
  50. synapse/tests/test_model_geospace.py +3 -1
  51. synapse/tests/test_model_inet.py +10 -1
  52. synapse/tests/test_model_infotech.py +47 -0
  53. synapse/tests/test_model_person.py +2 -0
  54. synapse/tests/test_model_syn.py +11 -0
  55. synapse/tests/test_model_telco.py +2 -1
  56. synapse/tests/test_utils_stormcov.py +1 -1
  57. synapse/tools/changelog.py +28 -0
  58. {synapse-2.178.0.dist-info → synapse-2.180.0.dist-info}/METADATA +1 -1
  59. {synapse-2.178.0.dist-info → synapse-2.180.0.dist-info}/RECORD +62 -61
  60. {synapse-2.178.0.dist-info → synapse-2.180.0.dist-info}/WHEEL +1 -1
  61. {synapse-2.178.0.dist-info → synapse-2.180.0.dist-info}/LICENSE +0 -0
  62. {synapse-2.178.0.dist-info → synapse-2.180.0.dist-info}/top_level.txt +0 -0
synapse/lib/ast.py CHANGED
@@ -419,7 +419,8 @@ class SubGraph:
419
419
  await asyncio.sleep(0)
420
420
  continue
421
421
 
422
- yield (pivonode, path.fork(pivonode), {'type': 'prop', 'prop': propname})
422
+ link = {'type': 'prop', 'prop': propname}
423
+ yield (pivonode, path.fork(pivonode, link), link)
423
424
 
424
425
  for iden in existing:
425
426
  buid = s_common.uhex(iden)
@@ -1436,12 +1437,13 @@ class LiftOper(Oper):
1436
1437
 
1437
1438
  return
1438
1439
 
1440
+ link = {'type': 'runtime'}
1439
1441
  async for node, path in genr:
1440
1442
 
1441
1443
  yield node, path
1442
1444
 
1443
1445
  async for subn in self.lift(runt, path):
1444
- yield subn, path.fork(subn)
1446
+ yield subn, path.fork(subn, link)
1445
1447
 
1446
1448
  async def lift(self, runt, path): # pragma: no cover
1447
1449
  raise NotImplementedError('Must define lift(runt, path)')
@@ -1913,8 +1915,9 @@ class PivotOut(PivotOper):
1913
1915
  # <syn:tag> -> * is "from tags to nodes with tags"
1914
1916
  if node.form.name == 'syn:tag':
1915
1917
 
1918
+ link = {'type': 'tag', 'tag': node.ndef[1], 'reverse': True}
1916
1919
  async for pivo in runt.snap.nodesByTag(node.ndef[1]):
1917
- yield pivo, path.fork(pivo)
1920
+ yield pivo, path.fork(pivo, link)
1918
1921
 
1919
1922
  return
1920
1923
 
@@ -1925,7 +1928,7 @@ class PivotOut(PivotOper):
1925
1928
  logger.warning(f'Missing node corresponding to ndef {n2def} on edge')
1926
1929
  return
1927
1930
 
1928
- yield pivo, path.fork(pivo)
1931
+ yield pivo, path.fork(pivo, {'type': 'prop', 'prop': 'n2'})
1929
1932
  return
1930
1933
 
1931
1934
  for name, prop in node.form.props.items():
@@ -1934,27 +1937,28 @@ class PivotOut(PivotOper):
1934
1937
  if valu is None:
1935
1938
  continue
1936
1939
 
1940
+ link = {'type': 'prop', 'prop': prop.name}
1937
1941
  # if the outbound prop is an ndef...
1938
1942
  if isinstance(prop.type, s_types.Ndef):
1939
1943
  pivo = await runt.snap.getNodeByNdef(valu)
1940
1944
  if pivo is None:
1941
1945
  continue
1942
1946
 
1943
- yield pivo, path.fork(pivo)
1947
+ yield pivo, path.fork(pivo, link)
1944
1948
  continue
1945
1949
 
1946
1950
  if isinstance(prop.type, s_types.Array):
1947
1951
  if isinstance(prop.type.arraytype, s_types.Ndef):
1948
1952
  for item in valu:
1949
1953
  if (pivo := await runt.snap.getNodeByNdef(item)) is not None:
1950
- yield pivo, path.fork(pivo)
1954
+ yield pivo, path.fork(pivo, link)
1951
1955
  continue
1952
1956
 
1953
1957
  typename = prop.type.opts.get('type')
1954
1958
  if runt.model.forms.get(typename) is not None:
1955
1959
  for item in valu:
1956
- async for pivo in runt.snap.nodesByPropValu(typename, '=', item):
1957
- yield pivo, path.fork(pivo)
1960
+ async for pivo in runt.snap.nodesByPropValu(typename, '=', item, norm=False):
1961
+ yield pivo, path.fork(pivo, link)
1958
1962
 
1959
1963
  form = runt.model.forms.get(prop.type.name)
1960
1964
  if form is None:
@@ -1962,7 +1966,7 @@ class PivotOut(PivotOper):
1962
1966
 
1963
1967
  if prop.isrunt:
1964
1968
  async for pivo in runt.snap.nodesByPropValu(form.name, '=', valu):
1965
- yield pivo, path.fork(pivo)
1969
+ yield pivo, path.fork(pivo, link)
1966
1970
  continue
1967
1971
 
1968
1972
  pivo = await runt.snap.getNodeByNdef((form.name, valu))
@@ -1973,7 +1977,7 @@ class PivotOut(PivotOper):
1973
1977
  if pivo.buid == node.buid:
1974
1978
  continue
1975
1979
 
1976
- yield pivo, path.fork(pivo)
1980
+ yield pivo, path.fork(pivo, link)
1977
1981
 
1978
1982
  class N1WalkNPivo(PivotOut):
1979
1983
 
@@ -1990,7 +1994,7 @@ class N1WalkNPivo(PivotOut):
1990
1994
  async for (verb, iden) in node.iterEdgesN1():
1991
1995
  wnode = await runt.snap.getNodeByBuid(s_common.uhex(iden))
1992
1996
  if wnode is not None:
1993
- yield wnode, path.fork(wnode)
1997
+ yield wnode, path.fork(wnode, {'type': 'edge', 'verb': verb})
1994
1998
 
1995
1999
  class PivotToTags(PivotOper):
1996
2000
  '''
@@ -2057,7 +2061,7 @@ class PivotToTags(PivotOper):
2057
2061
  if pivo is None:
2058
2062
  continue
2059
2063
 
2060
- yield pivo, path.fork(pivo)
2064
+ yield pivo, path.fork(pivo, {'type': 'tag', 'tag': name})
2061
2065
 
2062
2066
  class PivotIn(PivotOper):
2063
2067
  '''
@@ -2083,23 +2087,27 @@ class PivotIn(PivotOper):
2083
2087
 
2084
2088
  pivo = await runt.snap.getNodeByNdef(ndef)
2085
2089
  if pivo is not None:
2086
- yield pivo, path.fork(pivo)
2090
+ yield pivo, path.fork(pivo, {'type': 'prop', 'prop': 'n1', 'reverse': True})
2087
2091
 
2088
2092
  return
2089
2093
 
2090
2094
  name, valu = node.ndef
2091
2095
 
2092
2096
  for prop in runt.model.getPropsByType(name):
2093
- async for pivo in runt.snap.nodesByPropValu(prop.full, '=', valu):
2094
- yield pivo, path.fork(pivo)
2097
+ link = {'type': 'prop', 'prop': prop.name, 'reverse': True}
2098
+ norm = node.form.typehash is not prop.typehash
2099
+ async for pivo in runt.snap.nodesByPropValu(prop.full, '=', valu, norm=norm):
2100
+ yield pivo, path.fork(pivo, link)
2095
2101
 
2096
2102
  for prop in runt.model.getArrayPropsByType(name):
2097
- async for pivo in runt.snap.nodesByPropArray(prop.full, '=', valu):
2098
- yield pivo, path.fork(pivo)
2103
+ norm = node.form.typehash is not prop.arraytypehash
2104
+ link = {'type': 'prop', 'prop': prop.name, 'reverse': True}
2105
+ async for pivo in runt.snap.nodesByPropArray(prop.full, '=', valu, norm=norm):
2106
+ yield pivo, path.fork(pivo, link)
2099
2107
 
2100
- async for refsbuid in runt.snap.getNdefRefs(node.buid):
2108
+ async for refsbuid, prop in runt.snap.getNdefRefs(node.buid, props=True):
2101
2109
  pivo = await runt.snap.getNodeByBuid(refsbuid)
2102
- yield pivo, path.fork(pivo)
2110
+ yield pivo, path.fork(pivo, {'type': 'prop', 'prop': prop, 'reverse': True})
2103
2111
 
2104
2112
  class N2WalkNPivo(PivotIn):
2105
2113
 
@@ -2116,7 +2124,7 @@ class N2WalkNPivo(PivotIn):
2116
2124
  async for (verb, iden) in node.iterEdgesN2():
2117
2125
  wnode = await runt.snap.getNodeByBuid(s_common.uhex(iden))
2118
2126
  if wnode is not None:
2119
- yield wnode, path.fork(wnode)
2127
+ yield wnode, path.fork(wnode, {'type': 'edge', 'verb': verb, 'reverse': True})
2120
2128
 
2121
2129
  class PivotInFrom(PivotOper):
2122
2130
  '''
@@ -2135,18 +2143,19 @@ class PivotInFrom(PivotOper):
2135
2143
  if isinstance(form.type, s_types.Edge):
2136
2144
 
2137
2145
  full = form.name + ':n2'
2138
-
2146
+ link = {'type': 'prop', 'prop': 'n2', 'reverse': True}
2139
2147
  async for node, path in genr:
2140
2148
 
2141
2149
  if self.isjoin:
2142
2150
  yield node, path
2143
2151
 
2144
- async for pivo in runt.snap.nodesByPropValu(full, '=', node.ndef):
2145
- yield pivo, path.fork(pivo)
2152
+ async for pivo in runt.snap.nodesByPropValu(full, '=', node.ndef, norm=False):
2153
+ yield pivo, path.fork(pivo, link)
2146
2154
 
2147
2155
  return
2148
2156
 
2149
2157
  # edge <- form
2158
+ link = {'type': 'prop', 'prop': 'n1', 'reverse': True}
2150
2159
  async for node, path in genr:
2151
2160
 
2152
2161
  if self.isjoin:
@@ -2166,7 +2175,7 @@ class PivotInFrom(PivotOper):
2166
2175
  if pivo is None:
2167
2176
  continue
2168
2177
 
2169
- yield pivo, path.fork(pivo)
2178
+ yield pivo, path.fork(pivo, link)
2170
2179
 
2171
2180
  class FormPivot(PivotOper):
2172
2181
  '''
@@ -2179,8 +2188,9 @@ class FormPivot(PivotOper):
2179
2188
  if isinstance(prop.type, s_types.Ndef):
2180
2189
 
2181
2190
  async def pgenr(node, strict=True):
2182
- async for pivo in runt.snap.nodesByPropValu(prop.full, '=', node.ndef):
2183
- yield pivo
2191
+ link = {'type': 'prop', 'prop': prop.name, 'reverse': True}
2192
+ async for pivo in runt.snap.nodesByPropValu(prop.full, '=', node.ndef, norm=False):
2193
+ yield pivo, link
2184
2194
 
2185
2195
  elif not prop.isform:
2186
2196
 
@@ -2190,15 +2200,17 @@ class FormPivot(PivotOper):
2190
2200
  async def pgenr(node, strict=True):
2191
2201
  if isarray:
2192
2202
  if isinstance(prop.type.arraytype, s_types.Ndef):
2193
- ngenr = runt.snap.nodesByPropArray(prop.full, '=', node.ndef)
2203
+ ngenr = runt.snap.nodesByPropArray(prop.full, '=', node.ndef, norm=False)
2194
2204
  else:
2195
- ngenr = runt.snap.nodesByPropArray(prop.full, '=', node.ndef[1])
2205
+ norm = prop.arraytypehash is not node.form.typehash
2206
+ ngenr = runt.snap.nodesByPropArray(prop.full, '=', node.ndef[1], norm=norm)
2196
2207
  else:
2197
- ngenr = runt.snap.nodesByPropValu(prop.full, '=', node.ndef[1])
2208
+ norm = prop.typehash is not node.form.typehash
2209
+ ngenr = runt.snap.nodesByPropValu(prop.full, '=', node.ndef[1], norm=norm)
2198
2210
 
2199
- # TODO cache/bypass normalization in loop!
2211
+ link = {'type': 'prop', 'prop': prop.name, 'reverse': True}
2200
2212
  async for pivo in ngenr:
2201
- yield pivo
2213
+ yield pivo, link
2202
2214
 
2203
2215
  # if dest form is a subtype of a graph "edge", use N1 automatically
2204
2216
  elif isinstance(prop.type, s_types.Edge):
@@ -2206,8 +2218,9 @@ class FormPivot(PivotOper):
2206
2218
  full = prop.name + ':n1'
2207
2219
 
2208
2220
  async def pgenr(node, strict=True):
2209
- async for pivo in runt.snap.nodesByPropValu(full, '=', node.ndef):
2210
- yield pivo
2221
+ link = {'type': 'prop', 'prop': 'n1', 'reverse': True}
2222
+ async for pivo in runt.snap.nodesByPropValu(full, '=', node.ndef, norm=False):
2223
+ yield pivo, link
2211
2224
 
2212
2225
  else:
2213
2226
  # form -> form pivot is nonsensical. Lets help out...
@@ -2219,8 +2232,9 @@ class FormPivot(PivotOper):
2219
2232
 
2220
2233
  # <syn:tag> -> <form> is "from tags to nodes" pivot
2221
2234
  if node.form.name == 'syn:tag' and prop.isform:
2235
+ link = {'type': 'tag', 'tag': node.ndef[1], 'reverse': True}
2222
2236
  async for pivo in runt.snap.nodesByTag(node.ndef[1], form=prop.name):
2223
- yield pivo
2237
+ yield pivo, link
2224
2238
 
2225
2239
  return
2226
2240
 
@@ -2233,7 +2247,7 @@ class FormPivot(PivotOper):
2233
2247
 
2234
2248
  pivo = await runt.snap.getNodeByNdef(node.get('n2'))
2235
2249
  if pivo:
2236
- yield pivo
2250
+ yield pivo, {'type': 'prop', 'prop': 'n2'}
2237
2251
 
2238
2252
  return
2239
2253
 
@@ -2251,8 +2265,9 @@ class FormPivot(PivotOper):
2251
2265
 
2252
2266
  refsvalu = node.get(refsname)
2253
2267
  if refsvalu is not None:
2254
- async for pivo in runt.snap.nodesByPropValu(refsform, '=', refsvalu):
2255
- yield pivo
2268
+ link = {'type': 'prop', 'prop': refsname}
2269
+ async for pivo in runt.snap.nodesByPropValu(refsform, '=', refsvalu, norm=False):
2270
+ yield pivo, link
2256
2271
 
2257
2272
  for refsname, refsform in refs.get('array'):
2258
2273
 
@@ -2263,9 +2278,10 @@ class FormPivot(PivotOper):
2263
2278
 
2264
2279
  refsvalu = node.get(refsname)
2265
2280
  if refsvalu is not None:
2281
+ link = {'type': 'prop', 'prop': refsname}
2266
2282
  for refselem in refsvalu:
2267
- async for pivo in runt.snap.nodesByPropValu(destform.name, '=', refselem):
2268
- yield pivo
2283
+ async for pivo in runt.snap.nodesByPropValu(destform.name, '=', refselem, norm=False):
2284
+ yield pivo, link
2269
2285
 
2270
2286
  for refsname in refs.get('ndef'):
2271
2287
 
@@ -2275,17 +2291,18 @@ class FormPivot(PivotOper):
2275
2291
  if refsvalu is not None and refsvalu[0] == destform.name:
2276
2292
  pivo = await runt.snap.getNodeByNdef(refsvalu)
2277
2293
  if pivo is not None:
2278
- yield pivo
2294
+ yield pivo, {'type': 'prop', 'prop': refsname}
2279
2295
 
2280
2296
  for refsname in refs.get('ndefarray'):
2281
2297
 
2282
2298
  found = True
2283
2299
 
2284
2300
  if (refsvalu := node.get(refsname)) is not None:
2301
+ link = {'type': 'prop', 'prop': refsname}
2285
2302
  for aval in refsvalu:
2286
2303
  if aval[0] == destform.name:
2287
2304
  if (pivo := await runt.snap.getNodeByNdef(aval)) is not None:
2288
- yield pivo
2305
+ yield pivo, link
2289
2306
 
2290
2307
  #########################################################################
2291
2308
  # reverse "-> form" pivots (ie inet:fqdn -> inet:dns:a)
@@ -2300,8 +2317,9 @@ class FormPivot(PivotOper):
2300
2317
  found = True
2301
2318
 
2302
2319
  refsprop = destform.props.get(refsname)
2303
- async for pivo in runt.snap.nodesByPropValu(refsprop.full, '=', node.ndef[1]):
2304
- yield pivo
2320
+ link = {'type': 'prop', 'prop': refsname, 'reverse': True}
2321
+ async for pivo in runt.snap.nodesByPropValu(refsprop.full, '=', node.ndef[1], norm=False):
2322
+ yield pivo, link
2305
2323
 
2306
2324
  # "reverse" array references...
2307
2325
  for refsname, refsform in refs.get('array'):
@@ -2312,8 +2330,9 @@ class FormPivot(PivotOper):
2312
2330
  found = True
2313
2331
 
2314
2332
  destprop = destform.props.get(refsname)
2315
- async for pivo in runt.snap.nodesByPropArray(destprop.full, '=', node.ndef[1]):
2316
- yield pivo
2333
+ link = {'type': 'prop', 'prop': refsname, 'reverse': True}
2334
+ async for pivo in runt.snap.nodesByPropArray(destprop.full, '=', node.ndef[1], norm=False):
2335
+ yield pivo, link
2317
2336
 
2318
2337
  # "reverse" ndef references...
2319
2338
  for refsname in refs.get('ndef'):
@@ -2321,16 +2340,18 @@ class FormPivot(PivotOper):
2321
2340
  found = True
2322
2341
 
2323
2342
  refsprop = destform.props.get(refsname)
2324
- async for pivo in runt.snap.nodesByPropValu(refsprop.full, '=', node.ndef):
2325
- yield pivo
2343
+ link = {'type': 'prop', 'prop': refsname, 'reverse': True}
2344
+ async for pivo in runt.snap.nodesByPropValu(refsprop.full, '=', node.ndef, norm=False):
2345
+ yield pivo, link
2326
2346
 
2327
2347
  for refsname in refs.get('ndefarray'):
2328
2348
 
2329
2349
  found = True
2330
2350
 
2331
2351
  refsprop = destform.props.get(refsname)
2332
- async for pivo in runt.snap.nodesByPropArray(refsprop.full, '=', node.ndef):
2333
- yield pivo
2352
+ link = {'type': 'prop', 'prop': refsname, 'reverse': True}
2353
+ async for pivo in runt.snap.nodesByPropArray(refsprop.full, '=', node.ndef, norm=False):
2354
+ yield pivo, link
2334
2355
 
2335
2356
  if strict and not found:
2336
2357
  mesg = f'No pivot found for {node.form.name} -> {destform.name}.'
@@ -2358,8 +2379,8 @@ class FormPivot(PivotOper):
2358
2379
 
2359
2380
  async def listpivot(node):
2360
2381
  for pgenr in pgenrs:
2361
- async for pivo in pgenr(node, strict=False):
2362
- yield pivo
2382
+ async for pivo, valu in pgenr(node, strict=False):
2383
+ yield pivo, valu
2363
2384
 
2364
2385
  return listpivot
2365
2386
 
@@ -2380,8 +2401,8 @@ class FormPivot(PivotOper):
2380
2401
  yield node, path
2381
2402
 
2382
2403
  try:
2383
- async for pivo in pgenr(node):
2384
- yield pivo, path.fork(pivo)
2404
+ async for pivo, link in pgenr(node):
2405
+ yield pivo, path.fork(pivo, link)
2385
2406
  except (s_exc.BadTypeValu, s_exc.BadLiftValu) as e:
2386
2407
  if not warned:
2387
2408
  logger.warning(f'Caught error during pivot: {e.items()}')
@@ -2417,11 +2438,12 @@ class PropPivotOut(PivotOper):
2417
2438
  await asyncio.sleep(0)
2418
2439
  continue
2419
2440
 
2441
+ link = {'type': 'prop', 'prop': prop.name}
2420
2442
  if prop.type.isarray:
2421
2443
  if isinstance(prop.type.arraytype, s_types.Ndef):
2422
2444
  for item in valu:
2423
2445
  if (pivo := await runt.snap.getNodeByNdef(item)) is not None:
2424
- yield pivo, path.fork(pivo)
2446
+ yield pivo, path.fork(pivo, link)
2425
2447
  continue
2426
2448
 
2427
2449
  fname = prop.type.arraytype.name
@@ -2433,8 +2455,8 @@ class PropPivotOut(PivotOper):
2433
2455
  continue
2434
2456
 
2435
2457
  for item in valu:
2436
- async for pivo in runt.snap.nodesByPropValu(fname, '=', item):
2437
- yield pivo, path.fork(pivo)
2458
+ async for pivo in runt.snap.nodesByPropValu(fname, '=', item, norm=False):
2459
+ yield pivo, path.fork(pivo, link)
2438
2460
 
2439
2461
  continue
2440
2462
 
@@ -2445,7 +2467,7 @@ class PropPivotOut(PivotOper):
2445
2467
  if pivo is None:
2446
2468
  logger.warning(f'Missing node corresponding to ndef {valu}')
2447
2469
  continue
2448
- yield pivo, path.fork(pivo)
2470
+ yield pivo, path.fork(pivo, link)
2449
2471
  continue
2450
2472
 
2451
2473
  # :prop -> *
@@ -2462,7 +2484,7 @@ class PropPivotOut(PivotOper):
2462
2484
  # A node explicitly deleted in the graph or missing from a underlying layer
2463
2485
  # could cause this lift to return None.
2464
2486
  if pivo:
2465
- yield pivo, path.fork(pivo)
2487
+ yield pivo, path.fork(pivo, link)
2466
2488
 
2467
2489
 
2468
2490
  class PropPivot(PivotOper):
@@ -2474,8 +2496,9 @@ class PropPivot(PivotOper):
2474
2496
 
2475
2497
  async def pgenr(node, srcprop, valu, strict=True):
2476
2498
 
2477
- # TODO cache/bypass normalization in loop!
2478
-
2499
+ link = {'type': 'prop', 'prop': srcprop.name}
2500
+ if not prop.isform:
2501
+ link['dest'] = prop.full
2479
2502
  # pivoting from an array prop to a non-array prop needs an extra loop
2480
2503
  if srcprop.type.isarray and not prop.type.isarray:
2481
2504
  if isinstance(srcprop.type.arraytype, s_types.Ndef) and prop.isform:
@@ -2484,12 +2507,13 @@ class PropPivot(PivotOper):
2484
2507
  continue
2485
2508
 
2486
2509
  if (pivo := await runt.snap.getNodeByNdef(aval)) is not None:
2487
- yield pivo
2510
+ yield pivo, link
2488
2511
  return
2489
2512
 
2513
+ norm = srcprop.arraytypehash is not prop.typehash
2490
2514
  for arrayval in valu:
2491
- async for pivo in runt.snap.nodesByPropValu(prop.full, '=', arrayval):
2492
- yield pivo
2515
+ async for pivo in runt.snap.nodesByPropValu(prop.full, '=', arrayval, norm=norm):
2516
+ yield pivo, link
2493
2517
 
2494
2518
  return
2495
2519
 
@@ -2501,17 +2525,19 @@ class PropPivot(PivotOper):
2501
2525
  if pivo is None:
2502
2526
  await runt.snap.warn(f'Missing node corresponding to ndef {valu}', log=False, ndef=valu)
2503
2527
  return
2504
- yield pivo
2528
+ yield pivo, link
2505
2529
 
2506
2530
  return
2507
2531
 
2508
2532
  if prop.type.isarray and not srcprop.type.isarray:
2509
- genr = runt.snap.nodesByPropArray(prop.full, '=', valu)
2533
+ norm = prop.arraytypehash is not srcprop.typehash
2534
+ genr = runt.snap.nodesByPropArray(prop.full, '=', valu, norm=norm)
2510
2535
  else:
2511
- genr = runt.snap.nodesByPropValu(prop.full, '=', valu)
2536
+ norm = prop.typehash is not srcprop.typehash
2537
+ genr = runt.snap.nodesByPropValu(prop.full, '=', valu, norm=norm)
2512
2538
 
2513
2539
  async for pivo in genr:
2514
- yield pivo
2540
+ yield pivo, link
2515
2541
 
2516
2542
  return pgenr
2517
2543
 
@@ -2565,8 +2591,8 @@ class PropPivot(PivotOper):
2565
2591
  continue
2566
2592
 
2567
2593
  try:
2568
- async for pivo in pgenr(node, srcprop, valu):
2569
- yield pivo, path.fork(pivo)
2594
+ async for pivo, link in pgenr(node, srcprop, valu):
2595
+ yield pivo, path.fork(pivo, link)
2570
2596
 
2571
2597
  except (s_exc.BadTypeValu, s_exc.BadLiftValu) as e:
2572
2598
  if not warned:
@@ -4214,19 +4240,20 @@ class EditUnivDel(Edit):
4214
4240
 
4215
4241
  class N1Walk(Oper):
4216
4242
 
4217
- def __init__(self, astinfo, kids=(), isjoin=False):
4243
+ def __init__(self, astinfo, kids=(), isjoin=False, reverse=False):
4218
4244
  Oper.__init__(self, astinfo, kids=kids)
4219
4245
  self.isjoin = isjoin
4246
+ self.reverse = reverse
4220
4247
 
4221
4248
  def repr(self):
4222
4249
  return f'{self.__class__.__name__}: {self.kids}, isjoin={self.isjoin}'
4223
4250
 
4224
4251
  async def walkNodeEdges(self, runt, node, verb=None):
4225
- async for _, iden in node.iterEdgesN1(verb=verb):
4252
+ async for verb, iden in node.iterEdgesN1(verb=verb):
4226
4253
  buid = s_common.uhex(iden)
4227
4254
  walknode = await runt.snap.getNodeByBuid(buid)
4228
4255
  if walknode is not None:
4229
- yield walknode
4256
+ yield verb, walknode
4230
4257
 
4231
4258
  def buildfilter(self, runt, destforms, cmpr):
4232
4259
 
@@ -4326,21 +4353,28 @@ class N1Walk(Oper):
4326
4353
  if verb == '*':
4327
4354
  verb = None
4328
4355
 
4329
- async for walknode in self.walkNodeEdges(runt, node, verb=verb):
4356
+ async for verbname, walknode in self.walkNodeEdges(runt, node, verb=verb):
4330
4357
 
4331
4358
  if destfilt and not await destfilt(walknode, path, cmprvalu):
4332
4359
  continue
4333
4360
 
4334
- yield walknode, path.fork(walknode)
4361
+ link = {'type': 'edge', 'verb': verbname}
4362
+ if self.reverse:
4363
+ link['reverse'] = True
4364
+
4365
+ yield walknode, path.fork(walknode, link)
4335
4366
 
4336
4367
  class N2Walk(N1Walk):
4337
4368
 
4369
+ def __init__(self, astinfo, kids=(), isjoin=False):
4370
+ N1Walk.__init__(self, astinfo, kids=kids, isjoin=isjoin, reverse=True)
4371
+
4338
4372
  async def walkNodeEdges(self, runt, node, verb=None):
4339
- async for _, iden in node.iterEdgesN2(verb=verb):
4373
+ async for verb, iden in node.iterEdgesN2(verb=verb):
4340
4374
  buid = s_common.uhex(iden)
4341
4375
  walknode = await runt.snap.getNodeByBuid(buid)
4342
4376
  if walknode is not None:
4343
- yield walknode
4377
+ yield verb, walknode
4344
4378
 
4345
4379
  class EditEdgeAdd(Edit):
4346
4380
 
synapse/lib/base.py CHANGED
@@ -178,6 +178,16 @@ class Base:
178
178
  def onfini(self, func):
179
179
  '''
180
180
  Add a function/coroutine/Base to be called on fini().
181
+
182
+ The rules around how to register function/coroutine/Base to be called:
183
+ - Call this method with an instance of Base (this class) if holding
184
+ a reference to a bound method of the instance (such as a fini()
185
+ method) would cause the object to be leaked. This is appropriate
186
+ for ephemeral objects that may be constructed/destroyed multiple
187
+ times over the lifetime of a process.
188
+
189
+ - Call this method with an instance method if you want the object to
190
+ have a lifetime as long as the thing being fini'd.
181
191
  '''
182
192
  if self.isfini:
183
193
  if isinstance(func, Base):
@@ -498,9 +508,8 @@ class Base:
498
508
  def taskDone(task):
499
509
  self._active_tasks.remove(task)
500
510
  try:
501
- if not task.done():
502
- task.result()
503
- except asyncio.CancelledError: # pragma: no cover TODO: remove once >= py 3.8 only
511
+ task.result()
512
+ except asyncio.CancelledError:
504
513
  pass
505
514
  except Exception:
506
515
  logger.exception('Task %s scheduled through Base.schedCoro raised exception', task)