synapse 2.223.0__py311-none-any.whl → 2.225.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 (135) hide show
  1. synapse/axon.py +10 -5
  2. synapse/common.py +2 -2
  3. synapse/cortex.py +52 -3
  4. synapse/datamodel.py +1 -1
  5. synapse/lib/cell.py +1 -1
  6. synapse/lib/const.py +4 -0
  7. synapse/lib/layer.py +6 -6
  8. synapse/lib/multislabseqn.py +36 -1
  9. synapse/lib/nexus.py +67 -8
  10. synapse/lib/queue.py +4 -1
  11. synapse/lib/rstorm.py +2 -2
  12. synapse/lib/schemas.py +13 -1
  13. synapse/lib/slabseqn.py +28 -0
  14. synapse/lib/storm.py +40 -2
  15. synapse/lib/stormhttp.py +7 -1
  16. synapse/lib/stormlib/imap.py +12 -4
  17. synapse/lib/stormlib/task.py +0 -1
  18. synapse/lib/stormtypes.py +19 -1
  19. synapse/lib/version.py +2 -2
  20. synapse/models/inet.py +29 -0
  21. synapse/models/media.py +4 -0
  22. synapse/models/proj.py +3 -0
  23. synapse/models/risk.py +9 -0
  24. synapse/models/syn.py +8 -0
  25. synapse/tests/test_common.py +4 -0
  26. synapse/tests/test_cortex.py +53 -2
  27. synapse/tests/test_lib_agenda.py +1 -1
  28. synapse/tests/test_lib_cell.py +1 -1
  29. synapse/tests/test_lib_certdir.py +1 -1
  30. synapse/tests/test_lib_httpapi.py +1 -1
  31. synapse/tests/test_lib_layer.py +1 -1
  32. synapse/tests/test_lib_multislabseqn.py +22 -0
  33. synapse/tests/test_lib_nexus.py +42 -1
  34. synapse/tests/test_lib_slabseqn.py +30 -1
  35. synapse/tests/test_lib_storm.py +156 -1
  36. synapse/tests/test_lib_stormhttp.py +16 -0
  37. synapse/tests/test_lib_stormlib_imap.py +14 -0
  38. synapse/tests/test_lib_stormlib_oauth.py +1 -1
  39. synapse/tests/test_lib_stormsvc.py +1 -1
  40. synapse/tests/test_lib_stormtypes.py +12 -0
  41. synapse/tests/test_lib_trigger.py +1 -1
  42. synapse/tests/test_model_inet.py +29 -0
  43. synapse/tests/test_model_media.py +4 -1
  44. synapse/tests/test_model_proj.py +3 -1
  45. synapse/tests/test_model_risk.py +12 -0
  46. synapse/tests/test_model_syn.py +54 -2
  47. synapse/tests/{test_tools_axon2axon.py → test_tools_axon_copy.py} +4 -4
  48. synapse/tests/{test_tools_pullfile.py → test_tools_axon_get.py} +4 -4
  49. synapse/tests/{test_tools_pushfile.py → test_tools_axon_put.py} +7 -7
  50. synapse/tests/{test_tools_csvtool.py → test_tools_cortex_csv.py} +12 -3
  51. synapse/tests/{test_tools_feed.py → test_tools_cortex_feed.py} +2 -2
  52. synapse/tests/{test_tools_apikey.py → test_tools_service_apikey.py} +1 -4
  53. synapse/tests/{test_tools_backup.py → test_tools_service_backup.py} +5 -5
  54. synapse/tests/{test_tools_demote.py → test_tools_service_demote.py} +1 -1
  55. synapse/tests/{test_tools_healthcheck.py → test_tools_service_healthcheck.py} +1 -1
  56. synapse/tests/{test_tools_livebackup.py → test_tools_service_livebackup.py} +1 -1
  57. synapse/tests/{test_tools_modrole.py → test_tools_service_modrole.py} +1 -1
  58. synapse/tests/{test_tools_moduser.py → test_tools_service_moduser.py} +1 -1
  59. synapse/tests/{test_tools_promote.py → test_tools_service_promote.py} +1 -1
  60. synapse/tests/{test_tools_reload.py → test_tools_service_reload.py} +1 -1
  61. synapse/tests/{test_tools_shutdown.py → test_tools_service_shutdown.py} +1 -1
  62. synapse/tests/{test_tools_snapshot.py → test_tools_service_snapshot.py} +1 -1
  63. synapse/tests/{test_tools_storm.py → test_tools_storm_cli.py} +1 -1
  64. synapse/tests/{test_tools_pkgs_gendocs.py → test_tools_storm_pkg_doc.py} +12 -3
  65. synapse/tests/{test_tools_genpkg.py → test_tools_storm_pkg_gen.py} +1 -1
  66. synapse/tests/{test_tools_autodoc.py → test_tools_utils_autodoc.py} +1 -1
  67. synapse/tests/test_tools_utils_changelog.py +454 -0
  68. synapse/tests/{test_tools_easycert.py → test_tools_utils_easycert.py} +48 -46
  69. synapse/tests/{test_tools_guid.py → test_tools_utils_guid.py} +3 -3
  70. synapse/tests/{test_tools_json2mpk.py → test_tools_utils_json2mpk.py} +3 -3
  71. synapse/tests/{test_tools_rstorm.py → test_tools_utils_rstorm.py} +6 -1
  72. synapse/tests/utils.py +3 -1
  73. synapse/tools/apikey.py +4 -83
  74. synapse/tools/autodoc.py +3 -1031
  75. synapse/tools/axon/copy.py +44 -0
  76. synapse/tools/axon/get.py +64 -0
  77. synapse/tools/axon/put.py +122 -0
  78. synapse/tools/axon2axon.py +3 -36
  79. synapse/tools/backup.py +6 -176
  80. synapse/tools/changelog.py +3 -1098
  81. synapse/tools/cortex/csv.py +236 -0
  82. synapse/tools/cortex/feed.py +151 -0
  83. synapse/tools/csvtool.py +3 -227
  84. synapse/tools/demote.py +4 -40
  85. synapse/tools/docker/validate.py +3 -3
  86. synapse/tools/easycert.py +4 -129
  87. synapse/tools/feed.py +3 -140
  88. synapse/tools/genpkg.py +3 -307
  89. synapse/tools/guid.py +7 -6
  90. synapse/tools/healthcheck.py +3 -101
  91. synapse/tools/json2mpk.py +6 -38
  92. synapse/tools/livebackup.py +4 -27
  93. synapse/tools/modrole.py +3 -108
  94. synapse/tools/moduser.py +3 -179
  95. synapse/tools/pkgs/gendocs.py +3 -164
  96. synapse/tools/promote.py +4 -41
  97. synapse/tools/pullfile.py +3 -56
  98. synapse/tools/pushfile.py +3 -114
  99. synapse/tools/reload.py +4 -61
  100. synapse/tools/rstorm.py +3 -26
  101. synapse/tools/service/__init__.py +0 -0
  102. synapse/tools/service/apikey.py +90 -0
  103. synapse/tools/service/backup.py +181 -0
  104. synapse/tools/service/demote.py +47 -0
  105. synapse/tools/service/healthcheck.py +109 -0
  106. synapse/tools/service/livebackup.py +34 -0
  107. synapse/tools/service/modrole.py +116 -0
  108. synapse/tools/service/moduser.py +184 -0
  109. synapse/tools/service/promote.py +48 -0
  110. synapse/tools/service/reload.py +68 -0
  111. synapse/tools/service/shutdown.py +51 -0
  112. synapse/tools/service/snapshot.py +64 -0
  113. synapse/tools/shutdown.py +5 -45
  114. synapse/tools/snapshot.py +4 -57
  115. synapse/tools/storm/__init__.py +0 -0
  116. synapse/tools/storm/__main__.py +5 -0
  117. synapse/tools/{storm.py → storm/_cli.py} +0 -3
  118. synapse/tools/storm/pkg/__init__.py +0 -0
  119. synapse/tools/{pkgs/pandoc_filter.py → storm/pkg/_pandoc_filter.py} +1 -1
  120. synapse/tools/storm/pkg/doc.py +176 -0
  121. synapse/tools/storm/pkg/gen.py +315 -0
  122. synapse/tools/utils/__init__.py +0 -0
  123. synapse/tools/utils/autodoc.py +1040 -0
  124. synapse/tools/utils/changelog.py +1124 -0
  125. synapse/tools/utils/easycert.py +136 -0
  126. synapse/tools/utils/guid.py +11 -0
  127. synapse/tools/utils/json2mpk.py +46 -0
  128. synapse/tools/utils/rstorm.py +35 -0
  129. {synapse-2.223.0.dist-info → synapse-2.225.0.dist-info}/METADATA +1 -1
  130. {synapse-2.223.0.dist-info → synapse-2.225.0.dist-info}/RECORD +134 -105
  131. synapse/tests/test_tools_changelog.py +0 -196
  132. /synapse/tests/{test_tools_axon.py → test_tools_axon_dump_load.py} +0 -0
  133. {synapse-2.223.0.dist-info → synapse-2.225.0.dist-info}/WHEEL +0 -0
  134. {synapse-2.223.0.dist-info → synapse-2.225.0.dist-info}/licenses/LICENSE +0 -0
  135. {synapse-2.223.0.dist-info → synapse-2.225.0.dist-info}/top_level.txt +0 -0
