dsw-tdk 3.13.0__py3-none-any.whl → 4.27.0__py3-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.
- {dsw_tdk → dsw/tdk}/__init__.py +16 -15
- dsw/tdk/__main__.py +5 -0
- dsw/tdk/api_client.py +407 -0
- dsw/tdk/build_info.py +17 -0
- dsw/tdk/cli.py +708 -0
- dsw/tdk/config.py +151 -0
- dsw/tdk/consts.py +25 -0
- dsw/tdk/core.py +565 -0
- {dsw_tdk → dsw/tdk}/model.py +468 -422
- dsw/tdk/py.typed +0 -0
- dsw/tdk/templates/LICENSE.j2 +1 -0
- {dsw_tdk → dsw/tdk}/templates/README.md.j2 +13 -13
- dsw/tdk/templates/env.j2 +3 -0
- {dsw_tdk → dsw/tdk}/templates/starter.j2 +13 -14
- {dsw_tdk → dsw/tdk}/utils.py +198 -173
- dsw/tdk/validation.py +290 -0
- {dsw_tdk-3.13.0.dist-info → dsw_tdk-4.27.0.dist-info}/METADATA +28 -33
- dsw_tdk-4.27.0.dist-info/RECORD +20 -0
- {dsw_tdk-3.13.0.dist-info → dsw_tdk-4.27.0.dist-info}/WHEEL +1 -2
- dsw_tdk-4.27.0.dist-info/entry_points.txt +3 -0
- dsw_tdk/__main__.py +0 -3
- dsw_tdk/api_client.py +0 -273
- dsw_tdk/cli.py +0 -412
- dsw_tdk/consts.py +0 -19
- dsw_tdk/core.py +0 -385
- dsw_tdk/validation.py +0 -206
- dsw_tdk-3.13.0.dist-info/LICENSE +0 -201
- dsw_tdk-3.13.0.dist-info/RECORD +0 -17
- dsw_tdk-3.13.0.dist-info/entry_points.txt +0 -3
- dsw_tdk-3.13.0.dist-info/top_level.txt +0 -1
{dsw_tdk → dsw/tdk}/model.py
RENAMED
|
@@ -1,422 +1,468 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
from
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
self.
|
|
27
|
-
self.
|
|
28
|
-
self.
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
'
|
|
43
|
-
'
|
|
44
|
-
'
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
self.
|
|
77
|
-
self.
|
|
78
|
-
self.
|
|
79
|
-
self.steps
|
|
80
|
-
|
|
81
|
-
@classmethod
|
|
82
|
-
def load(cls, data):
|
|
83
|
-
format_spec = Format(
|
|
84
|
-
uuid=data.get('uuid', None),
|
|
85
|
-
name=data.get('name', None),
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
'
|
|
97
|
-
'
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
self.
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
'
|
|
127
|
-
'
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
self.
|
|
141
|
-
self.
|
|
142
|
-
self.
|
|
143
|
-
self.
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
filename = self.filename.name
|
|
151
|
-
for ext in self.TEMPLATE_EXTENSIONS:
|
|
152
|
-
if filename.endswith(ext):
|
|
153
|
-
return 'text/jinja2'
|
|
154
|
-
guessed_type = mimetypes.guess_type(filename, strict=False)
|
|
155
|
-
if guessed_type is None or guessed_type[0] is None:
|
|
156
|
-
return self.DEFAULT_CONTENT_TYPE
|
|
157
|
-
return guessed_type[0]
|
|
158
|
-
|
|
159
|
-
@property
|
|
160
|
-
def is_text(self):
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
self.
|
|
177
|
-
self.
|
|
178
|
-
self.
|
|
179
|
-
self.
|
|
180
|
-
self.
|
|
181
|
-
self.
|
|
182
|
-
self.
|
|
183
|
-
self.
|
|
184
|
-
self.
|
|
185
|
-
self.
|
|
186
|
-
self.
|
|
187
|
-
self.
|
|
188
|
-
self.
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
def
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
template
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
def
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
self.loaded_json['
|
|
242
|
-
self.loaded_json['
|
|
243
|
-
self.loaded_json['
|
|
244
|
-
self.loaded_json['
|
|
245
|
-
self.loaded_json['
|
|
246
|
-
|
|
247
|
-
self.loaded_json['
|
|
248
|
-
self.loaded_json['
|
|
249
|
-
self.loaded_json['
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
'
|
|
256
|
-
'
|
|
257
|
-
'
|
|
258
|
-
'
|
|
259
|
-
'
|
|
260
|
-
'
|
|
261
|
-
'
|
|
262
|
-
'
|
|
263
|
-
'readme': self.readme,
|
|
264
|
-
'allowedPackages': [ap.serialize() for ap in self.allowed_packages],
|
|
265
|
-
'formats': [f.serialize() for f in self.formats],
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
def
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
def
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
if
|
|
379
|
-
self.
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
def
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
self.
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
self.
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
1
|
+
import collections
|
|
2
|
+
import enum
|
|
3
|
+
import json
|
|
4
|
+
import logging
|
|
5
|
+
import mimetypes
|
|
6
|
+
import pathlib
|
|
7
|
+
import typing
|
|
8
|
+
|
|
9
|
+
import pathspec
|
|
10
|
+
|
|
11
|
+
from . import consts
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
mimetypes.init()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class TemplateFileType(enum.Enum):
|
|
18
|
+
ASSET = 'asset'
|
|
19
|
+
FILE = 'file'
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class PackageFilter:
|
|
23
|
+
|
|
24
|
+
def __init__(self, *, organization_id: str | None = None, km_id: str | None = None,
|
|
25
|
+
min_version: str | None = None, max_version: str | None = None):
|
|
26
|
+
self.organization_id = organization_id
|
|
27
|
+
self.km_id = km_id
|
|
28
|
+
self.min_version = min_version
|
|
29
|
+
self.max_version = max_version
|
|
30
|
+
|
|
31
|
+
@classmethod
|
|
32
|
+
def load(cls, data):
|
|
33
|
+
return PackageFilter(
|
|
34
|
+
organization_id=data.get('orgId', None),
|
|
35
|
+
km_id=data.get('kmId', None),
|
|
36
|
+
min_version=data.get('minVersion', None),
|
|
37
|
+
max_version=data.get('maxVersion', None),
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
def serialize(self):
|
|
41
|
+
return {
|
|
42
|
+
'orgId': self.organization_id,
|
|
43
|
+
'kmId': self.km_id,
|
|
44
|
+
'minVersion': self.min_version,
|
|
45
|
+
'maxVersion': self.max_version,
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class Step:
|
|
50
|
+
|
|
51
|
+
def __init__(self, *, name: str | None = None,
|
|
52
|
+
options: dict[str, str] | None = None):
|
|
53
|
+
self.name = name
|
|
54
|
+
self.options = options or {}
|
|
55
|
+
|
|
56
|
+
@classmethod
|
|
57
|
+
def load(cls, data):
|
|
58
|
+
return Step(
|
|
59
|
+
name=data.get('name', None),
|
|
60
|
+
options=data.get('options', None),
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
def serialize(self):
|
|
64
|
+
return {
|
|
65
|
+
'name': self.name,
|
|
66
|
+
'options': self.options,
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class Format:
|
|
71
|
+
|
|
72
|
+
DEFAULT_ICON = 'fas fa-file'
|
|
73
|
+
|
|
74
|
+
def __init__(self, *, uuid: str | None = None, name: str | None = None,
|
|
75
|
+
icon: str | None = None):
|
|
76
|
+
self.uuid = uuid
|
|
77
|
+
self.name = name
|
|
78
|
+
self.icon: str = icon or self.DEFAULT_ICON
|
|
79
|
+
self.steps: list[Step] = []
|
|
80
|
+
|
|
81
|
+
@classmethod
|
|
82
|
+
def load(cls, data):
|
|
83
|
+
format_spec = Format(
|
|
84
|
+
uuid=data.get('uuid', None),
|
|
85
|
+
name=data.get('name', None),
|
|
86
|
+
icon=data.get('icon', None),
|
|
87
|
+
)
|
|
88
|
+
for s_data in data.get('steps', []):
|
|
89
|
+
format_spec.steps.append(Step.load(s_data))
|
|
90
|
+
return format_spec
|
|
91
|
+
|
|
92
|
+
def serialize(self):
|
|
93
|
+
return {
|
|
94
|
+
'uuid': self.uuid,
|
|
95
|
+
'name': self.name,
|
|
96
|
+
'icon': self.icon,
|
|
97
|
+
'steps': [step.serialize() for step in self.steps],
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class TDKConfig:
|
|
102
|
+
|
|
103
|
+
DEFAULT_README = 'README.md'
|
|
104
|
+
DEFAULT_FILES = ['*', '!.git/', '!.env']
|
|
105
|
+
|
|
106
|
+
def __init__(self, *, version: str | None = None, readme_file: str | None = None,
|
|
107
|
+
files: list[str] | None = None):
|
|
108
|
+
self.version: str = version or consts.VERSION
|
|
109
|
+
readme_file_str: str = readme_file or self.DEFAULT_README
|
|
110
|
+
self.readme_file: pathlib.Path = pathlib.Path(readme_file_str)
|
|
111
|
+
self.files: list[str] = files or []
|
|
112
|
+
|
|
113
|
+
@classmethod
|
|
114
|
+
def load(cls, data):
|
|
115
|
+
return TDKConfig(
|
|
116
|
+
version=data.get('version', consts.VERSION),
|
|
117
|
+
readme_file=data.get('readmeFile', cls.DEFAULT_README),
|
|
118
|
+
files=data.get('files', cls.DEFAULT_FILES),
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
def use_default_files(self):
|
|
122
|
+
self.files = self.DEFAULT_FILES
|
|
123
|
+
|
|
124
|
+
def serialize(self):
|
|
125
|
+
return {
|
|
126
|
+
'version': self.version,
|
|
127
|
+
'readmeFile': str(self.readme_file),
|
|
128
|
+
'files': self.files,
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class TemplateFile:
|
|
133
|
+
|
|
134
|
+
DEFAULT_CONTENT_TYPE = 'application/octet-stream'
|
|
135
|
+
TEMPLATE_EXTENSIONS = ('.j2', '.jinja', '.jinja2', '.jnj')
|
|
136
|
+
|
|
137
|
+
def __init__(self, *, filename: pathlib.Path,
|
|
138
|
+
remote_id: str | None = None, remote_type: TemplateFileType | None = None,
|
|
139
|
+
content_type: str | None = None, content: bytes = b''):
|
|
140
|
+
self.remote_id = remote_id
|
|
141
|
+
self.filename = filename
|
|
142
|
+
self.content = content
|
|
143
|
+
self.content_type: str = content_type or self.guess_type()
|
|
144
|
+
self.remote_type: TemplateFileType = remote_type or self.guess_template_file_type()
|
|
145
|
+
|
|
146
|
+
def guess_template_file_type(self):
|
|
147
|
+
return TemplateFileType.FILE if self.is_text else TemplateFileType.ASSET
|
|
148
|
+
|
|
149
|
+
def guess_type(self) -> str:
|
|
150
|
+
filename = self.filename.name
|
|
151
|
+
for ext in self.TEMPLATE_EXTENSIONS:
|
|
152
|
+
if filename.endswith(ext):
|
|
153
|
+
return 'text/jinja2'
|
|
154
|
+
guessed_type = mimetypes.guess_type(filename, strict=False)
|
|
155
|
+
if guessed_type is None or guessed_type[0] is None:
|
|
156
|
+
return self.DEFAULT_CONTENT_TYPE
|
|
157
|
+
return guessed_type[0]
|
|
158
|
+
|
|
159
|
+
@property
|
|
160
|
+
def is_text(self):
|
|
161
|
+
if getattr(self, 'remote_type', None) == TemplateFileType.FILE:
|
|
162
|
+
return True
|
|
163
|
+
return self.content_type.startswith('text')
|
|
164
|
+
|
|
165
|
+
@property
|
|
166
|
+
def has_remote_id(self):
|
|
167
|
+
return self.remote_id is not None
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class Template:
|
|
171
|
+
|
|
172
|
+
# pylint: disable-next=too-many-arguments
|
|
173
|
+
def __init__(self, *, template_id=None, organization_id=None, version=None, name=None,
|
|
174
|
+
description=None, readme=None, template_license=None,
|
|
175
|
+
metamodel_version=None, tdk_config=None, loaded_json=None):
|
|
176
|
+
self.template_id: str | None = template_id
|
|
177
|
+
self.organization_id: str | None = organization_id
|
|
178
|
+
self.version: str | None = version
|
|
179
|
+
self.name: str | None = name
|
|
180
|
+
self.description: str | None = description
|
|
181
|
+
self.readme: str | None = readme
|
|
182
|
+
self.license: str | None = template_license
|
|
183
|
+
self.metamodel_version: str = metamodel_version or consts.METAMODEL_VERSION
|
|
184
|
+
self.allowed_packages: list[PackageFilter] = []
|
|
185
|
+
self.formats: list[Format] = []
|
|
186
|
+
self.files: dict[str, TemplateFile] = {}
|
|
187
|
+
self.extras: list[TemplateFile] = []
|
|
188
|
+
self.tdk_config: TDKConfig = tdk_config or TDKConfig()
|
|
189
|
+
self.loaded_json: collections.OrderedDict = loaded_json or collections.OrderedDict()
|
|
190
|
+
|
|
191
|
+
@property
|
|
192
|
+
def id(self) -> str:
|
|
193
|
+
return f'{self.organization_id}:{self.template_id}:{self.version}'
|
|
194
|
+
|
|
195
|
+
def id_with_org(self, organization_id: str) -> str:
|
|
196
|
+
return f'{organization_id}:{self.template_id}:{self.version}'
|
|
197
|
+
|
|
198
|
+
@classmethod
|
|
199
|
+
def _common_load(cls, data):
|
|
200
|
+
if 'id' in data:
|
|
201
|
+
composite_id = data['id'] # type: str
|
|
202
|
+
if composite_id.count(':') != 2:
|
|
203
|
+
raise RuntimeError(f'Invalid template ID: {composite_id}')
|
|
204
|
+
org_id, tmp_id, version = composite_id.split(':')
|
|
205
|
+
else:
|
|
206
|
+
try:
|
|
207
|
+
org_id = data['organizationId']
|
|
208
|
+
tmp_id = data['templateId']
|
|
209
|
+
version = data['version']
|
|
210
|
+
except KeyError as e:
|
|
211
|
+
raise RuntimeError('Cannot retrieve template ID') from e
|
|
212
|
+
template = Template(
|
|
213
|
+
template_id=tmp_id,
|
|
214
|
+
organization_id=org_id,
|
|
215
|
+
version=version,
|
|
216
|
+
name=data.get('name', 'Unknown template'),
|
|
217
|
+
description=data.get('description', ''),
|
|
218
|
+
template_license=data.get('license', 'no-license'),
|
|
219
|
+
metamodel_version=data.get('metamodelVersion', consts.METAMODEL_VERSION),
|
|
220
|
+
readme=data.get('readme', ''),
|
|
221
|
+
)
|
|
222
|
+
for ap_data in data.get('allowedPackages', []):
|
|
223
|
+
template.allowed_packages.append(PackageFilter.load(ap_data))
|
|
224
|
+
for f_data in data.get('formats', []):
|
|
225
|
+
template.formats.append(Format.load(f_data))
|
|
226
|
+
return template
|
|
227
|
+
|
|
228
|
+
@classmethod
|
|
229
|
+
def load_local(cls, data: collections.OrderedDict):
|
|
230
|
+
template = cls._common_load(data)
|
|
231
|
+
if '_tdk' in data:
|
|
232
|
+
template.tdk_config = TDKConfig.load(data['_tdk'])
|
|
233
|
+
template.loaded_json = data
|
|
234
|
+
return template
|
|
235
|
+
|
|
236
|
+
@classmethod
|
|
237
|
+
def load_remote(cls, data: dict):
|
|
238
|
+
return cls._common_load(data)
|
|
239
|
+
|
|
240
|
+
def serialize_local(self) -> collections.OrderedDict:
|
|
241
|
+
self.loaded_json['templateId'] = self.template_id
|
|
242
|
+
self.loaded_json['organizationId'] = self.organization_id
|
|
243
|
+
self.loaded_json['version'] = self.version
|
|
244
|
+
self.loaded_json['name'] = self.name
|
|
245
|
+
self.loaded_json['description'] = self.description
|
|
246
|
+
self.loaded_json['license'] = self.license
|
|
247
|
+
self.loaded_json['metamodelVersion'] = self.metamodel_version
|
|
248
|
+
self.loaded_json['allowedPackages'] = [ap.serialize() for ap in self.allowed_packages]
|
|
249
|
+
self.loaded_json['formats'] = [f.serialize() for f in self.formats]
|
|
250
|
+
self.loaded_json['_tdk'] = self.tdk_config.serialize()
|
|
251
|
+
return self.loaded_json
|
|
252
|
+
|
|
253
|
+
def serialize_remote(self) -> dict[str, typing.Any]:
|
|
254
|
+
return {
|
|
255
|
+
'id': self.id,
|
|
256
|
+
'templateId': self.template_id,
|
|
257
|
+
'organizationId': self.organization_id,
|
|
258
|
+
'version': self.version,
|
|
259
|
+
'name': self.name,
|
|
260
|
+
'description': self.description,
|
|
261
|
+
'license': self.license,
|
|
262
|
+
'metamodelVersion': self.metamodel_version,
|
|
263
|
+
'readme': self.readme,
|
|
264
|
+
'allowedPackages': [ap.serialize() for ap in self.allowed_packages],
|
|
265
|
+
'formats': [f.serialize() for f in self.formats],
|
|
266
|
+
'phase': 'DraftDocumentTemplatePhase',
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
def serialize_for_update(self) -> dict[str, typing.Any]:
|
|
270
|
+
return {
|
|
271
|
+
'templateId': self.template_id,
|
|
272
|
+
'version': self.version,
|
|
273
|
+
'name': self.name,
|
|
274
|
+
'description': self.description,
|
|
275
|
+
'license': self.license,
|
|
276
|
+
'metamodelVersion': self.metamodel_version,
|
|
277
|
+
'readme': self.readme,
|
|
278
|
+
'allowedPackages': [ap.serialize() for ap in self.allowed_packages],
|
|
279
|
+
'formats': [f.serialize() for f in self.formats],
|
|
280
|
+
'phase': 'DraftDocumentTemplatePhase',
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
def serialize_for_create(self, based_on: str | None = None) -> dict[str, typing.Any]:
|
|
284
|
+
return {
|
|
285
|
+
'basedOn': based_on,
|
|
286
|
+
'name': self.name,
|
|
287
|
+
'templateId': self.template_id,
|
|
288
|
+
'version': self.version,
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
def serialize_local_new(self) -> dict[str, typing.Any]:
|
|
292
|
+
return {
|
|
293
|
+
'templateId': self.template_id,
|
|
294
|
+
'organizationId': self.organization_id,
|
|
295
|
+
'version': self.version,
|
|
296
|
+
'name': self.name,
|
|
297
|
+
'description': self.description,
|
|
298
|
+
'license': self.license,
|
|
299
|
+
'metamodelVersion': self.metamodel_version,
|
|
300
|
+
'allowedPackages': [ap.serialize() for ap in self.allowed_packages],
|
|
301
|
+
'formats': [f.serialize() for f in self.formats],
|
|
302
|
+
'_tdk': self.tdk_config.serialize(),
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def _to_ordered_dict(tuples: list[tuple[str, typing.Any]]) -> collections.OrderedDict:
|
|
307
|
+
return collections.OrderedDict(tuples)
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
class TemplateProject:
|
|
311
|
+
|
|
312
|
+
TEMPLATE_FILE = 'template.json'
|
|
313
|
+
DEFAULT_PATTERNS = ['!**/.*', '!**/.*/', '!**/~*', '!**/~*/',
|
|
314
|
+
'!template.json', '!template.zip']
|
|
315
|
+
|
|
316
|
+
json_decoder = json.JSONDecoder(object_pairs_hook=_to_ordered_dict)
|
|
317
|
+
|
|
318
|
+
def __init__(self, template_dir: pathlib.Path, logger: logging.Logger):
|
|
319
|
+
self.template_dir = pathlib.Path(template_dir)
|
|
320
|
+
self.descriptor_path = self.template_dir / self.TEMPLATE_FILE
|
|
321
|
+
self.template: Template | None = None
|
|
322
|
+
self.used_readme: pathlib.Path | None = None
|
|
323
|
+
self._logger = logger
|
|
324
|
+
|
|
325
|
+
@property
|
|
326
|
+
def logger(self) -> logging.Logger:
|
|
327
|
+
return self._logger
|
|
328
|
+
|
|
329
|
+
@property
|
|
330
|
+
def safe_template(self) -> Template:
|
|
331
|
+
if self.template is None:
|
|
332
|
+
raise RuntimeError('Template is not loaded')
|
|
333
|
+
return self.template
|
|
334
|
+
|
|
335
|
+
def load_descriptor(self):
|
|
336
|
+
if not self.descriptor_path.is_file():
|
|
337
|
+
raise RuntimeError(f'Template file does not exist: {self.descriptor_path.as_posix()}')
|
|
338
|
+
try:
|
|
339
|
+
content = self.descriptor_path.read_text(encoding=consts.DEFAULT_ENCODING)
|
|
340
|
+
self.template = Template.load_local(self.json_decoder.decode(content))
|
|
341
|
+
except Exception as e:
|
|
342
|
+
raise RuntimeError(f'Unable to load template using {self.descriptor_path}.') from e
|
|
343
|
+
|
|
344
|
+
def load_readme(self):
|
|
345
|
+
readme = self.safe_template.tdk_config.readme_file
|
|
346
|
+
if readme is not None:
|
|
347
|
+
try:
|
|
348
|
+
self.used_readme = self.template_dir / readme
|
|
349
|
+
readme_text = self.used_readme.read_text(encoding=consts.DEFAULT_ENCODING)
|
|
350
|
+
self.safe_template.readme = readme_text
|
|
351
|
+
except Exception as e:
|
|
352
|
+
raise RuntimeWarning(f'README file "{readme}" cannot be loaded: {e}') from e
|
|
353
|
+
|
|
354
|
+
def load_file(self, filepath: pathlib.Path) -> TemplateFile:
|
|
355
|
+
try:
|
|
356
|
+
if filepath.is_absolute():
|
|
357
|
+
filepath = filepath.relative_to(self.template_dir)
|
|
358
|
+
template_file = TemplateFile(filename=filepath)
|
|
359
|
+
template_file.content = (self.template_dir / filepath).read_bytes()
|
|
360
|
+
self.safe_template.files[filepath.as_posix()] = template_file
|
|
361
|
+
return template_file
|
|
362
|
+
except Exception as e:
|
|
363
|
+
raise RuntimeWarning(f'Failed to load template file {filepath}: {e}') from e
|
|
364
|
+
|
|
365
|
+
def load_files(self):
|
|
366
|
+
self.safe_template.files.clear()
|
|
367
|
+
for f in self.list_files():
|
|
368
|
+
self.load_file(f)
|
|
369
|
+
|
|
370
|
+
@property
|
|
371
|
+
def files_pathspec(self) -> pathspec.PathSpec:
|
|
372
|
+
patterns = self.safe_template.tdk_config.files + self.DEFAULT_PATTERNS
|
|
373
|
+
return pathspec.PathSpec.from_lines('gitignore', patterns)
|
|
374
|
+
|
|
375
|
+
def list_files(self) -> list[pathlib.Path]:
|
|
376
|
+
files = (pathlib.Path(p)
|
|
377
|
+
for p in self.files_pathspec.match_tree_files(str(self.template_dir)))
|
|
378
|
+
if self.used_readme is not None:
|
|
379
|
+
return [p for p in files if p != self.used_readme.relative_to(self.template_dir)]
|
|
380
|
+
return list(files)
|
|
381
|
+
|
|
382
|
+
def _relative_paths_eq(self, filepath1: pathlib.Path | None,
|
|
383
|
+
filepath2: pathlib.Path | None) -> bool:
|
|
384
|
+
if filepath1 is None or filepath2 is None:
|
|
385
|
+
return False
|
|
386
|
+
return filepath1.relative_to(self.template_dir) == filepath2.relative_to(self.template_dir)
|
|
387
|
+
|
|
388
|
+
def is_template_file(self, filepath: pathlib.Path, include_descriptor: bool = False,
|
|
389
|
+
include_readme: bool = False):
|
|
390
|
+
if include_readme and self._relative_paths_eq(filepath, self.used_readme):
|
|
391
|
+
return True
|
|
392
|
+
if include_descriptor and self._relative_paths_eq(filepath, self.descriptor_path):
|
|
393
|
+
return True
|
|
394
|
+
return self.files_pathspec.match_file(filepath.relative_to(self.template_dir))
|
|
395
|
+
|
|
396
|
+
def load(self):
|
|
397
|
+
self.load_descriptor()
|
|
398
|
+
self.load_readme()
|
|
399
|
+
self.load_files()
|
|
400
|
+
|
|
401
|
+
def remove_template_file(self, filepath: pathlib.Path):
|
|
402
|
+
if filepath.is_absolute():
|
|
403
|
+
filepath = filepath.relative_to(self.template_dir)
|
|
404
|
+
filename = filepath.as_posix()
|
|
405
|
+
if filename in self.safe_template.files:
|
|
406
|
+
del self.safe_template.files[filename]
|
|
407
|
+
|
|
408
|
+
def update_template_file(self, template_file: TemplateFile):
|
|
409
|
+
filename = template_file.filename.as_posix()
|
|
410
|
+
self.safe_template.files[filename] = template_file
|
|
411
|
+
|
|
412
|
+
def get_template_file(self, filepath: pathlib.Path) -> TemplateFile | None:
|
|
413
|
+
if filepath.is_absolute():
|
|
414
|
+
filepath = filepath.relative_to(self.template_dir)
|
|
415
|
+
return self.safe_template.files.get(filepath.as_posix(), None)
|
|
416
|
+
|
|
417
|
+
def _write_file(self, filepath: pathlib.Path, contents: bytes, force: bool):
|
|
418
|
+
if filepath.exists() and not force:
|
|
419
|
+
self.logger.warning('Skipping file %s (not forced)', filepath.as_posix())
|
|
420
|
+
return
|
|
421
|
+
try:
|
|
422
|
+
filepath.parent.mkdir(parents=True, exist_ok=True)
|
|
423
|
+
filepath.write_bytes(contents)
|
|
424
|
+
self.logger.debug('Stored file %s', filepath.as_posix())
|
|
425
|
+
except Exception as e:
|
|
426
|
+
self.logger.error('Unable to write file %s: %s', filepath.as_posix(), e)
|
|
427
|
+
|
|
428
|
+
def store_descriptor(self, force: bool):
|
|
429
|
+
self._write_file(
|
|
430
|
+
filepath=self.descriptor_path,
|
|
431
|
+
contents=json.dumps(
|
|
432
|
+
obj=self.safe_template.serialize_local(),
|
|
433
|
+
indent=4,
|
|
434
|
+
).encode(encoding=consts.DEFAULT_ENCODING),
|
|
435
|
+
force=force,
|
|
436
|
+
)
|
|
437
|
+
|
|
438
|
+
def store_readme(self, force: bool):
|
|
439
|
+
if self.safe_template.tdk_config.readme_file is None:
|
|
440
|
+
self.logger.warning('No README file specified for the template')
|
|
441
|
+
return
|
|
442
|
+
if self.safe_template.readme is None:
|
|
443
|
+
self.logger.warning('No README content specified for the template')
|
|
444
|
+
return
|
|
445
|
+
self._write_file(
|
|
446
|
+
filepath=self.template_dir / self.safe_template.tdk_config.readme_file,
|
|
447
|
+
contents=self.safe_template.readme.encode(encoding=consts.DEFAULT_ENCODING),
|
|
448
|
+
force=force,
|
|
449
|
+
)
|
|
450
|
+
|
|
451
|
+
def store_files(self, force: bool):
|
|
452
|
+
for template_file in self.safe_template.files.values():
|
|
453
|
+
self._write_file(
|
|
454
|
+
filepath=self.template_dir / template_file.filename,
|
|
455
|
+
contents=template_file.content,
|
|
456
|
+
force=force,
|
|
457
|
+
)
|
|
458
|
+
|
|
459
|
+
def store(self, force: bool):
|
|
460
|
+
self.logger.debug('Ensuring directory %s', self.template_dir.as_posix())
|
|
461
|
+
self.template_dir.mkdir(parents=True, exist_ok=True)
|
|
462
|
+
self.logger.debug('Storing %s descriptor', self.TEMPLATE_FILE)
|
|
463
|
+
self.store_descriptor(force=force)
|
|
464
|
+
self.logger.debug('Storing README file')
|
|
465
|
+
self.store_readme(force=force)
|
|
466
|
+
self.logger.debug('Storing template files')
|
|
467
|
+
self.store_files(force=force)
|
|
468
|
+
self.logger.debug('Storing finished')
|