synapse 2.182.0__py311-none-any.whl → 2.184.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 (37) hide show
  1. synapse/cortex.py +5 -0
  2. synapse/datamodel.py +41 -3
  3. synapse/lib/ast.py +4 -3
  4. synapse/lib/autodoc.py +71 -5
  5. synapse/lib/cell.py +1 -1
  6. synapse/lib/storm.py +23 -0
  7. synapse/lib/stormlib/cortex.py +1 -0
  8. synapse/lib/stormlib/graph.py +17 -0
  9. synapse/lib/stormlib/infosec.py +2 -0
  10. synapse/lib/stormlib/model.py +2 -1
  11. synapse/lib/stormlib/random.py +84 -3
  12. synapse/lib/stormtypes.py +4 -0
  13. synapse/lib/stormwhois.py +3 -0
  14. synapse/lib/version.py +2 -2
  15. synapse/models/infotech.py +3 -15
  16. synapse/models/orgs.py +76 -2
  17. synapse/models/risk.py +22 -0
  18. synapse/tests/files/stormpkg/testpkg.yaml +10 -0
  19. synapse/tests/test_cortex.py +31 -44
  20. synapse/tests/test_lib_ast.py +58 -0
  21. synapse/tests/test_lib_autodoc.py +85 -0
  22. synapse/tests/test_lib_storm.py +18 -0
  23. synapse/tests/test_lib_stormlib_modelext.py +52 -0
  24. synapse/tests/test_lib_stormlib_random.py +93 -0
  25. synapse/tests/test_lib_stormwhois.py +4 -4
  26. synapse/tests/test_model_infotech.py +44 -1
  27. synapse/tests/test_model_orgs.py +37 -0
  28. synapse/tests/test_model_risk.py +3 -0
  29. synapse/tests/test_tools_autodoc.py +6 -0
  30. synapse/tests/utils.py +28 -0
  31. synapse/tools/autodoc.py +2 -1
  32. synapse/tools/changelog.py +53 -10
  33. {synapse-2.182.0.dist-info → synapse-2.184.0.dist-info}/METADATA +1 -1
  34. {synapse-2.182.0.dist-info → synapse-2.184.0.dist-info}/RECORD +37 -37
  35. {synapse-2.182.0.dist-info → synapse-2.184.0.dist-info}/WHEEL +1 -1
  36. {synapse-2.182.0.dist-info → synapse-2.184.0.dist-info}/LICENSE +0 -0
  37. {synapse-2.182.0.dist-info → synapse-2.184.0.dist-info}/top_level.txt +0 -0
@@ -20,3 +20,96 @@ class TestLibStormRandom(s_test.SynTest):
20
20
 
21
21
  with self.raises(s_exc.BadArg):
22
22
  await core.callStorm('return($lib.random.int(maxval=0, minval=1))')