@@ -1,1106 +1,11 @@
1
- import os
2
- import re
3
- import copy
4
- import gzip
5
- import pprint
6
- import datetime
7
- import tempfile
8
- import textwrap
9
- import traceback
10
- import subprocess
11
- import collections
12
-
13
- import regex
14
-
15
- import synapse.exc as s_exc
16
1
  import synapse.common as s_common
17
- import synapse.cortex as s_cortex
18
2
 
19
3
  import synapse.lib.cmd as s_cmd
20
- import synapse.lib.output as s_output
21
- import synapse.lib.autodoc as s_autodoc
22
- import synapse.lib.schemas as s_schemas
23
- import synapse.lib.version as s_version
24
-
25
- defstruct = (
26
- ('type', None),
27
- ('desc', ''),
28
- ('prs', ()),
29
- )
30
-
31
- SKIP_FILES = (
32
- '.gitkeep',
33
- 'modelrefs',
34
- )
35
-
36
- version_regex = r'^v[0-9]\.[0-9]+\.[0-9]+((a|b|rc)[0-9]*)?$'
37
-
38
- def _getCurrentCommit(outp: s_output.OutPut) -> str | None:
39
- try:
40
- ret = subprocess.run(['git', 'rev-parse', 'HEAD'],
41
- capture_output=True,
42
- timeout=15,
43
- check=False,
44
- text=True,
45
- )
46
- except Exception as e:
47
- outp.printf(f'Error grabbing commit: {e}')
48
- return
49
- else:
50
- commit = ret.stdout.strip()
51
- assert commit
52
- return commit
53
-
54
- async def _getCurrentModl(outp: s_output.OutPut) -> dict:
55
- with tempfile.TemporaryDirectory() as dirn:
56
- conf = {'health:sysctl:checks': False}
57
- async with await s_cortex.Cortex.anit(conf=conf, dirn=dirn) as core:
58
- modl = await core.getModelDict()
59
- # Reserialize modl so its consistent with the model on disk
60
- modl = s_common.yamlloads(s_common.yamldump(modl))
61
- return modl
62
-
63
-
64
- class ModelDiffer:
65
- def __init__(self, current_model: dict, reference_model: dict):
66
- self.cur_model = current_model
67
- self.ref_model = reference_model
68
- self.changes = {}
69
-
70
- self.cur_iface_to_allifaces = collections.defaultdict(list)
71
- for iface, info in self.cur_model.get('interfaces').items():
72
- self.cur_iface_to_allifaces[iface] = [iface]
73
- q = collections.deque(info.get('interfaces', ()))
74
- while q:
75
- _iface = q.popleft()
76
- if _iface in self.cur_iface_to_allifaces[iface]:
77
- continue
78
- self.cur_iface_to_allifaces[iface].append(_iface)
79
- q.extend(self.cur_model.get('interfaces').get(_iface).get('interfaces', ()))
80
-
81
- self.cur_type2iface = collections.defaultdict(list)
82
-
83
- for _type, tnfo in self.cur_model.get('types').items():
84
- for iface in tnfo.get('info').get('interfaces', ()):
85
- ifaces = self.cur_iface_to_allifaces[iface]
86
- for _iface in ifaces:
87
- if _iface not in self.cur_type2iface[_type]:
88
- self.cur_type2iface[_type].append(_iface)
89
-
90
- def _compareEdges(self, curv, oldv, outp: s_output.OutPut) -> dict:
91
- changes = {}
92
- if curv == oldv:
93
- return changes
94
-
95
- # Flatten the edges into structures that can be handled
96
- _curv = {tuple(item[0]): item[1] for item in curv}
97
- _oldv = {tuple(item[0]): item[1] for item in oldv}
98
-
99
- curedges = set(_curv.keys())
100
- oldedges = set(_oldv.keys())
101
-
102
- new_edges = curedges - oldedges
103
- del_edges = oldedges - curedges # This should generally not happen...
104
- assert len(del_edges) == 0, 'A edge was removed from the data model!'
105
-
106
- if new_edges:
107
- changes['new_edges'] = {k: _curv.get(k) for k in new_edges}
108
-
109
- updated_edges = collections.defaultdict(dict)
110
- deprecated_edges = {}
111
-
112
- for edge, curinfo in _curv.items():
113
- if edge in new_edges:
114
- continue
115
- oldinfo = _oldv.get(edge)
116
- if curinfo == oldinfo:
117
- continue
118
-
119
- if curinfo.get('deprecated') and not oldinfo.get('deprecated'):
120
- deprecated_edges[edge] = curinfo
121
- continue
122
-
123
- if oldinfo.get('doc') != curinfo.get('doc'):
124
- updated_edges[edge] = curinfo
125
- continue
126
-
127
- # TODO - Support additional changes to the edges?
128
- assert False, f'A change was found for the edge: {edge}'
129
-
130
- if updated_edges:
131
- changes['updated_edges'] = dict(updated_edges)
132
-
133
- if deprecated_edges:
134
- changes['deprecated_edges'] = deprecated_edges
135
-
136
- return changes
137
-
138
- def _compareForms(self, curv, oldv, outp: s_output.OutPut) -> dict:
139
- changes = {}
140
- if curv == oldv:
141
- return changes
142
-
143
- curforms = set(curv.keys())
144
- oldforms = set(oldv.keys())
145
-
146
- new_forms = curforms - oldforms
147
- del_forms = oldforms - curforms # This should generally not happen...
148
- assert len(del_forms) == 0, 'A form was removed from the data model!'
149
-
150
- if new_forms:
151
- changes['new_forms'] = {k: curv.get(k) for k in new_forms}
152
-
153
- updated_forms = collections.defaultdict(dict)
154
- for form, curinfo in curv.items():
155
- if form in new_forms:
156
- continue
157
- oldinfo = oldv.get(form)
158
- if curinfo == oldinfo:
159
- continue
160
-
161
- # Check for different properties
162
- nprops = curinfo.get('props')
163
- oprops = oldinfo.get('props')
164
-
165
- new_props = set(nprops.keys()) - set(oprops.keys())
166
- del_props = set(oprops.keys()) - set(nprops.keys()) # This should generally not happen...
167
- assert len(del_props) == 0, 'A form was removed from the data model!'
168
-
169
- if new_props:
170
- updated_forms[form]['new_properties'] = {prop: nprops.get(prop) for prop in new_props}
171
- np_noiface = {}
172
- ifaces = self.cur_type2iface[form]
173
-
174
- for prop in new_props:
175
- # TODO record raw new_props to make bulk edits possible
176
- is_ifaceprop = False
177
- for iface in ifaces:
178
- # Is the prop in the new_interfaces or updated_interfaces lists?
179
- new_iface = self.changes.get('interfaces').get('new_interfaces', {}).get(iface)
180
- if new_iface and prop in new_iface.get('props', {}):
181
- is_ifaceprop = True
182
- break
183
-
184
- upt_iface = self.changes.get('interfaces').get('updated_interfaces', {}).get(iface)
185
- if upt_iface and prop in upt_iface.get('new_properties', {}):
186
- is_ifaceprop = True
187
- break
188
-
189
- if is_ifaceprop:
190
- continue
191
-
192
- np_noiface[prop] = nprops.get(prop)
193
-
194
- if np_noiface:
195
- updated_forms[form]['new_properties_no_interfaces'] = np_noiface
196
-
197
- updated_props = {}
198
- updated_props_noiface = {}
199
-
200
- deprecated_props = {}
201
- deprecated_props_noiface = {}
202
-
203
- for prop, cpinfo in nprops.items():
204
- if prop in new_props:
205
- continue
206
- opinfo = oprops.get(prop)
207
- if cpinfo == opinfo:
208
- continue
209
-
210
- # Deprecation has a higher priority than updated type information
211
- if cpinfo.get('deprecated') and not opinfo.get('deprecated'):
212
- # A deprecated property could be present on an updated iface
213
- deprecated_props[prop] = cpinfo
214
-
215
- is_ifaceprop = False
216
- for iface in self.cur_type2iface[form]:
217
- upt_iface = self.changes.get('interfaces').get('updated_interfaces', {}).get(iface)
218
- if upt_iface and prop in upt_iface.get('deprecated_properties', {}):
219
- is_ifaceprop = True
220
- break
221
- if is_ifaceprop:
222
- continue
223
-
224
- deprecated_props_noiface[prop] = cpinfo
225
- continue
226
-
227
- okeys = set(opinfo.keys())
228
- nkeys = set(cpinfo.keys())
229
-
230
- if nkeys - okeys:
231
- # We've added a key to the prop def.
232
- updated_props[prop] = {'type': 'addkey', 'keys': list(nkeys - okeys)}
233
-
234
- if okeys - nkeys:
235
- # We've removed a key from the prop def.
236
- updated_props[prop] = {'type': 'delkey', 'keys': list(okeys - nkeys)}
237
-
238
- # Check if type change happened, we'll want to document that.
239
- ctyp = cpinfo.get('type')
240
- otyp = opinfo.get('type')
241
- if ctyp == otyp:
242
- continue
243
-
244
- updated_props[prop] = {'type': 'type_change', 'new_type': ctyp, 'old_type': otyp}
245
- is_ifaceprop = False
246
- for iface in self.cur_type2iface[form]:
247
- upt_iface = self.changes.get('interfaces').get('updated_interfaces', {}).get(iface)
248
- if upt_iface and prop in upt_iface.get('updated_properties', {}):
249
- is_ifaceprop = True
250
- break
251
- if is_ifaceprop:
252
- continue
253
- updated_props_noiface[prop] = {'type': 'type_change', 'new_type': ctyp, 'old_type': otyp}
254
-
255
- if updated_props:
256
- updated_forms[form]['updated_properties'] = updated_props
257
-
258
- if updated_props_noiface:
259
- updated_forms[form]['updated_properties_no_interfaces'] = updated_props_noiface
260
-
261
- if deprecated_props:
262
- updated_forms[form]['deprecated_properties'] = deprecated_props
263
-
264
- if deprecated_props_noiface:
265
- updated_forms[form]['deprecated_properties_no_interfaces'] = deprecated_props_noiface
266
-
267
- if updated_forms:
268
- changes['updated_forms'] = dict(updated_forms)
269
-
270
- return changes
271
-
272
- def _compareIfaces(self, curv, oldv, outp: s_output.OutPut) -> dict:
273
- changes = {}
274
- if curv == oldv:
275
- return changes
276
-
277
- curfaces = set(curv.keys())
278
- oldfaces = set(oldv.keys())
279
-
280
- new_faces = curfaces - oldfaces
281
- del_faces = oldfaces - curfaces # This should generally not happen...
282
- assert len(del_faces) == 0, 'An interface was removed from the data model!'
283
-
284
- if new_faces:
285
- nv = {}
286
- for iface in new_faces:
287
- k = copy.deepcopy(curv.get(iface))
288
- # Rewrite props into a dictionary for easier lookup later
289
- k['props'] = {item[0]: {'type': item[1], 'props': item[2]} for item in k['props']}
290
- nv[iface] = k
291
-
292
- changes['new_interfaces'] = nv
293
-
294
- updated_interfaces = collections.defaultdict(dict)
295
-
296
- for iface, curinfo in curv.items():
297
- if iface in new_faces:
298
- continue
299
- oldinfo = oldv.get(iface)
300
-
301
- # Did the interface inheritance change?
302
- if curinfo.get('interfaces') != oldinfo.get('interfaces'):
303
- updated_interfaces[iface] = {'updated_interfaces': {'curv': curinfo.get('interfaces'),
304
- 'oldv': oldinfo.get('interfaces')}}
305
- # Did the interface have a property definition change?
306
- nprops = curinfo.get('props')
307
- oprops = oldinfo.get('props')
308
-
309
- # Convert props to dictionary
310
- nprops = {item[0]: {'type': item[1], 'props': item[2]} for item in nprops}
311
- oprops = {item[0]: {'type': item[1], 'props': item[2]} for item in oprops}
312
-
313
- new_props = set(nprops.keys()) - set(oprops.keys())
314
- del_props = set(oprops.keys()) - set(nprops.keys()) # This should generally not happen...
315
- assert len(del_props) == 0, f'A prop was removed from the iface {iface}'
316
-
317
- if new_props:
318
- updated_interfaces[iface]['new_properties'] = {prop: nprops.get(prop) for prop in new_props}
319
-
320
- updated_props = {}
321
- deprecated_props = {}
322
- for prop, cpinfo in nprops.items():
323
- if prop in new_props:
324
- continue
325
- opinfo = oprops.get(prop)
326
- if cpinfo == opinfo:
327
- continue
328
-
329
- if cpinfo.get('props').get('deprecated') and not opinfo.get('props').get('deprecated'):
330
- deprecated_props[prop] = cpinfo
331
- continue
332
-
333
- okeys = set(opinfo.keys())
334
- nkeys = set(cpinfo.keys())
335
-
336
- if nkeys - okeys:
337
- # We've added a key to the prop def.
338
- updated_props[prop] = {'type': 'addkey', 'keys': list(nkeys - okeys)}
339
-
340
- if okeys - nkeys:
341
- # We've removed a key from the prop def.
342
- updated_props[prop] = {'type': 'delkey', 'keys': list(okeys - nkeys)}
343
-
344
- # Check if type change happened, we'll want to document that.
345
- ctyp = cpinfo.get('type')
346
- otyp = opinfo.get('type')
347
- if ctyp == otyp:
348
- continue
349
-
350
- updated_props[prop] = {'type': 'type_change', 'new_type': ctyp, 'old_type': otyp}
351
- if updated_props:
352
- updated_interfaces[iface]['updated_properties'] = updated_props
353
-
354
- if deprecated_props:
355
- updated_interfaces[iface]['deprecated_properties'] = deprecated_props
356
-
357
- changes['updated_interfaces'] = dict(updated_interfaces)
358
-
359
- return changes
360
-
361
- def _compareTagprops(self, curv, oldv, outp: s_output.OutPut) -> dict:
362
- changes = {}
363
- if curv == oldv:
364
- return changes
365
- raise NotImplementedError('_compareTagprops')
366
-
367
- def _compareTypes(self, curv, oldv, outp: s_output.OutPut) -> dict:
368
- changes = {}
369
- if curv == oldv:
370
- return changes
371
-
372
- curtypes = set(curv.keys())
373
- oldtypes = set(oldv.keys())
374
-
375
- new_types = curtypes - oldtypes
376
- del_types = oldtypes - curtypes # This should generally not happen...
377
- assert len(del_types) == 0, 'A type was removed from the data model!'
378
-
379
- if new_types:
380
- changes['new_types'] = {k: curv.get(k) for k in new_types}
381
-
382
- updated_types = collections.defaultdict(dict)
383
- deprecated_types = collections.defaultdict(dict)
384
-
385
- for _type, curinfo in curv.items():
386
- if _type in new_types:
387
- continue
388
- oldinfo = oldv.get(_type)
389
- if curinfo == oldinfo:
390
- continue
391
-
392
- cnfo = curinfo.get('info')
393
- onfo = oldinfo.get('info')
394
-
395
- if cnfo.get('deprecated') and not onfo.get('deprecated'):
396
- deprecated_types[_type] = curinfo
397
- continue
398
-
399
- if cnfo.get('interfaces') != onfo.get('interfaces'):
400
- updated_types[_type]['updated_interfaces'] = {'curv': cnfo.get('interfaces'),
401
- 'oldv': onfo.get('interfaces'),
402
- }
403
-
404
- if curinfo.get('opts') != oldinfo.get('opts'):
405
- updated_types[_type]['updated_opts'] = {'curv': curinfo.get('opts'),
406
- 'oldv': oldinfo.get('opts'),
407
- }
408
-
409
- if updated_types:
410
- changes['updated_types'] = dict(updated_types)
411
-
412
- if deprecated_types:
413
- changes['deprecated_types'] = dict(deprecated_types)
414
-
415
- return changes
416
-
417
- def _compareUnivs(self, curv, oldv, outp: s_output.OutPut) -> dict:
418
- changes = {}
419
- if curv == oldv:
420
- return changes
421
- raise NotImplementedError('_compareUnivs')
422
-
423
- def diffModl(self, outp: s_output.OutPut) -> dict | None:
424
- if self.changes:
425
- return self.changes
426
-
427
- # These are order sensitive due to interface knowledge being required in order
428
- # to deconflict downstream changes on forms.
429
- known_keys = {
430
- 'interfaces': self._compareIfaces,
431
- 'types': self._compareTypes,
432
- 'forms': self._compareForms,
433
- 'tagprops': self._compareTagprops,
434
- 'edges': self._compareEdges,
435
- 'univs': self._compareUnivs,
436
- }
437
-
438
- all_keys = set(self.cur_model.keys()).union(self.ref_model.keys())
439
-
440
- for key, func in known_keys.items():
441
- self.changes[key] = func(self.cur_model.get(key),
442
- self.ref_model.get(key),
443
- outp)
444
- all_keys.remove(key)
445
-
446
- if all_keys:
447
- outp.printf(f'ERROR: Unknown model key found: {all_keys}')
448
- return
449
-
450
- return self.changes
451
-
452
- def _getModelFile(fp: str) -> dict | None:
453
- with s_common.genfile(fp) as fd:
454
- bytz = fd.read()
455
- large_bytz = gzip.decompress(bytz)
456
- ref_modl = s_common.yamlloads(large_bytz)
457
- return ref_modl
458
-
459
- async def gen(opts: s_cmd.argparse.Namespace,
460
- outp: s_output.OutPut):
461
-
462
- name = opts.name
463
- if name is None:
464
- name = f'{s_common.guid()}.yaml'
465
- fp = s_common.genpath(opts.cdir, name)
466
-
467
- data = dict(defstruct)
468
- data['type'] = opts.type
469
- data['desc'] = opts.desc
470
-
471
- if opts.pr:
472
- data['prs'] = [opts.pr]
473
-
474
- if opts.verbose:
475
- outp.printf('Validating data against schema')
476
-
477
- s_schemas._reqChangelogSchema(data)
478
-
479
- if opts.verbose:
480
- outp.printf('Saving the following information:')
481
- outp.printf(s_common.yamldump(data).decode())
482
-
483
- s_common.yamlsave(data, fp)
484
-
485
- outp.printf(f'Saved changelog entry to {fp=}')
486
-
487
- if opts.add:
488
- if opts.verbose:
489
- outp.printf('Adding file to git staging')
490
- argv = ['git', 'add', fp]
491
- ret = subprocess.run(argv, capture_output=True)
492
- if opts.verbose:
493
- outp.printf(f'stddout={ret.stdout}')
494
- outp.printf(f'stderr={ret.stderr}')
495
- ret.check_returncode()
496
-
497
- return 0
498
-
499
- def _gen_model_rst(version, model_ref, changes, current_model, outp: s_output.OutPut, width=80) -> s_autodoc.RstHelp:
500
- rst = s_autodoc.RstHelp()
501
- rst.addHead(f'{version} Model Updates', link=f'.. _{model_ref}:')
502
- rst.addLines(f'The following model updates were made during the ``{version}`` Synapse release.')
503
-
504
- if new_interfaces := changes.get('interfaces').get('new_interfaces'):
505
- rst.addHead('New Interfaces', lvl=1)
506
- for interface, info in new_interfaces.items():
507
- rst.addLines(f'``{interface}``')
508
- rst.addLines(*textwrap.wrap(info.get('doc'), initial_indent=' ', subsequent_indent=' ',
509
- width=width))
510
- rst.addLines('\n')
511
-
512
- # Deconflict new_forms vs new_types -> do not add types which appear in new_forms.
513
- new_forms = changes.get('forms').get('new_forms', {})
514
- new_types = changes.get('types').get('new_types', {})
515
- types_to_document = {k: v for k, v in new_types.items() if k not in new_forms}
516
-
517
- if types_to_document:
518
- rst.addHead('New Types', lvl=1)
519
- for _type, info in types_to_document.items():
520
- rst.addLines(f'``{_type}``')
521
- rst.addLines(*textwrap.wrap(info.get('info').get('doc'), initial_indent=' ', subsequent_indent=' ',
522
- width=width))
523
- rst.addLines('\n')
524
-
525
- if new_forms:
526
- rst.addHead('New Forms', lvl=1)
527
- for form, info in new_forms.items():
528
- rst.addLines(f'``{form}``')
529
- # Pull the form doc from the current model directly. In the event of an existing
530
- # type being turned into a form + then reindexed, it would not show up in the
531
- # type diff, so we can't rely on the doc being present there.
532
- doc = current_model.get('types').get(form).get('info').get('doc')
533
- rst.addLines(*textwrap.wrap(doc, initial_indent=' ', subsequent_indent=' ',
534
- width=width))
535
- rst.addLines('\n')
536
-
537
- # Check for new properties
538
- updated_forms = changes.get('forms').get('updated_forms', {})
539
- new_props = []
540
- for form, info in updated_forms.items():
541
- if 'new_properties' in info:
542
- new_props.append((form, info))
543
- if new_props:
544
- rst.addHead('New Properties', lvl=1)
545
- new_props.sort(key=lambda x: x[0])
546
- for form, info in new_props:
547
- rst.addLines(f'``{form}``')
548
- new_form_props = list(info.get('new_properties').items())
549
- if len(new_form_props) > 1:
550
- rst.addLines(' The form had the following properties added to it:', '\n')
551
- new_form_props.sort(key=lambda x: x[0])
552
- for name, info in new_form_props:
553
- lines = [
554
- f' ``{name}``',
555
- *textwrap.wrap(info.get('doc'), initial_indent=' ', subsequent_indent=' ',
556
- width=width),
557
- '\n'
558
- ]
559
- rst.addLines(*lines)
560
-
561
- else:
562
- name, info = new_form_props[0]
563
- lines = [
564
- ' The form had the following property added to it:',
565
- '\n'
566
- f' ``{name}``',
567
- *textwrap.wrap(info.get('doc'), initial_indent=' ', subsequent_indent=' ',
568
- width=width),
569
- '\n'
570
- ]
571
- rst.addLines(*lines)
572
-
573
- # Updated interfaces
574
- if updated_interfaces := changes.get('interfaces').get('updated_interfaces', {}):
575
- upd_ifaces = list(updated_interfaces.items())
576
- upd_ifaces.sort(key=lambda x: x[0])
577
- rst.addHead('Updated Interfaces', lvl=1)
578
- for iface, info in upd_ifaces:
579
- lines = [f'``{iface}``',
580
- ]
581
- for key, valu in sorted(info.items(), key=lambda x: x[0]):
582
- if key == 'deprecated_properties':
583
- for prop, pnfo in sorted(valu.items(), key=lambda x: x[0]):
584
- mesg = f'The interface property ``{prop}`` has been deprecated.'
585
- lines.extend(textwrap.wrap(mesg, initial_indent=' ', subsequent_indent=' ',
586
- width=width))
587
- lines.append('\n')
588
- elif key == 'new_properties':
589
- for prop, pnfo in sorted(valu.items(), key=lambda x: x[0]):
590
- mesg = f'The property ``{prop}`` has been added to the interface.'
591
- lines.extend(textwrap.wrap(mesg, initial_indent=' ', subsequent_indent=' ',
592
- width=width))
593
- lines.append('\n')
594
- elif key == 'updated_properties':
595
- for prop, pnfo in sorted(valu.items(), key=lambda x: x[0]):
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.')
604
- lines.extend(textwrap.wrap(mesg, initial_indent=' ', subsequent_indent=' ',
605
- width=width))
606
- lines.append('\n')
607
- else: # pragma: no cover
608
- outp.printf(f'Unknown key: {key=} {valu=}')
609
- raise s_exc.SynErr(mesg=f'Unknown updated interface key: {key=} {valu=}')
610
- rst.addLines(*lines)
611
-
612
- # Updated types
613
- if updated_types := changes.get('types').get('updated_types', {}):
614
- upd_types = list(updated_types.items())
615
- upd_types.sort(key=lambda x: x[0])
616
- rst.addHead('Updated Types', lvl=1)
617
- for _type, info in upd_types:
618
- lines = [f'``{_type}``',
619
- ]
620
- for key, valu in sorted(info.items(), key=lambda x: x[0]):
621
- if key == 'updated_interfaces':
622
- mesg = f'The type interface has been modified from {valu.get("oldv")}' \
623
- f' to {valu.get("curv")}.'
624
- lines.extend(textwrap.wrap(mesg, initial_indent=' ', subsequent_indent=' ',
625
- width=width))
626
- lines.append('\n')
627
- elif key == 'updated_opts':
628
- mesg = f'The type has been modified from {valu.get("oldv")}' \
629
- f' to {valu.get("curv")}.'
630
- lines.extend(textwrap.wrap(mesg, initial_indent=' ', subsequent_indent=' ',
631
- width=width))
632
- lines.append('\n')
633
- else: # pragma: no cover
634
- outp.printf(f'Unknown key: {key=} {valu=}')
635
- raise s_exc.SynErr(mesg=f'Unknown updated type key: {key=} {valu=}')
636
- rst.addLines(*lines)
637
-
638
- # Updated Forms
639
- # We don't really have a "updated forms" to display since the delta for forms data is really property
640
- # deltas covered elsewhere.
641
-
642
- # Updated Edges
643
- # TODO Add support for updated edges
644
-
645
- # Updated Properties
646
- upd_props = []
647
- for form, info in updated_forms.items():
648
- if 'updated_properties' in info:
649
- upd_props.append((form, info))
650
- if upd_props:
651
- rst.addHead('Updated Properties', lvl=1)
652
- upd_props.sort(key=lambda x: x[0])
653
- for form, info in upd_props:
654
- rst.addLines(f'``{form}``')
655
- upd_form_props = list(info.get('updated_properties').items())
656
- if len(upd_form_props) > 1:
657
- rst.addLines(' The form had the following properties updated:', '\n')
658
- upd_form_props.sort(key=lambda x: x[0])
659
- for prop, pnfo in upd_form_props:
660
- ptyp = pnfo.get('type')
661
- if ptyp == 'type_change':
662
- mesg = f'The property ``{prop}`` has been modified from {pnfo.get("old_type")}' \
663
- f' to {pnfo.get("new_type")}.'
664
- elif ptyp == 'delkey':
665
- mesg = f'The property ``{prop}`` had the ``{pnfo.get("keys")}`` keys removed from its definition.'
666
- elif ptyp == 'addkey':
667
- mesg = f'The property ``{prop}`` had the ``{pnfo.get("keys")}`` keys added to its definition.'
668
- else:
669
- raise s_exc.NoSuchImpl(mesg=f'pnfo.type={ptyp} not supported.')
670
- lines = [
671
- *textwrap.wrap(mesg, initial_indent=' ', subsequent_indent=' ',
672
- width=width),
673
- '\n'
674
- ]
675
- rst.addLines(*lines)
676
-
677
- else:
678
- prop, pnfo = upd_form_props[0]
679
- ptyp = pnfo.get('type')
680
- if ptyp == 'type_change':
681
- mesg = f'The property ``{prop}`` has been modified from {pnfo.get("old_type")}' \
682
- f' to {pnfo.get("new_type")}.'
683
- elif ptyp == 'delkey':
684
- mesg = f'The property ``{prop}`` had the ``{pnfo.get("keys")}`` keys removed from its definition.'
685
- elif ptyp == 'addkey':
686
- mesg = f'The property ``{prop}`` had the ``{pnfo.get("keys")}`` keys added to its definition.'
687
- else:
688
- raise s_exc.NoSuchImpl(mesg=f'pnfo.type={ptyp} not supported.')
689
-
690
- lines = [
691
- ' The form had the following property updated:',
692
- '\n',
693
- *textwrap.wrap(mesg, initial_indent=' ', subsequent_indent=' ',
694
- width=width),
695
- '\n'
696
- ]
697
- rst.addLines(*lines)
698
-
699
- # Light Edges
700
- if new_edges := changes.get('edges').get('new_edges'):
701
- new_edges = list(new_edges.items())
702
- new_edges.sort(key=lambda x: x[0][1])
703
- rst.addHead('Light Edges', lvl=1)
704
- for (n1, name, n2), info in new_edges:
705
- if n1 is not None and n2 is not None:
706
- mesg = f'''When used with a ``{n1}`` and an ``{n2}`` node, the edge indicates {info.get('doc')}'''
707
- elif n1 is None and n2 is not None:
708
- mesg = f'''When used with a ``{n2}`` target node, the edge indicates {info.get('doc')}'''
709
- elif n1 is not None and n2 is None:
710
- mesg = f'''When used with a ``{n1}`` node, the edge indicates {info.get('doc')}'''
711
- else:
712
- mesg = info.get('doc')
713
-
714
- rst.addLines(
715
- f'``{name}``',
716
- *textwrap.wrap(mesg, initial_indent=' ', subsequent_indent=' ', width=width),
717
- '\n',
718
- )
719
-
720
- # Deprecated Interfaces
721
- # TODO Support deprecated interfaces!
722
-
723
- # Deprecated Types
724
- # Deconflict deprecated forms vs deprecated_types, so we do not
725
- # not call out types which are also forms in the current model.
726
- deprecated_types = changes.get('types').get('deprecated_types', {})
727
- deprecated_forms = {k: v for k, v in deprecated_types.items() if k in current_model.get('forms')}
728
- deprecated_types = {k: v for k, v in deprecated_types.items() if k not in deprecated_forms}
729
- if deprecated_types:
730
- rst.addHead('Deprecated Types', lvl=1)
731
- rst.addLines('The following types have been marked as deprecated:', '\n')
732
-
733
- for _type, info in deprecated_types.items():
734
- rst.addLines(
735
- f'* ``{_type}``',
736
- )
737
- rst.addLines('\n')
738
-
739
- # Deprecated Forms
740
- if deprecated_forms:
741
- rst.addHead('Deprecated Types', lvl=1)
742
- rst.addLines('The following forms have been marked as deprecated:', '\n')
743
-
744
- for _type, info in deprecated_forms.items():
745
- rst.addLines(
746
- f'* ``{_type}``',
747
- )
748
- rst.addLines('\n')
749
-
750
- # Deprecated Properties
751
- dep_props = []
752
- for form, info in updated_forms.items():
753
- if 'deprecated_properties' in info:
754
- dep_props.append((form, info))
755
- if dep_props:
756
- rst.addHead('Deprecated Properties', lvl=1)
757
- dep_props.sort(key=lambda x: x[0])
758
- for form, info in dep_props:
759
- rst.addLines(f'``{form}``')
760
- dep_form_props = list(info.get('deprecated_properties').items())
761
- if len(dep_form_props) > 1:
762
- rst.addLines(' The form had the following properties deprecated:', '\n')
763
- dep_form_props.sort(key=lambda x: x[0])
764
- for name, info in dep_form_props:
765
- lines = [
766
- f' ``{name}``',
767
- *textwrap.wrap(info.get('doc'), initial_indent=' ', subsequent_indent=' ',
768
- width=width),
769
- '\n'
770
- ]
771
- rst.addLines(*lines)
772
-
773
- else:
774
- name, info = dep_form_props[0]
775
- lines = [
776
- ' The form had the following property deprecated:',
777
- '\n'
778
- f' ``{name}``',
779
- *textwrap.wrap(info.get('doc'), initial_indent=' ', subsequent_indent=' ',
780
- width=width),
781
- '\n'
782
- ]
783
- rst.addLines(*lines)
784
-
785
- if dep_edges := changes.get('edges').get('deprecated_edges'):
786
-
787
- rst.addHead('Deprecated Edges', lvl=1)
788
- for (n1, name, n2), info in dep_edges.items():
789
- if n1 is not None and n2 is not None:
790
- mesg = f'''The edge has been deprecated when used with a ``{n1}`` and an ``{n2}`` node. {info.get('doc')}'''
791
- elif n1 is None and n2 is not None:
792
- mesg = f'''The edge has been deprecated when used with a ``{n2}`` target node. {info.get('doc')}'''
793
- elif n1 is not None and n2 is None:
794
- mesg = f'''The edge has been deprecated when used with a ``{n1}`` node. {info.get('doc')}'''
795
- else:
796
- mesg = f'''The edge has been deprecated. {info.get('doc')}'''
797
-
798
- rst.addLines(
799
- f'``{name}``',
800
- *textwrap.wrap(mesg, initial_indent=' ', subsequent_indent=' ', width=width),
801
- '\n',
802
- )
803
-
804
- return rst
805
-
806
-
807
- async def format(opts: s_cmd.argparse.Namespace,
808
- outp: s_output.OutPut):
809
-
810
- if not regex.match(version_regex, opts.version):
811
- outp.printf(f'Failed to match {opts.version} vs {version_regex}')
812
- return 1
813
-
814
- entries = collections.defaultdict(list)
815
-
816
- files_processed = [] # Eventually for removing files from git.
817
-
818
- for fn in os.listdir(opts.cdir):
819
- if fn in SKIP_FILES:
820
- continue
821
- fp = s_common.genpath(opts.cdir, fn)
822
- if opts.verbose:
823
- outp.printf(f'Reading: {fp=}')
824
- try:
825
- data = s_common.yamlload(fp)
826
- except Exception as e:
827
- outp.printf(f'Error parsing yaml from {fp=}: {e}')
828
- continue
829
-
830
- if opts.verbose:
831
- outp.printf('Got the following data:')
832
- outp.printf(pprint.pformat(data))
833
-
834
- files_processed.append(fp)
835
-
836
- s_schemas._reqChangelogSchema(data)
837
-
838
- data.setdefault('prs', [])
839
- prs = data.get('prs')
840
-
841
- if opts.prs_from_git:
842
-
843
- argv = ['git', 'log', '--pretty=oneline', fp]
844
- ret = subprocess.run(argv, capture_output=True)
845
- if opts.verbose:
846
- outp.printf(f'stddout={ret.stdout}')
847
- outp.printf(f'stderr={ret.stderr}')
848
- ret.check_returncode()
849
-
850
- for line in ret.stdout.splitlines():
851
- line = line.decode()
852
- line = line.strip()
853
- if not line:
854
- continue
855
- match = re.search('\\(#(?P<pr>\\d{1,})\\)', line)
856
- if match:
857
- for pr in match.groups():
858
- pr = int(pr)
859
- if pr not in prs:
860
- prs.append(pr)
861
- if opts.verbose:
862
- outp.printf(f'Added PR #{pr} to the pr list from [{line=}]')
863
-
864
- if opts.enforce_prs and not prs:
865
- outp.printf(f'Entry is missing PR numbers: {fp=}')
866
- return 1
867
-
868
- if opts.verbose:
869
- outp.printf(f'Got data from {fp=}')
870
-
871
- prs.sort() # sort the PRs inplace
872
- entries[data.get('type')].append(data)
873
-
874
- if not entries:
875
- outp.printf(f'No files passed validation from {opts.dir}')
876
- return 1
877
-
878
- date = opts.date
879
- if date is None:
880
- date = datetime.datetime.utcnow().strftime('%Y-%m-%d')
881
- header = f'{opts.version} - {date}'
882
- text = f'{header}\n{"=" * len(header)}\n'
883
-
884
- modeldiff = False
885
- clean_vers_ref = opts.version.replace(".", "_")
886
- model_rst_ref = f'userguide_model_{clean_vers_ref}'
887
-
888
- if opts.model_ref:
889
- # TODO find previous model file automatically?
890
- if opts.verbose:
891
- outp.printf(f'Getting reference model from {opts.model_ref}')
892
-
893
- ref_modl = _getModelFile(opts.model_ref)
894
-
895
- if opts.model_current:
896
- to_modl = _getModelFile(opts.model_current)
897
- cur_modl = to_modl.get('model')
898
- if opts.verbose:
899
- outp.printf(f'Comparing {to_modl.get("version")} - {to_modl.get("commit")} vs {ref_modl.get("version")} - {ref_modl.get("commit")}')
900
- else:
901
- cur_modl = await _getCurrentModl(outp)
902
- if opts.verbose:
903
- outp.printf(f'Comparing current model vs {ref_modl.get("version")} - {ref_modl.get("commit")}')
904
-
905
- differ = ModelDiffer(cur_modl, ref_modl.get('model'))
906
- changes = differ.diffModl(outp)
907
- has_changes = sum([len(v) for v in changes.values()])
908
- if has_changes:
909
- entries['model'].append({'prs': [], 'type': 'skip'})
910
- modeldiff = True
911
- rst = _gen_model_rst(opts.version, model_rst_ref, changes, cur_modl, outp, width=opts.width)
912
- model_text = rst.getRstText()
913
- if opts.verbose:
914
- outp.printf(model_text)
915
- if opts.model_doc_dir:
916
- fp = s_common.genpath(opts.model_doc_dir, f'update_{clean_vers_ref}.rst')
917
- with s_common.genfile(fp) as fd:
918
- fd.truncate(0)
919
- fd.write(model_text.encode())
920
- outp.printf(f'Wrote model changes to {fp}')
921
- if opts.verbose:
922
- outp.printf(f'Adding file to git.')
923
- argv = ['git', 'add', fp]
924
- ret = subprocess.run(argv, capture_output=True)
925
- if opts.verbose:
926
- outp.printf(f'stddout={ret.stdout}')
927
- outp.printf(f'stderr={ret.stderr}')
928
- ret.check_returncode()
929
- else:
930
- outp.printf(f'No model changes detected.')
931
-
932
- for key, header in s_schemas._changelogTypes.items():
933
- dataz = entries.get(key)
934
- if dataz:
935
- text = text + f'\n{header}\n{"-" * len(header)}'
936
- dataz.sort(key=lambda x: x.get('prs'))
937
- for data in dataz:
938
- desc = data.get('desc') # type: str
939
- if desc is None and data.get('type') == 'skip':
940
- continue
941
- desc_lines = desc.splitlines()
942
- for i, chunk in enumerate(desc_lines):
943
- if i == 0:
944
- for line in textwrap.wrap(chunk, initial_indent='- ', subsequent_indent=' ', width=opts.width):
945
- text = f'{text}\n{line}'
946
- else:
947
- text = text + '\n'
948
- for line in textwrap.wrap(chunk, initial_indent=' ', subsequent_indent=' ', width=opts.width):
949
- text = f'{text}\n{line}'
950
-
951
- if not opts.hide_prs:
952
- for pr in data.get('prs'):
953
- text = f'{text}\n (`#{pr} <https://github.com/vertexproject/synapse/pull/{pr}>`_)'
954
- if key == 'migration':
955
- text = text + '\n- See :ref:`datamigration` for more information about automatic migrations.'
956
- elif key == 'model':
957
- if modeldiff:
958
- text = text + f'\n- See :ref:`{model_rst_ref}` for more detailed model changes.'
959
- text = text + '\n'
960
-
961
- if opts.rm:
962
- if opts.verbose:
963
- outp.printf('Staging file removals in git')
964
- for fp in files_processed:
965
- argv = ['git', 'rm', fp]
966
- ret = subprocess.run(argv, capture_output=True)
967
- if opts.verbose:
968
- outp.printf(f'stddout={ret.stdout}')
969
- outp.printf(f'stderr={ret.stderr}')
970
- ret.check_returncode()
971
-
972
- outp.printf('CHANGELOG ENTRY:\n\n')
973
- outp.printf(text)
974
-
975
- return 0
976
-
977
- async def model(opts: s_cmd.argparse.Namespace,
978
- outp: s_output.OutPut):
979
-
980
- if opts.save:
981
- modl = await _getCurrentModl(outp)
982
-
983
- dirn = s_common.gendir(opts.cdir, 'modelrefs')
984
- current_commit = _getCurrentCommit(outp)
985
- if not current_commit:
986
- return 1
987
- wrapped_modl = {
988
- 'model': modl,
989
- 'commit': current_commit,
990
- 'version': s_version.version,
991
- }
992
-
993
- fp = s_common.genpath(dirn, f'model_{s_version.verstring}_{current_commit}.yaml.gz')
994
- with s_common.genfile(fp) as fd:
995
- fd.truncate(0)
996
- bytz = s_common.yamldump(wrapped_modl)
997
- small_bytz = gzip.compress(bytz)
998
- _ = fd.write(small_bytz)
999
-
1000
- outp.printf(f'Saved model to {fp}')
1001
- return 0
1002
-
1003
- if opts.compare:
1004
- ref_modl = _getModelFile(opts.compare)
1005
- if opts.to:
1006
- to_modl = _getModelFile(opts.to)
1007
- modl = to_modl.get('model')
1008
- outp.printf(f'Comparing {to_modl.get("version")} - {to_modl.get("commit")} vs {ref_modl.get("version")} - {ref_modl.get("commit")}')
1009
- else:
1010
- modl = await _getCurrentModl(outp)
1011
- outp.printf(f'Comparing current model vs {ref_modl.get("version")} - {ref_modl.get("commit")}')
1012
- differ = ModelDiffer(modl, ref_modl.get('model'))
1013
- changes = differ.diffModl(outp)
1014
- for line in pprint.pformat(changes).splitlines(keepends=False):
1015
- outp.printf(line)
1016
- return 0
1017
-
1018
- async def main(argv, outp=s_output.stdout):
1019
- pars = getArgParser(outp)
1020
-
1021
- opts = pars.parse_args(argv)
1022
- if opts.git_dir_check:
1023
- if not os.path.exists(os.path.join(os.getcwd(), '.git')):
1024
- outp.printf('Current working directory must be the root of the repository.')
1025
- return 1
1026
-
1027
- if opts.verbose:
1028
- outp.printf(f'{opts=}')
1029
-
1030
- try:
1031
- return await opts.func(opts, outp)
1032
- except Exception as e:
1033
- outp.printf(f'Error running {opts.func}: {traceback.format_exc()}')
1034
- return 1
1035
-
1036
- def getArgParser(outp: s_output.OutPut):
1037
- desc = '''Command line tool to manage changelog entries.
1038
- This tool and any data formats associated with it may change at any time.
1039
- '''
1040
- pars = s_cmd.Parser(prog='synapse.tools.changelog', outp=outp, description=desc)
1041
-
1042
- subpars = pars.add_subparsers(required=True,
1043
- title='subcommands',
1044
- dest='cmd', )
1045
- gen_pars = subpars.add_parser('gen', help='Generate a new changelog entry.')
1046
- gen_pars.set_defaults(func=gen)
1047
- gen_pars.add_argument('-t', '--type', required=True, choices=list(s_schemas._changelogTypes.keys()),
1048
- help='The changelog type.')
1049
- gen_pars.add_argument('desc', type=str,
1050
- help='The description to populate the initial changelog entry with.', )
1051
- gen_pars.add_argument('-p', '--pr', type=int, default=False,
1052
- help='PR number associated with the changelog entry.')
1053
- gen_pars.add_argument('-a', '--add', default=False, action='store_true',
1054
- help='Add the newly created file to the current git staging area.')
1055
- # Hidden name override. Mainly for testing.
1056
- gen_pars.add_argument('-n', '--name', default=None, type=str,
1057
- help=s_cmd.argparse.SUPPRESS)
1058
-
1059
- format_pars = subpars.add_parser('format', help='Format existing files into a RST block.')
1060
- format_pars.set_defaults(func=format)
1061
- mux_prs = format_pars.add_mutually_exclusive_group()
1062
- mux_prs.add_argument('--hide-prs', default=False, action='store_true',
1063
- help='Hide PR entries.')
1064
- mux_prs.add_argument('--enforce-prs', default=False, action='store_true',
1065
- help='Enforce PRs list to be populated with at least one number.', )
1066
- format_pars.add_argument('--prs-from-git', default=False, action='store_true',
1067
- help='Attempt to populate any PR numbers from a given files commit history.')
1068
- format_pars.add_argument('-w', '--width', help='Maximum column width to wrap descriptions at.',
1069
- default=79, type=int)
1070
- format_pars.add_argument('--version', required=True, action='store', type=str,
1071
- help='Version number')
1072
- format_pars.add_argument('-d', '--date', action='store', type=str,
1073
- help='Date to use with the changelog entry')
1074
- format_pars.add_argument('-r', '--rm', default=False, action='store_true',
1075
- help='Stage the changelog files as deleted files in git.')
1076
- format_pars.add_argument('-m', '--model-ref', default=None, action='store', type=str,
1077
- help='Baseline model to use when generating model deltas. This is normally the previous releases model file.')
1078
- format_pars.add_argument('--model-current', default=None, action='store',
1079
- help='Optional model file to use as a reference as the current model.')
1080
- format_pars.add_argument('--model-doc-dir', default=None, action='store',
1081
- help='Directory to write the model changes too.')
1082
-
1083
- model_pars = subpars.add_parser('model', help='Helper for working with the Cortex data model.')
1084
- model_pars.set_defaults(func=model)
1085
- mux_model = model_pars.add_mutually_exclusive_group(required=True)
1086
- mux_model.add_argument('-s', '--save', action='store_true', default=False,
1087
- help='Save a copy of the current model to a file.')
1088
- mux_model.add_argument('-c', '--compare', action='store', default=None,
1089
- help='Model to compare the current model against. Useful for debugging modl diff functionality.'
1090
- )
1091
- model_pars.add_argument('-t', '--to', action='store', default=None,
1092
- help='The model file to compare against. Will not use current model if specified.')
1093
4
 
1094
- for p in (gen_pars, format_pars, model_pars):
1095
- p.add_argument('-v', '--verbose', default=False, action='store_true',
1096
- help='Enable verbose output')
1097
- p.add_argument('--cdir', default='./changes', action='store',
1098
- help='Directory of changelog files.')
1099
- # Hidden name override. Mainly for testing.
1100
- p.add_argument('--disable-git-dir-check', dest='git_dir_check', default=True, action='store_false',
1101
- help=s_cmd.argparse.SUPPRESS)
5
+ from synapse.tools.utils.changelog import main
1102
6
 
1103
- return pars
7
+ s_common.deprecated('synapse.tools.changelog is deprecated. Please use synapse.tools.utils.changelog instead.',
8
+ curv='v2.225.0')
1104
9
 
1105
10
  if __name__ == '__main__': # pragma: no cover
1106
11
  s_cmd.exitmain(main)