dsw-tdk 4.21.0__tar.gz → 4.22.1__tar.gz
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-4.21.0 → dsw_tdk-4.22.1}/CHANGELOG.md +18 -0
- {dsw_tdk-4.21.0/dsw_tdk.egg-info → dsw_tdk-4.22.1}/PKG-INFO +1 -1
- {dsw_tdk-4.21.0 → dsw_tdk-4.22.1}/dsw/tdk/api_client.py +35 -35
- {dsw_tdk-4.21.0 → dsw_tdk-4.22.1}/dsw/tdk/build_info.py +4 -4
- {dsw_tdk-4.21.0 → dsw_tdk-4.22.1}/dsw/tdk/cli.py +211 -54
- dsw_tdk-4.22.1/dsw/tdk/config.py +154 -0
- {dsw_tdk-4.21.0 → dsw_tdk-4.22.1}/dsw/tdk/consts.py +4 -2
- {dsw_tdk-4.21.0 → dsw_tdk-4.22.1}/dsw/tdk/core.py +127 -114
- {dsw_tdk-4.21.0 → dsw_tdk-4.22.1}/dsw/tdk/model.py +13 -13
- {dsw_tdk-4.21.0 → dsw_tdk-4.22.1}/dsw/tdk/templates/env.j2 +1 -0
- {dsw_tdk-4.21.0 → dsw_tdk-4.22.1}/dsw/tdk/validation.py +33 -1
- {dsw_tdk-4.21.0 → dsw_tdk-4.22.1/dsw_tdk.egg-info}/PKG-INFO +1 -1
- {dsw_tdk-4.21.0 → dsw_tdk-4.22.1}/dsw_tdk.egg-info/SOURCES.txt +1 -0
- {dsw_tdk-4.21.0 → dsw_tdk-4.22.1}/pyproject.toml +1 -1
- {dsw_tdk-4.21.0 → dsw_tdk-4.22.1}/tests/test_cmd_dot-env.py +12 -4
- {dsw_tdk-4.21.0 → dsw_tdk-4.22.1}/tests/test_cmd_get.py +9 -9
- {dsw_tdk-4.21.0 → dsw_tdk-4.22.1}/tests/test_cmd_list.py +8 -8
- {dsw_tdk-4.21.0 → dsw_tdk-4.22.1}/tests/test_cmd_new.py +2 -0
- {dsw_tdk-4.21.0 → dsw_tdk-4.22.1}/tests/test_cmd_put.py +6 -5
- {dsw_tdk-4.21.0 → dsw_tdk-4.22.1}/tests/test_cmd_verify.py +1 -1
- {dsw_tdk-4.21.0 → dsw_tdk-4.22.1}/LICENSE +0 -0
- {dsw_tdk-4.21.0 → dsw_tdk-4.22.1}/MANIFEST.in +0 -0
- {dsw_tdk-4.21.0 → dsw_tdk-4.22.1}/README.md +0 -0
- {dsw_tdk-4.21.0 → dsw_tdk-4.22.1}/dsw/tdk/__init__.py +0 -0
- {dsw_tdk-4.21.0 → dsw_tdk-4.22.1}/dsw/tdk/__main__.py +0 -0
- {dsw_tdk-4.21.0 → dsw_tdk-4.22.1}/dsw/tdk/templates/LICENSE.j2 +0 -0
- {dsw_tdk-4.21.0 → dsw_tdk-4.22.1}/dsw/tdk/templates/README.md.j2 +0 -0
- {dsw_tdk-4.21.0 → dsw_tdk-4.22.1}/dsw/tdk/templates/starter.j2 +0 -0
- {dsw_tdk-4.21.0 → dsw_tdk-4.22.1}/dsw/tdk/utils.py +0 -0
- {dsw_tdk-4.21.0 → dsw_tdk-4.22.1}/dsw_tdk.egg-info/dependency_links.txt +0 -0
- {dsw_tdk-4.21.0 → dsw_tdk-4.22.1}/dsw_tdk.egg-info/entry_points.txt +0 -0
- {dsw_tdk-4.21.0 → dsw_tdk-4.22.1}/dsw_tdk.egg-info/not-zip-safe +0 -0
- {dsw_tdk-4.21.0 → dsw_tdk-4.22.1}/dsw_tdk.egg-info/requires.txt +0 -0
- {dsw_tdk-4.21.0 → dsw_tdk-4.22.1}/dsw_tdk.egg-info/top_level.txt +0 -0
- {dsw_tdk-4.21.0 → dsw_tdk-4.22.1}/setup.cfg +0 -0
- {dsw_tdk-4.21.0 → dsw_tdk-4.22.1}/setup.py +0 -0
- {dsw_tdk-4.21.0 → dsw_tdk-4.22.1}/tests/test_basic.py +0 -0
- {dsw_tdk-4.21.0 → dsw_tdk-4.22.1}/tests/test_cmd_package.py +0 -0
- {dsw_tdk-4.21.0 → dsw_tdk-4.22.1}/tests/test_cmd_unpackage.py +0 -0
|
@@ -8,6 +8,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
10
|
|
|
11
|
+
## [4.22.1]
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- Fixed checking metamodel and API version in TDK
|
|
16
|
+
|
|
17
|
+
## [4.22.0]
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- Support shared TDK config across document template projects
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- Switch to use semver for document template metamodel versioning
|
|
26
|
+
|
|
11
27
|
## [4.21.0]
|
|
12
28
|
|
|
13
29
|
Released for version consistency with other DSW tools.
|
|
@@ -589,3 +605,5 @@ Initial DSW Template Development Kit (versioned as part of the [DSW platform](ht
|
|
|
589
605
|
[4.20.0]: /../../tree/v4.20.0
|
|
590
606
|
[4.20.1]: /../../tree/v4.20.1
|
|
591
607
|
[4.21.0]: /../../tree/v4.21.0
|
|
608
|
+
[4.22.0]: /../../tree/v4.22.0
|
|
609
|
+
[4.22.1]: /../../tree/v4.22.1
|
|
@@ -9,7 +9,7 @@ from .consts import DEFAULT_ENCODING, APP, VERSION
|
|
|
9
9
|
from .model import Template, TemplateFile, TemplateFileType
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
class
|
|
12
|
+
class WizardCommunicationError(RuntimeError):
|
|
13
13
|
|
|
14
14
|
def __init__(self, reason: str, message: str):
|
|
15
15
|
"""
|
|
@@ -28,31 +28,31 @@ def handle_client_errors(func):
|
|
|
28
28
|
async def handled_client_call(job, *args, **kwargs):
|
|
29
29
|
try:
|
|
30
30
|
return await func(job, *args, **kwargs)
|
|
31
|
-
except
|
|
31
|
+
except WizardCommunicationError as e:
|
|
32
32
|
# Already DSWCommunicationError (re-raise)
|
|
33
33
|
raise e
|
|
34
34
|
except aiohttp.client_exceptions.ContentTypeError as e:
|
|
35
|
-
raise
|
|
35
|
+
raise WizardCommunicationError(
|
|
36
36
|
reason='Unexpected response type',
|
|
37
37
|
message=e.message
|
|
38
38
|
) from e
|
|
39
39
|
except aiohttp.client_exceptions.ClientResponseError as e:
|
|
40
|
-
raise
|
|
40
|
+
raise WizardCommunicationError(
|
|
41
41
|
reason='Error response status',
|
|
42
42
|
message=f'Server responded with error HTTP status {e.status}: {e.message}'
|
|
43
43
|
) from e
|
|
44
44
|
except aiohttp.client_exceptions.InvalidURL as e:
|
|
45
|
-
raise
|
|
45
|
+
raise WizardCommunicationError(
|
|
46
46
|
reason='Invalid URL',
|
|
47
47
|
message=f'Provided API URL seems invalid: {e.url}'
|
|
48
48
|
) from e
|
|
49
49
|
except aiohttp.client_exceptions.ClientConnectorError as e:
|
|
50
|
-
raise
|
|
50
|
+
raise WizardCommunicationError(
|
|
51
51
|
reason='Server unreachable',
|
|
52
52
|
message=f'Desired server is not reachable (errno {e.os_error.errno})'
|
|
53
53
|
) from e
|
|
54
54
|
except Exception as e:
|
|
55
|
-
raise
|
|
55
|
+
raise WizardCommunicationError(
|
|
56
56
|
reason='Communication error',
|
|
57
57
|
message=f'Communication with server failed ({e})'
|
|
58
58
|
) from e
|
|
@@ -60,7 +60,7 @@ def handle_client_errors(func):
|
|
|
60
60
|
|
|
61
61
|
|
|
62
62
|
# pylint: disable-next=too-many-public-methods
|
|
63
|
-
class
|
|
63
|
+
class WizardAPIClient:
|
|
64
64
|
|
|
65
65
|
def _headers(self, extra=None):
|
|
66
66
|
headers = {
|
|
@@ -75,7 +75,7 @@ class DSWAPIClient:
|
|
|
75
75
|
def _check_status(r: aiohttp.ClientResponse, expected_status):
|
|
76
76
|
r.raise_for_status()
|
|
77
77
|
if r.status != expected_status:
|
|
78
|
-
raise
|
|
78
|
+
raise WizardCommunicationError(
|
|
79
79
|
reason='Unexpected response status',
|
|
80
80
|
message=f'Server responded with unexpected HTTP status {r.status}: '
|
|
81
81
|
f'{r.reason} (expecting {expected_status})'
|
|
@@ -160,7 +160,7 @@ class DSWAPIClient:
|
|
|
160
160
|
body = await self._post_json('/tokens', json=req)
|
|
161
161
|
token_value = body.get('token', None)
|
|
162
162
|
if not isinstance(token_value, str):
|
|
163
|
-
raise
|
|
163
|
+
raise WizardCommunicationError(
|
|
164
164
|
reason='Invalid response',
|
|
165
165
|
message='Server did not return a valid token'
|
|
166
166
|
)
|
|
@@ -226,8 +226,8 @@ class DSWAPIClient:
|
|
|
226
226
|
result = []
|
|
227
227
|
for file_body in body:
|
|
228
228
|
file_id = file_body['uuid']
|
|
229
|
-
|
|
230
|
-
result.append(
|
|
229
|
+
file = await self.get_template_draft_file(remote_id, file_id)
|
|
230
|
+
result.append(file)
|
|
231
231
|
return result
|
|
232
232
|
|
|
233
233
|
@handle_client_errors
|
|
@@ -276,23 +276,23 @@ class DSWAPIClient:
|
|
|
276
276
|
return _load_remote_template(body)
|
|
277
277
|
|
|
278
278
|
@handle_client_errors
|
|
279
|
-
async def post_template_draft_file(self, remote_id: str,
|
|
279
|
+
async def post_template_draft_file(self, remote_id: str, file: TemplateFile):
|
|
280
280
|
data = await self._post_json(
|
|
281
281
|
endpoint=f'/document-template-drafts/{remote_id}/files',
|
|
282
282
|
json={
|
|
283
|
-
'fileName':
|
|
284
|
-
'content':
|
|
283
|
+
'fileName': file.filename.as_posix(),
|
|
284
|
+
'content': file.content.decode(DEFAULT_ENCODING)
|
|
285
285
|
}
|
|
286
286
|
)
|
|
287
287
|
return _load_remote_file(data)
|
|
288
288
|
|
|
289
289
|
@handle_client_errors
|
|
290
|
-
async def put_template_draft_file_content(self, remote_id: str,
|
|
290
|
+
async def put_template_draft_file_content(self, remote_id: str, file: TemplateFile):
|
|
291
291
|
self.session.headers.update(self._headers())
|
|
292
292
|
async with self.session.put(
|
|
293
293
|
f'{self.api_url}/document-template-drafts/{remote_id}'
|
|
294
|
-
f'/files/{
|
|
295
|
-
data=
|
|
294
|
+
f'/files/{file.remote_id}/content',
|
|
295
|
+
data=file.content,
|
|
296
296
|
headers={'Content-Type': 'text/plain;charset=UTF-8'},
|
|
297
297
|
) as r:
|
|
298
298
|
self._check_status(r, expected_status=200)
|
|
@@ -300,17 +300,17 @@ class DSWAPIClient:
|
|
|
300
300
|
return _load_remote_file(body)
|
|
301
301
|
|
|
302
302
|
@handle_client_errors
|
|
303
|
-
async def post_template_draft_asset(self, remote_id: str,
|
|
303
|
+
async def post_template_draft_asset(self, remote_id: str, file: TemplateFile):
|
|
304
304
|
data = aiohttp.FormData()
|
|
305
305
|
data.add_field(
|
|
306
306
|
name='file',
|
|
307
|
-
value=
|
|
308
|
-
filename=
|
|
309
|
-
content_type=
|
|
307
|
+
value=file.content,
|
|
308
|
+
filename=file.filename.as_posix(),
|
|
309
|
+
content_type=file.content_type,
|
|
310
310
|
)
|
|
311
311
|
data.add_field(
|
|
312
312
|
name='fileName',
|
|
313
|
-
value=
|
|
313
|
+
value=file.filename.as_posix(),
|
|
314
314
|
)
|
|
315
315
|
async with self.session.post(
|
|
316
316
|
f'{self.api_url}/document-template-drafts/{remote_id}/assets',
|
|
@@ -319,30 +319,30 @@ class DSWAPIClient:
|
|
|
319
319
|
) as r:
|
|
320
320
|
self._check_status(r, expected_status=201)
|
|
321
321
|
body = await r.json()
|
|
322
|
-
return _load_remote_asset(body,
|
|
322
|
+
return _load_remote_asset(body, file.content)
|
|
323
323
|
|
|
324
324
|
@handle_client_errors
|
|
325
|
-
async def put_template_draft_asset_content(self, remote_id: str,
|
|
325
|
+
async def put_template_draft_asset_content(self, remote_id: str, file: TemplateFile):
|
|
326
326
|
data = aiohttp.FormData()
|
|
327
327
|
data.add_field(
|
|
328
328
|
name='file',
|
|
329
|
-
value=
|
|
330
|
-
filename=
|
|
331
|
-
content_type=
|
|
329
|
+
value=file.content,
|
|
330
|
+
filename=file.filename.as_posix(),
|
|
331
|
+
content_type=file.content_type,
|
|
332
332
|
)
|
|
333
333
|
data.add_field(
|
|
334
334
|
name='fileName',
|
|
335
|
-
value=
|
|
335
|
+
value=file.filename.as_posix(),
|
|
336
336
|
)
|
|
337
337
|
async with self.session.put(
|
|
338
338
|
f'{self.api_url}/document-template-drafts/{remote_id}'
|
|
339
|
-
f'/assets/{
|
|
339
|
+
f'/assets/{file.remote_id}/content',
|
|
340
340
|
data=data,
|
|
341
341
|
headers=self._headers()
|
|
342
342
|
) as r:
|
|
343
343
|
self._check_status(r, expected_status=200)
|
|
344
344
|
body = await r.json()
|
|
345
|
-
return _load_remote_asset(body,
|
|
345
|
+
return _load_remote_asset(body, file.content)
|
|
346
346
|
|
|
347
347
|
@handle_client_errors
|
|
348
348
|
async def delete_template_draft(self, remote_id: str) -> bool:
|
|
@@ -374,25 +374,25 @@ class DSWAPIClient:
|
|
|
374
374
|
def _load_remote_file(data: dict) -> TemplateFile:
|
|
375
375
|
content: str = data.get('content', '')
|
|
376
376
|
filename: str = str(data.get('fileName', ''))
|
|
377
|
-
|
|
377
|
+
file = TemplateFile(
|
|
378
378
|
remote_id=data.get('uuid', None),
|
|
379
379
|
remote_type=TemplateFileType.FILE,
|
|
380
380
|
filename=pathlib.Path(urllib.parse.unquote(filename)),
|
|
381
381
|
content=content.encode(encoding=DEFAULT_ENCODING),
|
|
382
382
|
)
|
|
383
|
-
return
|
|
383
|
+
return file
|
|
384
384
|
|
|
385
385
|
|
|
386
386
|
def _load_remote_asset(data: dict, content: bytes) -> TemplateFile:
|
|
387
387
|
filename = str(data.get('fileName', ''))
|
|
388
|
-
|
|
388
|
+
asset = TemplateFile(
|
|
389
389
|
remote_id=data.get('uuid', None),
|
|
390
390
|
remote_type=TemplateFileType.ASSET,
|
|
391
391
|
filename=pathlib.Path(urllib.parse.unquote(filename)),
|
|
392
392
|
content_type=data.get('contentType', None),
|
|
393
393
|
content=content,
|
|
394
394
|
)
|
|
395
|
-
return
|
|
395
|
+
return asset
|
|
396
396
|
|
|
397
397
|
|
|
398
398
|
def _load_remote_template(data: dict) -> Template:
|
|
@@ -9,9 +9,9 @@ BuildInfo = namedtuple(
|
|
|
9
9
|
)
|
|
10
10
|
|
|
11
11
|
BUILD_INFO = BuildInfo(
|
|
12
|
-
version='v4.
|
|
13
|
-
built_at='2025-
|
|
14
|
-
sha='
|
|
12
|
+
version='v4.22.1~13699ca',
|
|
13
|
+
built_at='2025-09-04 06:55:59Z',
|
|
14
|
+
sha='13699ca0f74ff51a24bd76e242a89ef434b74658',
|
|
15
15
|
branch='HEAD',
|
|
16
|
-
tag='v4.
|
|
16
|
+
tag='v4.22.1',
|
|
17
17
|
)
|
|
@@ -8,16 +8,16 @@ import signal
|
|
|
8
8
|
import sys
|
|
9
9
|
|
|
10
10
|
import click
|
|
11
|
-
import dotenv
|
|
12
11
|
import humanize
|
|
13
12
|
import slugify
|
|
14
13
|
import watchfiles
|
|
15
14
|
|
|
16
|
-
from .api_client import
|
|
15
|
+
from .api_client import WizardCommunicationError
|
|
16
|
+
from .config import CONFIG
|
|
17
|
+
from .consts import VERSION, DEFAULT_LIST_FORMAT, DEFAULT_ENCODING
|
|
17
18
|
from .core import TDKCore, TDKProcessingError
|
|
18
|
-
from .consts import VERSION, DEFAULT_LIST_FORMAT
|
|
19
19
|
from .model import Template
|
|
20
|
-
from .utils import TemplateBuilder, FormatSpec, safe_utf8
|
|
20
|
+
from .utils import TemplateBuilder, FormatSpec, safe_utf8, create_dot_env
|
|
21
21
|
from .validation import ValidationError
|
|
22
22
|
|
|
23
23
|
CURRENT_DIR = pathlib.Path.cwd()
|
|
@@ -89,19 +89,29 @@ def print_template_info(template: Template):
|
|
|
89
89
|
for format_spec in template.formats:
|
|
90
90
|
click.echo(f' - {format_spec.name}')
|
|
91
91
|
click.echo('Files:')
|
|
92
|
-
for
|
|
93
|
-
filesize = humanize.naturalsize(len(
|
|
94
|
-
click.echo(f' - {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
92
|
+
for template_file in template.files.values():
|
|
93
|
+
filesize = humanize.naturalsize(len(template_file.content))
|
|
94
|
+
click.echo(f' - {template_file.filename.as_posix()} [{filesize}]')
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def ensure_api_config(api_url: str | None, api_key: str | None):
|
|
98
|
+
if api_url is not None:
|
|
99
|
+
CONFIG.use_local_env()
|
|
100
|
+
CONFIG.set_api_url(api_url)
|
|
101
|
+
if not CONFIG.has_api_url:
|
|
102
|
+
CONFIG.set_api_url(
|
|
103
|
+
api_url=click.prompt('API URL'),
|
|
104
|
+
)
|
|
105
|
+
if api_key is not None:
|
|
106
|
+
CONFIG.use_local_env()
|
|
107
|
+
CONFIG.set_api_key(api_key)
|
|
108
|
+
if not CONFIG.has_api_key:
|
|
109
|
+
CONFIG.set_api_key(
|
|
110
|
+
api_key=click.prompt('API Key', hide_input=True),
|
|
111
|
+
)
|
|
112
|
+
if not CONFIG.has_api_url or not CONFIG.has_api_key:
|
|
113
|
+
ClickPrinter.error('API URL and API Key are required to proceed.')
|
|
114
|
+
sys.exit(1)
|
|
105
115
|
|
|
106
116
|
|
|
107
117
|
class ClickLogger(logging.Logger):
|
|
@@ -244,18 +254,38 @@ def dir_from_id(template_id: str) -> pathlib.Path:
|
|
|
244
254
|
|
|
245
255
|
|
|
246
256
|
@click.group(cls=AliasedGroup)
|
|
247
|
-
@click.option('-
|
|
257
|
+
@click.option('-d', '--dot-env', default='.env', required=False,
|
|
248
258
|
show_default=True, type=click.Path(file_okay=True, dir_okay=False),
|
|
249
|
-
help='
|
|
259
|
+
help='File path to dot-env file with environment variables.')
|
|
260
|
+
@click.option('-e', '--environment', default=None, required=False,
|
|
261
|
+
help='Configuration environment name.')
|
|
262
|
+
@click.option('--no-dot-env', is_flag=True, default=False,
|
|
263
|
+
help='Do not load .env file, use only environment variables.')
|
|
264
|
+
@click.option('--no-config', is_flag=True, default=False,
|
|
265
|
+
help='Do not load shared configuration, use only environment variables.')
|
|
250
266
|
@click.option('-q', '--quiet', is_flag=True,
|
|
251
267
|
help='Hide additional information logs.')
|
|
252
268
|
@click.option('--debug', is_flag=True,
|
|
253
269
|
help='Enable debug logging.')
|
|
254
270
|
@click.version_option(version=VERSION)
|
|
255
271
|
@click.pass_context
|
|
256
|
-
def main(ctx, quiet, debug, dot_env):
|
|
257
|
-
if
|
|
258
|
-
|
|
272
|
+
def main(ctx, quiet, debug, dot_env, environment, no_dot_env, no_config):
|
|
273
|
+
if not no_config:
|
|
274
|
+
try:
|
|
275
|
+
CONFIG.load_home_config()
|
|
276
|
+
except Exception as e:
|
|
277
|
+
ClickPrinter.warning('Failed to load shared configuration')
|
|
278
|
+
ClickPrinter.warning(f'> {e}')
|
|
279
|
+
if not no_dot_env and dot_env is not None:
|
|
280
|
+
dot_env_path = pathlib.Path(dot_env)
|
|
281
|
+
if dot_env_path.exists():
|
|
282
|
+
CONFIG.load_dotenv(path=dot_env_path)
|
|
283
|
+
try:
|
|
284
|
+
if environment is not None:
|
|
285
|
+
CONFIG.switch_current_env(environment)
|
|
286
|
+
except Exception as e:
|
|
287
|
+
ClickPrinter.warning('Failed to set config environment')
|
|
288
|
+
ClickPrinter.warning(f'> {e}')
|
|
259
289
|
ctx.ensure_object(CLIContext)
|
|
260
290
|
ctx.obj.dot_env_file = dot_env
|
|
261
291
|
if quiet:
|
|
@@ -292,13 +322,13 @@ def new_template(ctx, template_dir, force):
|
|
|
292
322
|
@click.argument('TEMPLATE-ID')
|
|
293
323
|
@click.argument('TEMPLATE-DIR', type=NEW_DIR_TYPE, default=None, required=False)
|
|
294
324
|
@click.option('-u', '--api-url', metavar='API-URL', envvar='DSW_API_URL',
|
|
295
|
-
|
|
325
|
+
help='URL of Wizard server API.')
|
|
296
326
|
@click.option('-k', '--api-key', metavar='API-KEY', envvar='DSW_API_KEY',
|
|
297
|
-
|
|
298
|
-
hide_input=True)
|
|
327
|
+
help='API key for Wizard instance.')
|
|
299
328
|
@click.option('-f', '--force', is_flag=True, help='Overwrite any existing files.')
|
|
300
329
|
@click.pass_context
|
|
301
|
-
def get_template(ctx,
|
|
330
|
+
def get_template(ctx, template_id, template_dir, api_url, api_key, force):
|
|
331
|
+
ensure_api_config(api_url, api_key)
|
|
302
332
|
template_dir = pathlib.Path(template_dir or dir_from_id(template_id))
|
|
303
333
|
|
|
304
334
|
async def main_routine():
|
|
@@ -306,7 +336,10 @@ def get_template(ctx, api_url, template_id, template_dir, api_key, force):
|
|
|
306
336
|
template_type = 'unknown'
|
|
307
337
|
zip_data = None
|
|
308
338
|
try:
|
|
309
|
-
await tdk.init_client(
|
|
339
|
+
await tdk.init_client(
|
|
340
|
+
api_url=CONFIG.env.api_url,
|
|
341
|
+
api_key=CONFIG.env.api_key,
|
|
342
|
+
)
|
|
310
343
|
try:
|
|
311
344
|
await tdk.load_remote(template_id=template_id)
|
|
312
345
|
template_type = 'draft'
|
|
@@ -314,7 +347,7 @@ def get_template(ctx, api_url, template_id, template_dir, api_key, force):
|
|
|
314
347
|
zip_data = await tdk.download_bundle(template_id=template_id)
|
|
315
348
|
template_type = 'bundle'
|
|
316
349
|
await tdk.safe_client.close()
|
|
317
|
-
except
|
|
350
|
+
except WizardCommunicationError as e:
|
|
318
351
|
ClickPrinter.error('Could not get template:', bold=True)
|
|
319
352
|
ClickPrinter.error(f'> {e.reason}\n> {e.message}')
|
|
320
353
|
await tdk.safe_client.close()
|
|
@@ -352,16 +385,16 @@ def get_template(ctx, api_url, template_id, template_dir, api_key, force):
|
|
|
352
385
|
@main.command(help='Upload template to Wizard.', name='put')
|
|
353
386
|
@click.argument('TEMPLATE-DIR', type=DIR_TYPE, default=CURRENT_DIR, required=False)
|
|
354
387
|
@click.option('-u', '--api-url', metavar='API-URL', envvar='DSW_API_URL',
|
|
355
|
-
|
|
388
|
+
help='URL of Wizard server API.')
|
|
356
389
|
@click.option('-k', '--api-key', metavar='API-KEY', envvar='DSW_API_KEY',
|
|
357
|
-
|
|
358
|
-
hide_input=True)
|
|
390
|
+
help='API key for Wizard instance.')
|
|
359
391
|
@click.option('-f', '--force', is_flag=True,
|
|
360
392
|
help='Delete template if already exists.')
|
|
361
393
|
@click.option('-w', '--watch', is_flag=True,
|
|
362
394
|
help='Enter watch mode to continually upload changes.')
|
|
363
395
|
@click.pass_context
|
|
364
|
-
def put_template(ctx,
|
|
396
|
+
def put_template(ctx, template_dir, api_url, api_key, force, watch):
|
|
397
|
+
ensure_api_config(api_url, api_key)
|
|
365
398
|
tdk = TDKCore(logger=ctx.obj.logger)
|
|
366
399
|
stop_event = asyncio.Event()
|
|
367
400
|
|
|
@@ -379,10 +412,13 @@ def put_template(ctx, api_url, template_dir, api_key, force, watch):
|
|
|
379
412
|
async def main_routine():
|
|
380
413
|
load_local(tdk, template_dir)
|
|
381
414
|
try:
|
|
382
|
-
await tdk.init_client(
|
|
415
|
+
await tdk.init_client(
|
|
416
|
+
api_url=CONFIG.env.api_url,
|
|
417
|
+
api_key=CONFIG.env.api_key,
|
|
418
|
+
)
|
|
383
419
|
await tdk.store_remote(force=force)
|
|
384
420
|
ClickPrinter.success(f'Template {tdk.safe_project.safe_template.id} '
|
|
385
|
-
f'uploaded to {api_url}')
|
|
421
|
+
f'uploaded to {CONFIG.env.api_url}')
|
|
386
422
|
|
|
387
423
|
if watch:
|
|
388
424
|
ClickPrinter.watch('Entering watch mode... (press Ctrl+C to abort)')
|
|
@@ -394,7 +430,7 @@ def put_template(ctx, api_url, template_dir, api_key, force, watch):
|
|
|
394
430
|
ClickPrinter.error(f'> {e.message}\n> {e.hint}')
|
|
395
431
|
await tdk.safe_client.safe_close()
|
|
396
432
|
sys.exit(1)
|
|
397
|
-
except
|
|
433
|
+
except WizardCommunicationError as e:
|
|
398
434
|
ClickPrinter.failure('Could not upload template')
|
|
399
435
|
ClickPrinter.error(f'> {e.reason}\n> {e.message}')
|
|
400
436
|
ClickPrinter.error('> Probably incorrect API URL, metamodel version, '
|
|
@@ -404,9 +440,9 @@ def put_template(ctx, api_url, template_dir, api_key, force, watch):
|
|
|
404
440
|
sys.exit(1)
|
|
405
441
|
|
|
406
442
|
# pylint: disable-next=unused-argument
|
|
407
|
-
def set_stop_event(
|
|
408
|
-
|
|
409
|
-
ClickPrinter.warning(f'Got {
|
|
443
|
+
def set_stop_event(signal_num, frame):
|
|
444
|
+
signal_name = signal.Signals(signal_num).name
|
|
445
|
+
ClickPrinter.warning(f'Got {signal_name}, finishing... Bye!')
|
|
410
446
|
stop_event.set()
|
|
411
447
|
|
|
412
448
|
signal.signal(signal.SIGINT, set_stop_event)
|
|
@@ -460,10 +496,9 @@ def extract_package(ctx, template_package, output, force: bool):
|
|
|
460
496
|
|
|
461
497
|
@main.command(help='List templates from Wizard via API.', name='list')
|
|
462
498
|
@click.option('-u', '--api-url', metavar='API-URL', envvar='DSW_API_URL',
|
|
463
|
-
|
|
499
|
+
help='URL of Wizard server API.')
|
|
464
500
|
@click.option('-k', '--api-key', metavar='API-KEY', envvar='DSW_API_KEY',
|
|
465
|
-
|
|
466
|
-
hide_input=True)
|
|
501
|
+
help='API key for Wizard instance.')
|
|
467
502
|
@click.option('--output-format', default=DEFAULT_LIST_FORMAT,
|
|
468
503
|
metavar='FORMAT', help='Entry format string for printing.')
|
|
469
504
|
@click.option('-r', '--released-only', is_flag=True, help='List only released templates')
|
|
@@ -471,10 +506,15 @@ def extract_package(ctx, template_package, output, force: bool):
|
|
|
471
506
|
@click.pass_context
|
|
472
507
|
def list_templates(ctx, api_url, api_key, output_format: str,
|
|
473
508
|
released_only: bool, drafts_only: bool):
|
|
509
|
+
ensure_api_config(api_url, api_key)
|
|
510
|
+
|
|
474
511
|
async def main_routine():
|
|
475
512
|
tdk = TDKCore(logger=ctx.obj.logger)
|
|
476
513
|
try:
|
|
477
|
-
await tdk.init_client(
|
|
514
|
+
await tdk.init_client(
|
|
515
|
+
api_url=CONFIG.env.api_url,
|
|
516
|
+
api_key=CONFIG.env.api_key,
|
|
517
|
+
)
|
|
478
518
|
if released_only:
|
|
479
519
|
templates = await tdk.list_remote_templates()
|
|
480
520
|
for template in templates:
|
|
@@ -494,7 +534,7 @@ def list_templates(ctx, api_url, api_key, output_format: str,
|
|
|
494
534
|
click.echo(output_format.format(template=template))
|
|
495
535
|
await tdk.safe_client.safe_close()
|
|
496
536
|
|
|
497
|
-
except
|
|
537
|
+
except WizardCommunicationError as e:
|
|
498
538
|
ClickPrinter.failure('Failed to get list of templates')
|
|
499
539
|
ClickPrinter.error(f'> {e.reason}\n> {e.message}')
|
|
500
540
|
await tdk.safe_client.safe_close()
|
|
@@ -521,24 +561,141 @@ def verify_template(ctx, template_dir):
|
|
|
521
561
|
click.echo(f' - {err.field_name}: {err.message}')
|
|
522
562
|
|
|
523
563
|
|
|
524
|
-
@main.
|
|
564
|
+
@main.group(help='Manage shared user configuration (~/.dsw-tdk).', name='config')
|
|
565
|
+
@click.pass_context
|
|
566
|
+
# pylint: disable-next=unused-argument
|
|
567
|
+
def config(ctx):
|
|
568
|
+
pass
|
|
569
|
+
|
|
570
|
+
|
|
571
|
+
@config.command(name='init', help='Initialize the shared user configuration (~/.dsw-tdk).')
|
|
572
|
+
@click.option('-f', '--force', is_flag=True, help='Overwrite file if already exists.')
|
|
573
|
+
def config_init(force):
|
|
574
|
+
if CONFIG.HOME_CONFIG.exists() and not force:
|
|
575
|
+
ClickPrinter.error('Configuration file already exists. Use `--force` to overwrite it.')
|
|
576
|
+
sys.exit(1)
|
|
577
|
+
CONFIG.shared_envs.clear()
|
|
578
|
+
CONFIG.default_env_name = None
|
|
579
|
+
|
|
580
|
+
click.echo('You need to specify your first environment name (default one).')
|
|
581
|
+
click.echo('Recommendation: use short and lowercase name, e.g. "production"')
|
|
582
|
+
environment = click.prompt('Environment name', default='production')
|
|
583
|
+
api_url = click.prompt('API URL')
|
|
584
|
+
api_key = click.prompt('API Key', hide_input=True)
|
|
585
|
+
try:
|
|
586
|
+
CONFIG.add_shared_env(
|
|
587
|
+
name=environment,
|
|
588
|
+
api_url=api_url,
|
|
589
|
+
api_key=api_key,
|
|
590
|
+
)
|
|
591
|
+
CONFIG.default_env_name = environment
|
|
592
|
+
except Exception as e:
|
|
593
|
+
ClickPrinter.failure('Failed to add environment')
|
|
594
|
+
ClickPrinter.error(f'> {e}')
|
|
595
|
+
sys.exit(1)
|
|
596
|
+
while True:
|
|
597
|
+
add_another = click.confirm('Do you want to add another environment?', default=False)
|
|
598
|
+
if not add_another:
|
|
599
|
+
break
|
|
600
|
+
environment = click.prompt('Environment name')
|
|
601
|
+
api_url = click.prompt('API URL')
|
|
602
|
+
api_key = click.prompt('API Key', hide_input=True)
|
|
603
|
+
try:
|
|
604
|
+
CONFIG.add_shared_env(
|
|
605
|
+
name=environment,
|
|
606
|
+
api_url=api_url,
|
|
607
|
+
api_key=api_key,
|
|
608
|
+
)
|
|
609
|
+
except Exception as e:
|
|
610
|
+
ClickPrinter.failure('Failed to add environment (skipping)')
|
|
611
|
+
ClickPrinter.warning(f'> {e}')
|
|
612
|
+
|
|
613
|
+
try:
|
|
614
|
+
CONFIG.persist(force=force)
|
|
615
|
+
ClickPrinter.success('Configuration initialized successfully.')
|
|
616
|
+
except Exception as e:
|
|
617
|
+
ClickPrinter.failure('Failed to initialize configuration')
|
|
618
|
+
ClickPrinter.error(f'> {e}')
|
|
619
|
+
sys.exit(1)
|
|
620
|
+
|
|
621
|
+
|
|
622
|
+
@config.command(name='edit', help='Edit the shared user configuration (~/.dsw-tdk).')
|
|
623
|
+
@click.option('-f', '--force', is_flag=True, help='Create file if does not exist.')
|
|
624
|
+
def config_edit(force):
|
|
625
|
+
if not CONFIG.HOME_CONFIG.exists():
|
|
626
|
+
if force:
|
|
627
|
+
CONFIG.HOME_CONFIG.parent.mkdir(parents=True, exist_ok=True)
|
|
628
|
+
CONFIG.HOME_CONFIG.touch()
|
|
629
|
+
else:
|
|
630
|
+
ClickPrinter.error('Configuration file does not exist. Use `init` command '
|
|
631
|
+
'or `--force` flag to create it.')
|
|
632
|
+
sys.exit(1)
|
|
633
|
+
click.edit(
|
|
634
|
+
filename=str(CONFIG.HOME_CONFIG),
|
|
635
|
+
extension='.cfg',
|
|
636
|
+
require_save=True,
|
|
637
|
+
)
|
|
638
|
+
|
|
639
|
+
|
|
640
|
+
@config.command(name='check', help='Check the current configuration that can be loaded.')
|
|
641
|
+
def config_check():
|
|
642
|
+
hidden = click.style('(hidden)', fg='red', bold=True)
|
|
643
|
+
not_set = click.style('(not set)', fg='yellow', bold=True)
|
|
644
|
+
click.secho('Shared configuration (~/.dsw-tdk):', bold=True)
|
|
645
|
+
for env_name, env in CONFIG.shared_envs.items():
|
|
646
|
+
if env_name == CONFIG.current_env_name:
|
|
647
|
+
env_out = click.style(env_name, fg='green', bold=True)
|
|
648
|
+
click.echo(f'{env_out} (current)')
|
|
649
|
+
elif env_name == CONFIG.default_env_name:
|
|
650
|
+
env_out = click.style(env_name, fg='cyan', bold=True)
|
|
651
|
+
click.echo(f'{env_out} (default)')
|
|
652
|
+
else:
|
|
653
|
+
env_out = click.style(env_name, fg='blue', bold=True)
|
|
654
|
+
click.echo(env_out)
|
|
655
|
+
click.echo(f' API URL: {env.api_url if env.api_url else not_set}')
|
|
656
|
+
click.echo(f' API Key: {hidden if env.api_key else not_set}')
|
|
657
|
+
click.echo('')
|
|
658
|
+
click.secho('Project-local configuration:', bold=True)
|
|
659
|
+
if CONFIG.current_env_name == CONFIG.LOCAL_CONFIG:
|
|
660
|
+
env_out = click.style(CONFIG.LOCAL_CONFIG, fg='green', bold=True)
|
|
661
|
+
click.echo(f'{env_out} (current)')
|
|
662
|
+
else:
|
|
663
|
+
env_out = click.style('local', fg='blue', bold=True)
|
|
664
|
+
click.echo(env_out)
|
|
665
|
+
if CONFIG.local_env.api_url:
|
|
666
|
+
click.echo(f' API URL: {CONFIG.local_env.api_url}')
|
|
667
|
+
else:
|
|
668
|
+
click.echo(f' API URL: {not_set}')
|
|
669
|
+
if CONFIG.local_env.api_key:
|
|
670
|
+
click.echo(f' API Key: {hidden}')
|
|
671
|
+
else:
|
|
672
|
+
click.echo(f' API Key: {not_set}')
|
|
673
|
+
|
|
674
|
+
|
|
675
|
+
@config.command(name='dot-env', help='Create a .env file with API configuration.')
|
|
525
676
|
@click.argument('TEMPLATE-DIR', type=DIR_TYPE, default=CURRENT_DIR, required=False)
|
|
526
677
|
@click.option('-u', '--api-url', metavar='API-URL', envvar='DSW_API_URL',
|
|
527
|
-
|
|
678
|
+
help='URL of Wizard server API.')
|
|
528
679
|
@click.option('-k', '--api-key', metavar='API-KEY', envvar='DSW_API_KEY',
|
|
529
|
-
|
|
530
|
-
hide_input=True)
|
|
680
|
+
help='API key for Wizard instance.')
|
|
531
681
|
@click.option('-f', '--force', is_flag=True, help='Overwrite file if already exists.')
|
|
532
682
|
@click.pass_context
|
|
533
|
-
def
|
|
683
|
+
def config_create_dotenv(ctx, template_dir, api_url, api_key, force):
|
|
684
|
+
ensure_api_config(api_url, api_key)
|
|
534
685
|
filename = ctx.obj.dot_env_file or '.env'
|
|
535
|
-
|
|
686
|
+
output = pathlib.Path(template_dir) / filename
|
|
536
687
|
try:
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
688
|
+
if output.exists():
|
|
689
|
+
if force:
|
|
690
|
+
ClickPrinter.warning(f'Overwriting {output.as_posix()} (forced)', )
|
|
691
|
+
else:
|
|
692
|
+
raise FileExistsError(f'File {output.as_posix()} already exists (not forced)')
|
|
693
|
+
output.write_text(
|
|
694
|
+
data=create_dot_env(
|
|
695
|
+
api_url=CONFIG.env.api_url,
|
|
696
|
+
api_key=CONFIG.env.api_key,
|
|
697
|
+
),
|
|
698
|
+
encoding=DEFAULT_ENCODING,
|
|
542
699
|
)
|
|
543
700
|
except Exception as e:
|
|
544
701
|
ClickPrinter.failure('Failed to create dot-env file')
|