23
+
24
+ async def test_stormlib_random_generator(self):
25
+ async with self.getTestCore() as core:
26
+
27
+ # Seedless generators
28
+ q = '''$r=$lib.random.generator()
29
+ return(($r.int(10), $r.int(10), $r.int(10)))
30
+ '''
31
+ valu = await core.callStorm(q)
32
+ for v in valu:
33
+ self.true(v >= 0 and v <= 10)
34
+
35
+ # There is no seed on the generator
36
+ q = '''$r=$lib.random.generator() return ( $r.seed )'''
37
+ valu = await core.callStorm(q)
38
+ self.none(valu)
39
+
40
+ # Generators can be made and seeds set
41
+ q = '''$r=$lib.random.generator() $r.seed=myCoolTestSeed
42
+ return ( (($r.int(10), $r.int(10), $r.int(10)), $r.seed) )'''
43
+ valu = await core.callStorm(q)
44
+ self.eq(valu, ((5, 9, 1), 'myCoolTestSeed'))
45
+
46
+ # Setting a seed resets the generator
47
+ q = '''$r=$lib.random.generator(seed=myCoolTestSeed) $ret=()
48
+ $ret.append($r.int(10)) $ret.append($r.int(10)) $ret.append($r.int(10))
49
+ $r.seed=myCoolTestSeed
50
+ $ret.append($r.int(10)) $ret.append($r.int(10)) $ret.append($r.int(10))
51
+ return ($ret)'''
52
+ valu = await core.callStorm(q)
53
+ self.eq(valu, (5, 9, 1, 5, 9, 1))
54
+
55
+ # Clearing the seed makes the generator random.
56
+ q = '''$r=$lib.random.generator(seed=myCoolTestSeed) $ret=()
57
+ $ret.append($r.int(10)) $ret.append($r.int(10)) $ret.append($r.int(10))
58
+ $r.seed=(null)
59
+ $ret.append($r.int(10)) $ret.append($r.int(10)) $ret.append($r.int(10))
60
+ return ($ret)'''
61
+ valu = await core.callStorm(q)
62
+ self.len(6, valu)
63
+ self.eq(valu[:3], (5, 9, 1))
64
+ self.ne(valu[3:], (5, 9, 1))
65
+ for v in valu[3:]:
66
+ self.true(v >= 0 and v <= 10)
67
+
68
+ # Seeded generators are consistent
69
+ q = '''$r=$lib.random.generator(seed=myCoolTestSeed)
70
+ return(($r.int(10), $r.int(10), $r.int(10)))
71
+ '''
72
+ valu = await core.callStorm(q)
73
+ self.eq(valu, (5, 9, 1))
74
+
75
+ new_valu = await core.callStorm(q)
76
+ self.eq(valu, new_valu)
77
+
78
+ q = '''$r=$lib.random.generator(seed=myCoolTestSeed)
79
+ $r2 = $lib.random.generator(seed=$r.seed)
80
+ return(($r.int(10), $r2.int(10)))
81
+ '''
82
+ valu = await core.callStorm(q)
83
+ self.eq(valu, (5, 5))
84
+
85
+ q = '''return($lib.vars.type($lib.random.generator(x)))'''
86
+ valu = await core.callStorm(q)
87
+ self.eq(valu, 'random')
88
+
89
+ # Seeds are stringified
90
+ q = '''$r=$lib.random.generator(seed=1234567890)
91
+ $r2 = $lib.random.generator(seed=(1234567890))
92
+ return(($r.int(10), $r2.int(10), $r.seed, $r2.seed))
93
+ '''
94
+ valu = await core.callStorm(q)
95
+ self.eq(valu, (5, 5, '1234567890', '1234567890'))
96
+
97
+ # Empty string value is still a str
98
+ q = '''$r=$lib.random.generator(seed='') return ($r.int(10))'''
99
+ valu = await core.callStorm(q)
100
+ self.eq(valu, 7)
101
+
102
+ # Sad path
103
+ with self.raises(s_exc.BadArg):
104
+ await core.callStorm('$r=$lib.random.generator(seed="") return($r.int(maxval=0, minval=1))')
105
+
106
+ # Printing objects
107
+ msgs = await core.stormlist('$lib.print($lib.random.generator())')
108
+ self.stormIsInPrint('random', msgs)
109
+ self.stormNotInPrint('seed=', msgs)
110
+
111
+ msgs = await core.stormlist('$lib.print($lib.random.generator(seed=""))')
112
+ self.stormIsInPrint('random seed=', msgs)
113
+
114
+ msgs = await core.stormlist('$lib.print($lib.random.generator(seed=haha))')
115
+ self.stormIsInPrint('random seed=haha', msgs)
@@ -84,8 +84,8 @@ class StormWhoisTest(s_test.SynTest):
84
84
  '''
85
85
  opts = {'vars': {'props': props}}
86
86
  mesgs = await core.stormlist(stormcmd, opts=opts)
87
- warn = [m[1]['mesg'] for m in mesgs if m[0] == 'warn']
88
- self.isin('Insufficient guid vals identified, using random guid:', warn[0])
87
+ self.stormIsInWarn('$lib.inet.whois.guid() is deprecated', mesgs)
88
+ self.stormIsInWarn('Insufficient guid vals identified, using random guid:', mesgs)
89
89
  self.len(1, await core.nodes(f'inet:whois:ipquery:fqdn={props["fqdn"]}'))
90
90
 
91
91
  props = {
@@ -97,8 +97,8 @@ class StormWhoisTest(s_test.SynTest):
97
97
  '''
98
98
  opts = {'vars': {'props': props}}
99
99
  mesgs = await core.stormlist(stormcmd, opts=opts)
