dsw-tdk 4.20.1__py2.py3-none-any.whl → 4.22.0__py2.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/api_client.py +41 -35
- dsw/tdk/build_info.py +4 -4
- dsw/tdk/cli.py +210 -53
- dsw/tdk/config.py +154 -0
- dsw/tdk/consts.py +4 -2
- dsw/tdk/core.py +119 -113
- dsw/tdk/model.py +13 -13
- dsw/tdk/templates/env.j2 +1 -0
- dsw/tdk/validation.py +33 -1
- {dsw_tdk-4.20.1.dist-info → dsw_tdk-4.22.0.dist-info}/METADATA +2 -1
- dsw_tdk-4.22.0.dist-info/RECORD +21 -0
- dsw_tdk-4.20.1.dist-info/RECORD +0 -20
- {dsw_tdk-4.20.1.dist-info → dsw_tdk-4.22.0.dist-info}/WHEEL +0 -0
- {dsw_tdk-4.20.1.dist-info → dsw_tdk-4.22.0.dist-info}/entry_points.txt +0 -0
- {dsw_tdk-4.20.1.dist-info → dsw_tdk-4.22.0.dist-info}/licenses/LICENSE +0 -0
- {dsw_tdk-4.20.1.dist-info → dsw_tdk-4.22.0.dist-info}/top_level.txt +0 -0
dsw/tdk/api_client.py
CHANGED
|
@@ -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})'
|
|
@@ -158,7 +158,13 @@ class DSWAPIClient:
|
|
|
158
158
|
async def login(self, email: str, password: str) -> str | None:
|
|
159
159
|
req = {'email': email, 'password': password}
|
|
160
160
|
body = await self._post_json('/tokens', json=req)
|
|
161
|
-
|
|
161
|
+
token_value = body.get('token', None)
|
|
162
|
+
if not isinstance(token_value, str):
|
|
163
|
+
raise WizardCommunicationError(
|
|
164
|
+
reason='Invalid response',
|
|
165
|
+
message='Server did not return a valid token'
|
|
166
|
+
)
|
|
167
|
+
self.token = token_value
|
|
162
168
|
return self.token
|
|
163
169
|
|
|
164
170
|
@handle_client_errors
|
|
@@ -220,8 +226,8 @@ class DSWAPIClient:
|
|
|
220
226
|
result = []
|
|
221
227
|
for file_body in body:
|
|
222
228
|
file_id = file_body['uuid']
|
|
223
|
-
|
|
224
|
-
result.append(
|
|
229
|
+
file = await self.get_template_draft_file(remote_id, file_id)
|
|
230
|
+
result.append(file)
|
|
225
231
|
return result
|
|
226
232
|
|
|
227
233
|
@handle_client_errors
|
|
@@ -270,23 +276,23 @@ class DSWAPIClient:
|
|
|
270
276
|
return _load_remote_template(body)
|
|
271
277
|
|
|
272
278
|
@handle_client_errors
|
|
273
|
-
async def post_template_draft_file(self, remote_id: str,
|
|
279
|
+
async def post_template_draft_file(self, remote_id: str, file: TemplateFile):
|
|
274
280
|
data = await self._post_json(
|
|
275
281
|
endpoint=f'/document-template-drafts/{remote_id}/files',
|
|
276
282
|
json={
|
|
277
|
-
'fileName':
|
|
278
|
-
'content':
|
|
283
|
+
'fileName': file.filename.as_posix(),
|
|
284
|
+
'content': file.content.decode(DEFAULT_ENCODING)
|
|
279
285
|
}
|
|
280
286
|
)
|
|
281
287
|
return _load_remote_file(data)
|
|
282
288
|
|
|
283
289
|
@handle_client_errors
|
|
284
|
-
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):
|
|
285
291
|
self.session.headers.update(self._headers())
|
|
286
292
|
async with self.session.put(
|
|
287
293
|
f'{self.api_url}/document-template-drafts/{remote_id}'
|
|
288
|
-
f'/files/{
|
|
289
|
-
data=
|
|
294
|
+
f'/files/{file.remote_id}/content',
|
|
295
|
+
data=file.content,
|
|
290
296
|
headers={'Content-Type': 'text/plain;charset=UTF-8'},
|
|
291
297
|
) as r:
|
|
292
298
|
self._check_status(r, expected_status=200)
|
|
@@ -294,17 +300,17 @@ class DSWAPIClient:
|
|
|
294
300
|
return _load_remote_file(body)
|
|
295
301
|
|
|
296
302
|
@handle_client_errors
|
|
297
|
-
async def post_template_draft_asset(self, remote_id: str,
|
|
303
|
+
async def post_template_draft_asset(self, remote_id: str, file: TemplateFile):
|
|
298
304
|
data = aiohttp.FormData()
|
|
299
305
|
data.add_field(
|
|
300
306
|
name='file',
|
|
301
|
-
value=
|
|
302
|
-
filename=
|
|
303
|
-
content_type=
|
|
307
|
+
value=file.content,
|
|
308
|
+
filename=file.filename.as_posix(),
|
|
309
|
+
content_type=file.content_type,
|
|
304
310
|
)
|
|
305
311
|
data.add_field(
|
|
306
312
|
name='fileName',
|
|
307
|
-
value=
|
|
313
|
+
value=file.filename.as_posix(),
|
|
308
314
|
)
|
|
309
315
|
async with self.session.post(
|
|
310
316
|
f'{self.api_url}/document-template-drafts/{remote_id}/assets',
|
|
@@ -313,30 +319,30 @@ class DSWAPIClient:
|
|
|
313
319
|
) as r:
|
|
314
320
|
self._check_status(r, expected_status=201)
|
|
315
321
|
body = await r.json()
|
|
316
|
-
return _load_remote_asset(body,
|
|
322
|
+
return _load_remote_asset(body, file.content)
|
|
317
323
|
|
|
318
324
|
@handle_client_errors
|
|
319
|
-
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):
|
|
320
326
|
data = aiohttp.FormData()
|
|
321
327
|
data.add_field(
|
|
322
328
|
name='file',
|
|
323
|
-
value=
|
|
324
|
-
filename=
|
|
325
|
-
content_type=
|
|
329
|
+
value=file.content,
|
|
330
|
+
filename=file.filename.as_posix(),
|
|
331
|
+
content_type=file.content_type,
|
|
326
332
|
)
|
|
327
333
|
data.add_field(
|
|
328
334
|
name='fileName',
|
|
329
|
-
value=
|
|
335
|
+
value=file.filename.as_posix(),
|
|
330
336
|
)
|
|
331
337
|
async with self.session.put(
|
|
332
338
|
f'{self.api_url}/document-template-drafts/{remote_id}'
|
|
333
|
-
f'/assets/{
|
|
339
|
+
f'/assets/{file.remote_id}/content',
|
|
334
340
|
data=data,
|
|
335
341
|
headers=self._headers()
|
|
336
342
|
) as r:
|
|
337
343
|
self._check_status(r, expected_status=200)
|
|
338
344
|
body = await r.json()
|
|
339
|
-
return _load_remote_asset(body,
|
|
345
|
+
return _load_remote_asset(body, file.content)
|
|
340
346
|
|
|
341
347
|
@handle_client_errors
|
|
342
348
|
async def delete_template_draft(self, remote_id: str) -> bool:
|
|
@@ -368,25 +374,25 @@ class DSWAPIClient:
|
|
|
368
374
|
def _load_remote_file(data: dict) -> TemplateFile:
|
|
369
375
|
content: str = data.get('content', '')
|
|
370
376
|
filename: str = str(data.get('fileName', ''))
|
|
371
|
-
|
|
377
|
+
file = TemplateFile(
|
|
372
378
|
remote_id=data.get('uuid', None),
|
|
373
379
|
remote_type=TemplateFileType.FILE,
|
|
374
380
|
filename=pathlib.Path(urllib.parse.unquote(filename)),
|
|
375
381
|
content=content.encode(encoding=DEFAULT_ENCODING),
|
|
376
382
|
)
|
|
377
|
-
return
|
|
383
|
+
return file
|
|
378
384
|
|
|
379
385
|
|
|
380
386
|
def _load_remote_asset(data: dict, content: bytes) -> TemplateFile:
|
|
381
387
|
filename = str(data.get('fileName', ''))
|
|
382
|
-
|
|
388
|
+
asset = TemplateFile(
|
|
383
389
|
remote_id=data.get('uuid', None),
|
|
384
390
|
remote_type=TemplateFileType.ASSET,
|
|
385
391
|
filename=pathlib.Path(urllib.parse.unquote(filename)),
|
|
386
392
|
content_type=data.get('contentType', None),
|
|
387
393
|
content=content,
|
|
388
394
|
)
|
|
389
|
-
return
|
|
395
|
+
return asset
|
|
390
396
|
|
|
391
397
|
|
|
392
398
|
def _load_remote_template(data: dict) -> Template:
|
dsw/tdk/build_info.py
CHANGED
|
@@ -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.0~dffe6c1',
|
|
13
|
+
built_at='2025-09-02 13:01:04Z',
|
|
14
|
+
sha='dffe6c10e47b8795a0d27d495d1c0c0a5d4a76fb',
|
|
15
15
|
branch='HEAD',
|
|
16
|
-
tag='v4.
|
|
16
|
+
tag='v4.22.0',
|
|
17
17
|
)
|
dsw/tdk/cli.py
CHANGED
|
@@ -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,7 +412,10 @@ 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
421
|
f'uploaded to {api_url}')
|
|
@@ -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')
|