100
- warn = [m[1]['mesg'] for m in mesgs if m[0] == 'warn']
101
- self.isin('Insufficient guid vals identified, using random guid:', warn[0])
100
+ self.stormIsInWarn('$lib.inet.whois.guid() is deprecated', mesgs)
101
+ self.stormIsInWarn('Insufficient guid vals identified, using random guid:', mesgs)
102
102
  self.len(1, await core.nodes(f'inet:whois:ipcontact:asn={props["asn"]}'))
103
103
 
104
104
  # Failure cases
@@ -1547,6 +1547,45 @@ class InfotechModelTest(s_t_utils.SynTest):
1547
1547
  self.nn(node.get('reg'))
1548
1548
  self.eq(node.get('sandbox:file'), sandfile)
1549
1549
 
1550
+ async with self.getTestCore() as core:
1551
+ forms = [
1552
+ 'it:fs:file',
1553
+ 'it:exec:file:add',
1554
+ 'it:exec:file:del',
1555
+ 'it:exec:file:read',
1556
+ 'it:exec:file:write',
1557
+ ]
1558
+
1559
+ for form in forms:
1560
+ opts = {'vars': {'form': form}}
1561
+ nodes = await core.nodes('[ *$form=($form, calc) :path="c:/windows/system32/calc.exe" ]', opts=opts)
1562
+ self.len(1, nodes)
1563
+ self.eq(nodes[0].get('path'), 'c:/windows/system32/calc.exe')
1564
+ self.eq(nodes[0].get('path:base'), 'calc.exe')
1565
+ self.eq(nodes[0].get('path:dir'), 'c:/windows/system32')
1566
+ self.eq(nodes[0].get('path:ext'), 'exe')
1567
+
1568
+ nodes = await core.nodes('*$form=($form, calc) [ :path="c:/users/blackout/script.ps1" ]', opts=opts)
1569
+ self.len(1, nodes)
1570
+ self.eq(nodes[0].get('path'), 'c:/users/blackout/script.ps1')
1571
+ self.eq(nodes[0].get('path:base'), 'script.ps1')
1572
+ self.eq(nodes[0].get('path:dir'), 'c:/users/blackout')
1573
+ self.eq(nodes[0].get('path:ext'), 'ps1')
1574
+
1575
+ nodes = await core.nodes('*$form=($form, calc) [ -:path:base -:path:dir -:path:ext ]', opts=opts)
1576
+ self.len(1, nodes)
1577
+ self.eq(nodes[0].get('path'), 'c:/users/blackout/script.ps1')
1578
+ self.none(nodes[0].get('path:base'))
1579
+ self.none(nodes[0].get('path:dir'))
1580
+ self.none(nodes[0].get('path:ext'))
1581
+
1582
+ nodes = await core.nodes('*$form=($form, calc) [ :path="c:/users/admin/superscript.bat" ]', opts=opts)
1583
+ self.len(1, nodes)
1584
+ self.eq(nodes[0].get('path'), 'c:/users/admin/superscript.bat')
1585
+ self.eq(nodes[0].get('path:base'), 'superscript.bat')
1586
+ self.eq(nodes[0].get('path:dir'), 'c:/users/admin')
1587
+ self.eq(nodes[0].get('path:ext'), 'bat')
1588
+
1550
1589
  async def test_it_app_yara(self):
1551
1590
 
1552
1591
  async with self.getTestCore() as core:
@@ -1636,8 +1675,12 @@ class InfotechModelTest(s_t_utils.SynTest):
1636
1675
  self.eq(1640995200000, nodes[0].get('updated'))
1637
1676
  self.nn(nodes[0].get('author'))
1638
1677
 
1639
- nodes = await core.nodes('[ it:app:snort:hit=$hit :rule=$rule :flow=$flow :src="tcp://[::ffff:0102:0304]:0" :dst="tcp://[::ffff:0505:0505]:80" :time=2015 :sensor=$host :version=1.2.3 ]', opts=opts)
1678
+ nodes = await core.nodes('''[ it:app:snort:hit=$hit
1679
+ :rule=$rule :flow=$flow :src="tcp://[::ffff:0102:0304]:0"
1680
+ :dst="tcp://[::ffff:0505:0505]:80" :time=2015 :sensor=$host
1681
+ :version=1.2.3 :dropped=true ]''', opts=opts)
1640
1682
  self.len(1, nodes)
1683
+ self.true(nodes[0].get('dropped'))
1641
1684
  self.eq(rule, nodes[0].get('rule'))
1642
1685
  self.eq(flow, nodes[0].get('flow'))
1643
1686
  self.eq(host, nodes[0].get('sensor'))
@@ -640,6 +640,7 @@ class OuModelTest(s_t_utils.SynTest):
640
640
 
641
641
  nodes = await core.nodes('''[ ou:requirement=50b757fafe4a839ec499023ebcffe7c0
642
642
  :name="acquire pizza toppings"
643
+ :type=foo.bar
643
644
  :text="The team must acquire ANSI standard pizza toppings."
644
645
  :goal={[ ou:goal=* :name=pizza ]}
645
646
  :issuer={[ ps:contact=* :name=visi ]}
@@ -657,6 +658,7 @@ class OuModelTest(s_t_utils.SynTest):
657
658
  self.eq('The team must acquire ANSI standard pizza toppings.', nodes[0].get('text'))
658
659
  self.eq(1, nodes[0].get('deps:min'))
659
660
  self.eq(50, nodes[0].get('priority'))
661
+ self.eq('foo.bar.', nodes[0].get('type'))
660
662
  self.eq(True, nodes[0].get('optional'))
661
663
  self.eq(1328140800000, nodes[0].get('issued'))
662
664
  self.eq((1672531200000, 9223372036854775807), nodes[0].get('period'))
@@ -665,6 +667,39 @@ class OuModelTest(s_t_utils.SynTest):
665
667
  self.len(1, await core.nodes('ou:requirement=50b757fafe4a839ec499023ebcffe7c0 -> ou:goal +:name=pizza'))
666
668
  self.len(1, await core.nodes('ou:requirement=50b757fafe4a839ec499023ebcffe7c0 :issuer -> ps:contact +:name=visi'))
667
669
  self.len(1, await core.nodes('ou:requirement=50b757fafe4a839ec499023ebcffe7c0 :assignee -> ps:contact +:orgname=ledos'))
670
+ self.len(1, await core.nodes('ou:requirement=50b757fafe4a839ec499023ebcffe7c0 -> ou:requirement:type:taxonomy'))
671
+
672
+ nodes = await core.nodes('''
673
+ [ ou:asset=*
674
+ :id=V-31337
675
+ :name="visi laptop"
676
+ :type=host.laptop
677
+ :priority=highest
678
+ :priority:confidentiality=highest
679
+ :priority:integrity=highest
680
+ :priority:availability=highest
681
+ :node = (it:host, *)
682
+ :period=(2016, ?)
683
+ :status=deployed
684
+ :org={[ ou:org=* :name=vertex ]}
685
+ :owner={[ ps:contact=* :name=foo ]}
686
+ :operator={[ ps:contact=* :name=bar ]}
687
+ ]''')
688
+ self.len(1, nodes)
689
+ self.eq((1451606400000, 9223372036854775807), nodes[0].get('period'))
690
+ self.eq('visi laptop', nodes[0].get('name'))
691
+ self.eq('host.laptop.', nodes[0].get('type'))
692
+ self.eq('deployed.', nodes[0].get('status'))
693
+ self.eq(50, nodes[0].get('priority'))
694
+ self.eq(50, nodes[0].get('priority:confidentiality'))
695
+ self.eq(50, nodes[0].get('priority:integrity'))
696
+ self.eq(50, nodes[0].get('priority:availability'))
697
+
698
+ self.len(1, await core.nodes('ou:asset -> ou:asset:type:taxonomy'))
699
+ self.len(1, await core.nodes('ou:asset :node -> it:host'))
700
+ self.len(1, await core.nodes('ou:asset :org -> ou:org +:name=vertex'))
701
+ self.len(1, await core.nodes('ou:asset :owner -> ps:contact +:name=foo '))
702
+ self.len(1, await core.nodes('ou:asset :operator -> ps:contact +:name=bar '))
668
703
 
669
704
  async def test_ou_code_prefixes(self):
670
705
  guid0 = s_common.guid()
@@ -832,6 +867,7 @@ class OuModelTest(s_t_utils.SynTest):
832
867
  :orgfqdn = wootwoot.com
833
868
  :currency = USD
834
869
  :costs = 200
870
+ :budget = 300
835
871
  :revenue = 500
836
872
  :profit = 300
837
873
  :valuation = 1000000000
@@ -850,6 +886,7 @@ class OuModelTest(s_t_utils.SynTest):
850
886
  self.eq(nodes[0].get('orgfqdn'), 'wootwoot.com')
851
887
  self.eq(nodes[0].get('currency'), 'usd')
852
888
  self.eq(nodes[0].get('costs'), '200')
889
+ self.eq(nodes[0].get('budget'), '300')
853
890
  self.eq(nodes[0].get('revenue'), '500')
854
891
  self.eq(nodes[0].get('profit'), '300')
855
892
  self.eq(nodes[0].get('valuation'), '1000000000')
@@ -542,6 +542,7 @@ class RiskModelTest(s_t_utils.SynTest):
542
542
  risk:mitigation=*
543
543
  :vuln=*
544
544
  :name=" FooBar "
545
+ :type=foo.bar
545
546
  :desc=BazFaz
546
547
  :hardware=*
547
548
  :software=*
@@ -552,11 +553,13 @@ class RiskModelTest(s_t_utils.SynTest):
552
553
  self.eq('foobar', nodes[0].props['name'])
553
554
  self.eq('BazFaz', nodes[0].props['desc'])
554
555
  self.eq('vertex', nodes[0].get('reporter:name'))
556
+ self.eq('foo.bar.', nodes[0].get('type'))
555
557
  self.nn(nodes[0].get('reporter'))
556
558
  self.len(1, await core.nodes('risk:mitigation -> risk:vuln'))
557
559
  self.len(1, await core.nodes('risk:mitigation -> it:prod:softver'))
558
560
  self.len(1, await core.nodes('risk:mitigation -> it:prod:hardware'))
559
561
  self.len(1, await core.nodes('risk:mitigation -> it:mitre:attack:mitigation'))
562
+ self.len(1, await core.nodes('risk:mitigation -> risk:mitigation:type:taxonomy'))
560
563
 
561
564
  async def test_model_risk_tool_software(self):
562
565
 
@@ -191,6 +191,12 @@ class TestAutoDoc(s_t_utils.SynTest):
191
191
 
192
192
  self.isin('status()', s)
193
193
 
194
+ self.isin('newp()', s)
195
+ self.isin('.. warning::\n', s)
196
+ self.isin('``newp`` has been deprecated and will be removed in version v2.300.4.', s)
197
+ self.isin('Newp is no longer maintained. Use bar() instead.', s)
198
+ self.isin('Some nonexistent function', s)
199
+
194
200
  # coverage for no apidefs
195
201
  rst = s_l_autodoc.RstHelp()
196
202
  await s_autodoc.processStormModules(rst, 'foo', [])
synapse/tests/utils.py CHANGED
@@ -157,6 +157,7 @@ class LibTst(s_stormtypes.Lib):
157
157
  '''
158
158
  _storm_locals = (
159
159
  {'name': 'beep',
160
+ 'deprecated': {'eoldate': '8080-08-08'},
160
161
  'desc': '''
161
162
  Example storm func.
162
163
 
@@ -169,6 +170,7 @@ class LibTst(s_stormtypes.Lib):
169
170
  'returns': {'type': 'str', 'desc': 'The beeped string.', }}},
170
171
  {'name': 'someargs',
171
172
  'desc': '''Example storm func with args.''',
173
+ 'deprecated': {'eolvers': 'v3.0.0', 'mesg': 'This is a test library was deprecated from the day it was made.'},
172
174
  'type': {'type': 'function', '_funcname': 'someargs',
173
175
  'args': (
174
176
  {'name': 'valu', 'type': 'str', 'desc': 'The value to beep.', },
@@ -199,6 +201,32 @@ class LibTst(s_stormtypes.Lib):
199
201
  ret = f'A {valu} beep which {bar} the {faz}!'
200
202
  return ret
201
203
 
204
+ class LibDepr(s_stormtypes.Lib):
205
+ '''
206
+ Deprecate me!
207
+ '''
208
+ _storm_locals = (
209
+ {'name': 'boop',
210
+ 'desc': '''
211
+ An example storm function that's not deprecated on its own, but the entire library is.
212
+ ''',
213
+ 'type': {'type': 'function', '_funcname': 'boop',
214
+ 'args': (
215
+ {'name': 'valu', 'type': 'str', 'desc': 'What to boop.', },
216
+ ),
217
+ 'returns': {'type': 'str', 'desc': 'The booped.', }}},
218
+ )
219
+ _storm_lib_path = ('depr',)
220
+ _storm_lib_deprecation = {'eolvers': 'v3.0.0'}
221
+
222
+ def addLibFuncs(self): # pragma: no cover
223
+ self.locls.update({
224
+ 'boop': self.boop,
225
+ })
226
+
227
+ async def boop(self, valu): # pragma: no cover
228
+ return f'You have been booped, {valu}!'
229
+
202
230
  class TestType(s_types.Type):
203
231
 
204
232
  stortype = s_layer.STOR_TYPE_UTF8
synapse/tools/autodoc.py CHANGED
@@ -617,7 +617,8 @@ async def processStormModules(rst, pkgname, modules):
617
617
 
618
618
  callsig = s_autodoc.genCallsig(apitype)
619
619
  rst.addHead(f'{apiname}{callsig}', lvl=4)
620
-
620
+ if depr := apidef.get('deprecated'):
621
+ rst.addLines(*s_autodoc.genDeprecationWarning(apiname, depr, True))
621
622
  rst.addLines(*s_autodoc.prepareRstLines(apidesc))
622
623
  rst.addLines(*s_autodoc.getArgLines(apitype))
623
624
  rst.addLines(*s_autodoc.getReturnLines(apitype))
@@ -222,13 +222,25 @@ class ModelDiffer:
222
222
  deprecated_props_noiface[prop] = cpinfo
223
223
  continue
224
224
 
225
+ okeys = set(opinfo.keys())
226
+ nkeys = set(cpinfo.keys())
227
+
228
+ if nkeys - okeys:
229
+ # We've added a key to the prop def.
230
+ raise s_exc.NoSuchImpl(mesg='Have not implemented support for a prop def having a key added')
231
+
232
+ if okeys - nkeys:
233
+ # We've removed a key from the prop def.
234
+ updated_props[prop] = {'type': 'delkey', 'keys': list(okeys - nkeys)}
235
+ continue
236
+
225
237
  # Check if type change happened, we'll want to document that.
226
238
  ctyp = cpinfo.get('type')
227
239
  otyp = opinfo.get('type')
228
240
  if ctyp == otyp:
229
241
  continue
230
242
 
231
- updated_props[prop] = {'new_type': ctyp, 'old_type': otyp}
243
+ updated_props[prop] = {'type': 'type_change', 'new_type': ctyp, 'old_type': otyp}
232
244
  is_ifaceprop = False
233
245
  for iface in self.cur_type2iface[form]:
234
246
  upt_iface = self.changes.get('interfaces').get('updated_interfaces', {}).get(iface)
@@ -237,7 +249,7 @@ class ModelDiffer:
237
249
  break
238
250
  if is_ifaceprop:
239
251
  continue
240
- updated_props_noiface[prop] = {'new_type': ctyp, 'old_type': otyp}
252
+ updated_props_noiface[prop] = {'type': 'type_change', 'new_type': ctyp, 'old_type': otyp}
241
253
 
242
254
  if updated_props:
243
255
  updated_forms[form]['updated_properties'] = updated_props
@@ -317,13 +329,25 @@ class ModelDiffer:
317
329
  deprecated_props[prop] = cpinfo
318
330
  continue
319
331
 
332
+ okeys = set(opinfo.keys())
333
+ nkeys = set(cpinfo.keys())
334
+
335
+ if nkeys - okeys:
336
+ # We've added a key to the prop def.
337
+ raise s_exc.NoSuchImpl(mesg='Have not implemented support for a prop def having a key added')
338
+
339
+ if okeys - nkeys:
340
+ # We've removed a key from the prop def.
341
+ updated_props[prop] = {'type': 'delkey', 'keys': list(okeys - nkeys)}
342
+ continue
343
+
320
344
  # Check if type change happened, we'll want to document that.
321
345
  ctyp = cpinfo.get('type')
322
346
  otyp = opinfo.get('type')
323
347
  if ctyp == otyp:
324
348
  continue
325
349
 
326
- updated_props[prop] = {'new_type': ctyp, 'old_type': otyp}
350
+ updated_props[prop] = {'type': 'type_change', 'new_type': ctyp, 'old_type': otyp}
327
351
  if updated_props:
328
352
  updated_interfaces[iface]['updated_properties'] = updated_props
329
353
 
@@ -569,8 +593,14 @@ def _gen_model_rst(version, model_ref, changes, current_model, outp: s_output.Ou
569
593
  lines.append('\n')
570
594
  elif key == 'updated_properties':
571
595
  for prop, pnfo in sorted(valu.items(), key=lambda x: x[0]):
572
- mesg = f'The property ``{prop}`` has been modified from {pnfo.get("old_type")}' \
573
- f' to {pnfo.get("new_type")}.'
596
+ ptyp = pnfo.get('type')
597
+ if ptyp == 'type_change':
598
+ mesg = f'The property ``{prop}`` has been modified from {pnfo.get("old_type")}' \
599
+ f' to {pnfo.get("new_type")}.'
600
+ elif ptyp == 'delkey':
601
+ mesg = f'The property ``{prop}`` had the ``{pnfo.get("keys")}`` keys removed from its definition.'
602
+ else:
603
+ raise s_exc.NoSuchImpl(mesg=f'pnfo.type={ptyp} not supported.')
574
604
  lines.extend(textwrap.wrap(mesg, initial_indent=' ', subsequent_indent=' ',
575
605
  width=width))
576
606
  lines.append('\n')
@@ -621,11 +651,17 @@ def _gen_model_rst(version, model_ref, changes, current_model, outp: s_output.Ou
621
651
  rst.addLines(f'``{form}``')
622
652
  upd_form_props = list(info.get('updated_properties').items())
623
653
  if len(upd_form_props) > 1:
624
- rst.addLines(' The form had the following updated properties:', '\n')
654
+ rst.addLines(' The form had the following properties updated:', '\n')
625
655
  upd_form_props.sort(key=lambda x: x[0])
626
656
  for prop, pnfo in upd_form_props:
627
- mesg = f'The property ``{prop}`` has been modified from {pnfo.get("old_type")}' \
628
- f' to {pnfo.get("new_type")}.'
657
+ ptyp = pnfo.get('type')
658
+ if ptyp == 'type_change':
659
+ mesg = f'The property ``{prop}`` has been modified from {pnfo.get("old_type")}' \
660
+ f' to {pnfo.get("new_type")}.'
661
+ elif ptyp == 'delkey':
662
+ mesg = f'The property ``{prop}`` had the ``{pnfo.get("keys")}`` keys removed from its definition.'
663
+ else:
664
+ raise s_exc.NoSuchImpl(mesg=f'pnfo.type={ptyp} not supported.')
629
665
  lines = [
630
666
  *textwrap.wrap(mesg, initial_indent=' ', subsequent_indent=' ',
631
667
  width=width),
@@ -635,8 +671,15 @@ def _gen_model_rst(version, model_ref, changes, current_model, outp: s_output.Ou
635
671
 
636
672
  else:
637
673
  prop, pnfo = upd_form_props[0]
638
- mesg = f'The property ``{prop}`` has been modified from {pnfo.get("old_type")}' \
639
- f' to {pnfo.get("new_type")}.'
674
+ ptyp = pnfo.get('type')
675
+ if ptyp == 'type_change':
676
+ mesg = f'The property ``{prop}`` has been modified from {pnfo.get("old_type")}' \
677
+ f' to {pnfo.get("new_type")}.'
678
+ elif ptyp == 'delkey':
679
+ mesg = f'The property ``{prop}`` had the ``{pnfo.get("keys")}`` keys removed from its definition.'
680
+ else:
681
+ raise s_exc.NoSuchImpl(mesg=f'pnfo.type={ptyp} not supported.')
682
+
640
683
  lines = [
641
684
  ' The form had the following property updated:',
642
685
  '\n',
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: synapse
3
- Version: 2.182.0
3
+ Version: 2.184.0
4
4
  Summary: Synapse Intelligence Analysis Framework
5
5
  Author-email: The Vertex Project LLC <root@vertex.link>
6
6
  License: Apache License 2.0