dsw-tdk 3.22.1__tar.gz → 3.24.0__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.
Files changed (35) hide show
  1. {dsw-tdk-3.22.1 → dsw-tdk-3.24.0}/CHANGELOG.md +13 -0
  2. {dsw-tdk-3.22.1/dsw_tdk.egg-info → dsw-tdk-3.24.0}/PKG-INFO +2 -4
  3. {dsw-tdk-3.22.1 → dsw-tdk-3.24.0}/dsw/tdk/api_client.py +5 -1
  4. {dsw-tdk-3.22.1 → dsw-tdk-3.24.0}/dsw/tdk/build_info.py +4 -4
  5. {dsw-tdk-3.22.1 → dsw-tdk-3.24.0}/dsw/tdk/cli.py +76 -21
  6. {dsw-tdk-3.22.1 → dsw-tdk-3.24.0}/dsw/tdk/consts.py +1 -1
  7. {dsw-tdk-3.22.1 → dsw-tdk-3.24.0}/dsw/tdk/core.py +9 -3
  8. {dsw-tdk-3.22.1 → dsw-tdk-3.24.0}/dsw/tdk/validation.py +1 -4
  9. {dsw-tdk-3.22.1 → dsw-tdk-3.24.0/dsw_tdk.egg-info}/PKG-INFO +2 -4
  10. {dsw-tdk-3.22.1 → dsw-tdk-3.24.0}/dsw_tdk.egg-info/SOURCES.txt +8 -1
  11. {dsw-tdk-3.22.1 → dsw-tdk-3.24.0}/pyproject.toml +2 -4
  12. {dsw-tdk-3.22.1 → dsw-tdk-3.24.0}/tests/test_basic.py +2 -1
  13. dsw-tdk-3.24.0/tests/test_cmd_get.py +76 -0
  14. dsw-tdk-3.24.0/tests/test_cmd_list.py +53 -0
  15. dsw-tdk-3.24.0/tests/test_cmd_new.py +75 -0
  16. dsw-tdk-3.24.0/tests/test_cmd_package.py +57 -0
  17. dsw-tdk-3.24.0/tests/test_cmd_put.py +54 -0
  18. dsw-tdk-3.24.0/tests/test_cmd_unpackage.py +57 -0
  19. dsw-tdk-3.24.0/tests/test_cmd_verify.py +46 -0
  20. {dsw-tdk-3.22.1 → dsw-tdk-3.24.0}/LICENSE +0 -0
  21. {dsw-tdk-3.22.1 → dsw-tdk-3.24.0}/MANIFEST.in +0 -0
  22. {dsw-tdk-3.22.1 → dsw-tdk-3.24.0}/README.md +0 -0
  23. {dsw-tdk-3.22.1 → dsw-tdk-3.24.0}/dsw/tdk/__init__.py +0 -0
  24. {dsw-tdk-3.22.1 → dsw-tdk-3.24.0}/dsw/tdk/__main__.py +0 -0
  25. {dsw-tdk-3.22.1 → dsw-tdk-3.24.0}/dsw/tdk/model.py +0 -0
  26. {dsw-tdk-3.22.1 → dsw-tdk-3.24.0}/dsw/tdk/templates/README.md.j2 +0 -0
  27. {dsw-tdk-3.22.1 → dsw-tdk-3.24.0}/dsw/tdk/templates/starter.j2 +0 -0
  28. {dsw-tdk-3.22.1 → dsw-tdk-3.24.0}/dsw/tdk/utils.py +0 -0
  29. {dsw-tdk-3.22.1 → dsw-tdk-3.24.0}/dsw_tdk.egg-info/dependency_links.txt +0 -0
  30. {dsw-tdk-3.22.1 → dsw-tdk-3.24.0}/dsw_tdk.egg-info/entry_points.txt +0 -0
  31. {dsw-tdk-3.22.1 → dsw-tdk-3.24.0}/dsw_tdk.egg-info/not-zip-safe +0 -0
  32. {dsw-tdk-3.22.1 → dsw-tdk-3.24.0}/dsw_tdk.egg-info/requires.txt +0 -0
  33. {dsw-tdk-3.22.1 → dsw-tdk-3.24.0}/dsw_tdk.egg-info/top_level.txt +0 -0
  34. {dsw-tdk-3.22.1 → dsw-tdk-3.24.0}/setup.cfg +0 -0
  35. {dsw-tdk-3.22.1 → dsw-tdk-3.24.0}/setup.py +0 -0
@@ -8,6 +8,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
8
8
  ## [Unreleased]
9
9
 
10
10
 
11
+ ## [3.24.0]
12
+
13
+ Released for version consistency with other DSW tools.
14
+
15
+ ## [3.23.0]
16
+
17
+ ### Changed
18
+
19
+ - Added support for authentication using API keys
20
+ - Added deprecated warning for username/password authentication
21
+
11
22
  ## [3.22.1]
12
23
 
13
24
  Released for version consistency with other DSW tools.
@@ -312,3 +323,5 @@ Initial DSW Template Development Kit (versioned as part of the [DSW platform](ht
312
323
  [3.21.0]: /../../tree/v3.21.0
313
324
  [3.22.0]: /../../tree/v3.22.0
314
325
  [3.22.1]: /../../tree/v3.22.1
326
+ [3.23.0]: /../../tree/v3.23.0
327
+ [3.24.0]: /../../tree/v3.24.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dsw-tdk
3
- Version: 3.22.1
3
+ Version: 3.24.0
4
4
  Summary: Data Stewardship Wizard Template Development Toolkit
5
5
  Author-email: Marek Suchánek <marek.suchanek@ds-wizard.org>
6
6
  License: Apache License 2.0
@@ -12,14 +12,12 @@ Classifier: Framework :: AsyncIO
12
12
  Classifier: Development Status :: 5 - Production/Stable
13
13
  Classifier: License :: OSI Approved :: Apache Software License
14
14
  Classifier: Programming Language :: Python
15
- Classifier: Programming Language :: Python :: 3.7
16
- Classifier: Programming Language :: Python :: 3.8
17
15
  Classifier: Programming Language :: Python :: 3.9
18
16
  Classifier: Programming Language :: Python :: 3.10
19
17
  Classifier: Programming Language :: Python :: 3.11
20
18
  Classifier: Topic :: Internet :: WWW/HTTP
21
19
  Classifier: Topic :: Utilities
22
- Requires-Python: <4,>=3.7
20
+ Requires-Python: <4,>=3.9
23
21
  Description-Content-Type: text/markdown
24
22
  Provides-Extra: test
25
23
  License-File: LICENSE
@@ -91,7 +91,7 @@ class DSWAPIClient:
91
91
  """
92
92
  self.api_url = api_url
93
93
  self.token = None
94
- self.session = session or aiohttp.ClientSession(connector=aiohttp.TCPConnector(verify_ssl=False))
94
+ self.session = session or aiohttp.ClientSession(connector=aiohttp.TCPConnector(ssl=False))
95
95
 
96
96
  @property
97
97
  def templates_endpoint(self):
@@ -142,6 +142,10 @@ class DSWAPIClient:
142
142
  self.token = body.get('token', None)
143
143
  return self.token
144
144
 
145
+ @handle_client_errors
146
+ async def get_current_user(self) -> dict:
147
+ return await self._get_json('/users/current')
148
+
145
149
  @handle_client_errors
146
150
  async def check_template_exists(self, remote_id: str) -> bool:
147
151
  async with self.session.get(f'{self.templates_endpoint}/{remote_id}', headers=self._headers()) as r:
@@ -9,9 +9,9 @@ BuildInfo = namedtuple(
9
9
  )
10
10
 
11
11
  BUILD_INFO = BuildInfo(
12
- version='v3.22.1~f58badd',
13
- built_at='2023-04-14 12:55:45Z',
14
- sha='f58badda4c93b654651aedd46e082f1d756eb2aa',
12
+ version='v3.24.0~de1a777',
13
+ built_at='2023-05-31 09:42:09Z',
14
+ sha='de1a777fb9287f666e161e98200054ae757fa44c',
15
15
  branch='HEAD',
16
- tag='v3.22.1',
16
+ tag='v3.24.0',
17
17
  )
@@ -1,4 +1,6 @@
1
1
  import asyncio
2
+ import sys
3
+
2
4
  import click # type: ignore
3
5
  import datetime
4
6
  import dotenv
@@ -39,6 +41,11 @@ class ClickPrinter:
39
41
  def error(message: str, **kwargs):
40
42
  click.secho(message=message, err=True, fg='red', **kwargs)
41
43
 
44
+ @staticmethod
45
+ def warning(message: str, **kwargs):
46
+ click.secho('WARNING', fg='yellow', bold=True, nl=False)
47
+ click.echo(f': {message}')
48
+
42
49
  @staticmethod
43
50
  def success(message: str):
44
51
  click.secho('SUCCESS', fg='green', bold=True, nl=False)
@@ -171,6 +178,37 @@ class CLIContext:
171
178
  self.logger.muted = True
172
179
 
173
180
 
181
+ class APICredentials:
182
+
183
+ def __init__(self, username, password, api_key):
184
+ self.username = username
185
+ self.password = password
186
+ self.api_key = api_key
187
+
188
+ def check(self):
189
+ if self.api_key is not None:
190
+ return
191
+ if self.username is not None and self.password is not None:
192
+ ClickPrinter.warning('Using username/password credentials, '
193
+ 'consider switching to API keys.')
194
+ ClickPrinter.warning('Username/password authentication will be '
195
+ 'removed in 3.25 as deprecated.')
196
+ return False
197
+ ClickPrinter.failure('Invalid credentials entered! You need to provide '
198
+ 'either API key or username/password credentials.')
199
+ sys.exit(1)
200
+
201
+ def init_args(self):
202
+ if self.api_key is not None:
203
+ return {
204
+ 'api_key': self.api_key,
205
+ }
206
+ return {
207
+ 'username': self.username,
208
+ 'password': self.password,
209
+ }
210
+
211
+
174
212
  def interact_formats() -> Dict[str, FormatSpec]:
175
213
  add_format = click.confirm('Do you want to add a format?', default=True)
176
214
  formats = dict() # type: Dict[str, FormatSpec]
@@ -270,21 +308,25 @@ def new_template(ctx, template_dir, force):
270
308
  @click.argument('TEMPLATE-DIR', type=NEW_DIR_TYPE, default=None, required=False)
271
309
  @click.option('-s', '--api-server', metavar='API-URL', envvar='DSW_API',
272
310
  prompt='URL of DSW API', help='URL of DSW server API.', callback=rectify_url)
273
- @click.option('-u', '--username', envvar='DSW_USERNAME', prompt='Username', hide_input=False,
274
- metavar='EMAIL', help='Admin username (email) for DSW instance.')
275
- @click.option('-p', '--password', envvar='DSW_PASSWORD', prompt='Email', hide_input=True,
276
- metavar='PASSWORD', help='Admin password for DSW instance.')
311
+ @click.option('-u', '--username', envvar='DSW_USERNAME', metavar='EMAIL', default=None,
312
+ help='Admin username (email) for DSW instance.')
313
+ @click.option('-p', '--password', envvar='DSW_PASSWORD', metavar='PASSWORD', default=None,
314
+ help='Admin password for DSW instance.')
315
+ @click.option('-k', '--api-key', envvar='DSW_API_KEY', metavar='API-KEY', default=None,
316
+ help='API key for DSW instance.')
277
317
  @click.option('-f', '--force', is_flag=True, help='Overwrite any existing files.')
278
318
  @click.pass_context
279
- def get_template(ctx, api_server, template_id, template_dir, username, password, force):
280
- template_dir = template_dir or dir_from_id(template_id)
319
+ def get_template(ctx, api_server, template_id, template_dir, username, password, api_key, force):
320
+ template_dir = pathlib.Path(template_dir or dir_from_id(template_id))
321
+ creds = APICredentials(username=username, password=password, api_key=api_key)
322
+ creds.check()
281
323
 
282
324
  async def main_routine():
283
325
  tdk = TDKCore(logger=ctx.obj.logger)
284
326
  template_type = 'unknown'
285
327
  zip_data = None
286
328
  try:
287
- await tdk.init_client(api_url=api_server, username=username, password=password)
329
+ await tdk.init_client(api_url=api_server, **creds.init_args())
288
330
  try:
289
331
  await tdk.load_remote(template_id=template_id)
290
332
  template_type = 'draft'
@@ -295,6 +337,7 @@ def get_template(ctx, api_server, template_id, template_dir, username, password,
295
337
  except DSWCommunicationError as e:
296
338
  ClickPrinter.error('Could not get template:', bold=True)
297
339
  ClickPrinter.error(f'> {e.reason}\n> {e.message}')
340
+ await tdk.safe_client.close()
298
341
  exit(1)
299
342
  await tdk.safe_client.safe_close()
300
343
  if template_type == 'draft':
@@ -305,6 +348,7 @@ def get_template(ctx, api_server, template_id, template_dir, username, password,
305
348
  except Exception as e:
306
349
  ClickPrinter.failure('Could not store template locally')
307
350
  ClickPrinter.error(f'> {e}')
351
+ await tdk.safe_client.close()
308
352
  exit(1)
309
353
  elif template_type == 'bundle' and zip_data is not None:
310
354
  try:
@@ -313,6 +357,7 @@ def get_template(ctx, api_server, template_id, template_dir, username, password,
313
357
  except Exception as e:
314
358
  ClickPrinter.failure('Could not store template locally')
315
359
  ClickPrinter.error(f'> {e}')
360
+ await tdk.safe_client.close()
316
361
  exit(1)
317
362
  else:
318
363
  ClickPrinter.failure(f'{template_id} is not released nor draft of a document template')
@@ -326,15 +371,19 @@ def get_template(ctx, api_server, template_id, template_dir, username, password,
326
371
  @click.argument('TEMPLATE-DIR', type=DIR_TYPE, default=CURRENT_DIR, required=False)
327
372
  @click.option('-s', '--api-server', metavar='API-URL', envvar='DSW_API',
328
373
  prompt='URL of DSW API', help='URL of DSW server API.', callback=rectify_url)
329
- @click.option('-u', '--username', envvar='DSW_USERNAME', prompt='Username', hide_input=False,
330
- metavar='USERNAME', help='Admin username (email address) for DSW instance.')
331
- @click.option('-p', '--password', envvar='DSW_PASSWORD', prompt='Password', hide_input=True,
332
- metavar='PASSWORD', help='Admin password for DSW instance.')
374
+ @click.option('-u', '--username', envvar='DSW_USERNAME', metavar='EMAIL', default=None,
375
+ help='Admin username (email) for DSW instance.')
376
+ @click.option('-p', '--password', envvar='DSW_PASSWORD', metavar='PASSWORD', default=None,
377
+ help='Admin password for DSW instance.')
378
+ @click.option('-k', '--api-key', envvar='DSW_API_KEY', metavar='API-KEY', default=None,
379
+ help='API key for DSW instance.')
333
380
  @click.option('-f', '--force', is_flag=True, help='Delete template if already exists.')
334
381
  @click.option('-w', '--watch', is_flag=True, help='Enter watch mode to continually upload changes.')
335
382
  @click.pass_context
336
- def put_template(ctx, api_server, template_dir, username, password, force, watch):
383
+ def put_template(ctx, api_server, template_dir, username, password, api_key, force, watch):
337
384
  tdk = TDKCore(logger=ctx.obj.logger)
385
+ creds = APICredentials(username=username, password=password, api_key=api_key)
386
+ creds.check()
338
387
 
339
388
  async def watch_callback(changes):
340
389
  changes = list(changes)
@@ -350,7 +399,7 @@ def put_template(ctx, api_server, template_dir, username, password, force, watch
350
399
  async def main_routine():
351
400
  load_local(tdk, template_dir)
352
401
  try:
353
- await tdk.init_client(api_server, username, password)
402
+ await tdk.init_client(api_url=api_server, **creds.init_args())
354
403
  await tdk.store_remote(force=force)
355
404
  ClickPrinter.success(f'Template {tdk.safe_project.safe_template.id} '
356
405
  f'uploaded to {api_server}')
@@ -409,7 +458,7 @@ def extract_package(ctx, template_package, output, force: bool):
409
458
  data = pathlib.Path(template_package).read_bytes()
410
459
  tdk.extract_package(
411
460
  zip_data=data,
412
- template_dir=output,
461
+ template_dir=pathlib.Path(output),
413
462
  force=force,
414
463
  )
415
464
  except Exception as e:
@@ -422,21 +471,26 @@ def extract_package(ctx, template_package, output, force: bool):
422
471
  @main.command(help='List templates from DSW via API.', name='list')
423
472
  @click.option('-s', '--api-server', metavar='API-URL', envvar='DSW_API',
424
473
  prompt='URL of DSW API', help='URL of DSW server API.', callback=rectify_url)
425
- @click.option('-u', '--username', envvar='DSW_USERNAME', prompt='Username', hide_input=False,
426
- metavar='USERNAME', help='Admin username (email address) for DSW instance.')
427
- @click.option('-p', '--password', envvar='DSW_PASSWORD', prompt='Password', hide_input=True,
428
- metavar='PASSWORD', help='Admin password for DSW instance.')
474
+ @click.option('-u', '--username', envvar='DSW_USERNAME', metavar='EMAIL', default=None,
475
+ help='Admin username (email) for DSW instance.')
476
+ @click.option('-p', '--password', envvar='DSW_PASSWORD', metavar='PASSWORD', default=None,
477
+ help='Admin password for DSW instance.')
478
+ @click.option('-k', '--api-key', envvar='DSW_API_KEY', metavar='API-KEY', default=None,
479
+ help='API key for DSW instance.')
429
480
  @click.option('--output-format', default=DEFAULT_LIST_FORMAT,
430
481
  metavar='FORMAT', help='Entry format string for printing.')
431
482
  @click.option('-r', '--released-only', is_flag=True, help='List only released templates')
432
483
  @click.option('-d', '--drafts-only', is_flag=True, help='List only template drafts')
433
484
  @click.pass_context
434
- def list_templates(ctx, api_server, username, password, output_format: str,
485
+ def list_templates(ctx, api_server, username, password, api_key, output_format: str,
435
486
  released_only: bool, drafts_only: bool):
487
+ creds = APICredentials(username=username, password=password, api_key=api_key)
488
+ creds.check()
489
+
436
490
  async def main_routine():
437
491
  tdk = TDKCore(logger=ctx.obj.logger)
438
492
  try:
439
- await tdk.init_client(api_server, username, password)
493
+ await tdk.init_client(api_url=api_server, **creds.init_args())
440
494
  if released_only:
441
495
  templates = await tdk.list_remote_templates()
442
496
  for template in templates:
@@ -454,12 +508,13 @@ def list_templates(ctx, api_server, username, password, output_format: str,
454
508
  drafts = await tdk.list_remote_drafts()
455
509
  for template in drafts:
456
510
  click.echo(output_format.format(template=template))
511
+ await tdk.safe_client.safe_close()
457
512
 
458
513
  except DSWCommunicationError as e:
459
514
  ClickPrinter.failure('Failed to get list of templates')
460
515
  ClickPrinter.error(f'> {e.reason}\n> {e.message}')
516
+ await tdk.safe_client.safe_close()
461
517
  exit(1)
462
- await tdk.safe_client.safe_close()
463
518
 
464
519
  loop = asyncio.get_event_loop()
465
520
  loop.run_until_complete(main_routine())
@@ -3,7 +3,7 @@ import pathspec # type: ignore
3
3
  import re
4
4
 
5
5
  APP = 'dsw-tdk'
6
- VERSION = '3.22.0'
6
+ VERSION = '3.24.0'
7
7
  METAMODEL_VERSION = 11
8
8
 
9
9
  REGEX_SEMVER = re.compile(r'^[0-9]+\.[0-9]+\.[0-9]+$')
@@ -99,12 +99,18 @@ class TDKCore:
99
99
  raise RuntimeError('No DSW API client specified')
100
100
  return self.client
101
101
 
102
- async def init_client(self, api_url: str, username: str, password: str):
102
+ async def init_client(self, api_url: str, username: Optional[str] = None,
103
+ password: Optional[str] = None, api_key: Optional[str] = None):
103
104
  self.logger.info(f'Connecting to {api_url}')
104
105
  self.client = DSWAPIClient(api_url=api_url)
105
- await self.client.login(email=username, password=password)
106
+ if api_key is not None:
107
+ self.client.token = api_key # type: ignore
108
+ if username is not None and password is not None:
109
+ await self.client.login(email=username, password=password)
106
110
  self.remote_version = await self.client.get_api_version()
107
- self.logger.info(f'Successfully authenticated as {username}')
111
+ user = await self.client.get_current_user()
112
+ self.logger.info(f'Successfully authenticated as {user["firstName"]} '
113
+ f'{user["lastName"]} ({user["email"]})')
108
114
  self.logger.debug(f'Connected to API version {self.remote_version}')
109
115
 
110
116
  def prepare_local(self, template_dir):
@@ -63,7 +63,7 @@ def _validate_version(field_name: str, value) -> List[ValidationError]:
63
63
 
64
64
  def _validate_natural(field_name: str, value) -> List[ValidationError]:
65
65
  if value is not None and (not isinstance(value, int) or value < 1):
66
- return [ValidationError(field_name, 'Field {field_name} must be positive integer')]
66
+ return [ValidationError(field_name, 'It must be positive integer')]
67
67
  return []
68
68
 
69
69
 
@@ -173,9 +173,7 @@ def _validate_steps(field_name: str, value: List[Step]) -> List[ValidationError]
173
173
  FormatValidator = GenericValidator({
174
174
  'uuid': [_validate_required, _validate_non_empty],
175
175
  'name': [_validate_required, _validate_non_empty],
176
- 'short_name': [_validate_required, _validate_non_empty],
177
176
  'icon': [_validate_required, _validate_non_empty],
178
- 'color': [_validate_required, _validate_non_empty],
179
177
  'steps': [_validate_required, _validate_steps],
180
178
  })
181
179
 
@@ -198,7 +196,6 @@ TemplateValidator = GenericValidator({
198
196
  'name': [_validate_required, _validate_non_empty],
199
197
  'description': [_validate_required, _validate_non_empty],
200
198
  'readme': [_validate_required, _validate_non_empty],
201
- 'recommended_package_id': [_validate_package_id],
202
199
  'license': [_validate_required, _validate_non_empty],
203
200
  'metamodel_version': [_validate_natural],
204
201
  'allowed_packages': [_validate_package_filters],
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dsw-tdk
3
- Version: 3.22.1
3
+ Version: 3.24.0
4
4
  Summary: Data Stewardship Wizard Template Development Toolkit
5
5
  Author-email: Marek Suchánek <marek.suchanek@ds-wizard.org>
6
6
  License: Apache License 2.0
@@ -12,14 +12,12 @@ Classifier: Framework :: AsyncIO
12
12
  Classifier: Development Status :: 5 - Production/Stable
13
13
  Classifier: License :: OSI Approved :: Apache Software License
14
14
  Classifier: Programming Language :: Python
15
- Classifier: Programming Language :: Python :: 3.7
16
- Classifier: Programming Language :: Python :: 3.8
17
15
  Classifier: Programming Language :: Python :: 3.9
18
16
  Classifier: Programming Language :: Python :: 3.10
19
17
  Classifier: Programming Language :: Python :: 3.11
20
18
  Classifier: Topic :: Internet :: WWW/HTTP
21
19
  Classifier: Topic :: Utilities
22
- Requires-Python: <4,>=3.7
20
+ Requires-Python: <4,>=3.9
23
21
  Description-Content-Type: text/markdown
24
22
  Provides-Extra: test
25
23
  License-File: LICENSE
@@ -23,4 +23,11 @@ dsw_tdk.egg-info/entry_points.txt
23
23
  dsw_tdk.egg-info/not-zip-safe
24
24
  dsw_tdk.egg-info/requires.txt
25
25
  dsw_tdk.egg-info/top_level.txt
26
- tests/test_basic.py
26
+ tests/test_basic.py
27
+ tests/test_cmd_get.py
28
+ tests/test_cmd_list.py
29
+ tests/test_cmd_new.py
30
+ tests/test_cmd_package.py
31
+ tests/test_cmd_put.py
32
+ tests/test_cmd_unpackage.py
33
+ tests/test_cmd_verify.py
@@ -4,7 +4,7 @@ build-backend = 'setuptools.build_meta'
4
4
 
5
5
  [project]
6
6
  name = 'dsw-tdk'
7
- version = "3.22.1"
7
+ version = "3.24.0"
8
8
  description = 'Data Stewardship Wizard Template Development Toolkit'
9
9
  readme = 'README.md'
10
10
  keywords = ['documents', 'dsw', 'jinja2', 'template', 'toolkit']
@@ -17,15 +17,13 @@ classifiers = [
17
17
  'Development Status :: 5 - Production/Stable',
18
18
  'License :: OSI Approved :: Apache Software License',
19
19
  'Programming Language :: Python',
20
- 'Programming Language :: Python :: 3.7',
21
- 'Programming Language :: Python :: 3.8',
22
20
  'Programming Language :: Python :: 3.9',
23
21
  'Programming Language :: Python :: 3.10',
24
22
  'Programming Language :: Python :: 3.11',
25
23
  'Topic :: Internet :: WWW/HTTP',
26
24
  'Topic :: Utilities',
27
25
  ]
28
- requires-python = '>=3.7, <4'
26
+ requires-python = '>=3.9, <4'
29
27
  dependencies = [
30
28
  'aiohttp',
31
29
  'click',
@@ -1,6 +1,7 @@
1
1
  import pytest
2
2
 
3
3
  from click.testing import CliRunner
4
+
4
5
  from dsw.tdk import main
5
6
 
6
7
 
@@ -17,7 +18,7 @@ def test_base_help_works():
17
18
 
18
19
 
19
20
  @pytest.mark.parametrize(
20
- 'cmd', ['new', 'list', 'get', 'put', 'verify', 'package']
21
+ 'cmd', ['new', 'list', 'get', 'put', 'verify', 'package', 'unpackage']
21
22
  )
22
23
  def test_command_help_works(cmd):
23
24
  runner = CliRunner()
@@ -0,0 +1,76 @@
1
+ import pathlib
2
+
3
+ import pytest
4
+ import click.testing
5
+
6
+ from dsw.tdk import main
7
+
8
+
9
+ @pytest.mark.vcr
10
+ def test_get_released(tmp_path: pathlib.Path, dsw_env: dict):
11
+ runner = click.testing.CliRunner()
12
+ with runner.isolated_filesystem(temp_dir=tmp_path) as isolated_dir:
13
+ root_dir = pathlib.Path(isolated_dir)
14
+ template_dir = root_dir / 'dsw_questionnaire-report_2.8.0'
15
+ result = runner.invoke(main, args=['get', 'dsw:questionnaire-report:2.8.0'], env=dsw_env)
16
+ assert result.exit_code == 0
17
+ assert (template_dir / 'template.json').exists()
18
+ assert (template_dir / 'README.md').exists()
19
+
20
+
21
+ @pytest.mark.vcr
22
+ def test_get_draft(tmp_path: pathlib.Path, dsw_env: dict):
23
+ runner = click.testing.CliRunner()
24
+ with runner.isolated_filesystem(temp_dir=tmp_path) as isolated_dir:
25
+ root_dir = pathlib.Path(isolated_dir)
26
+ template_dir = root_dir / 'myorg_questionnaire-report_2.9.0'
27
+ result = runner.invoke(main, args=['get', 'myorg:questionnaire-report:2.9.0'], env=dsw_env)
28
+ assert result.exit_code == 0
29
+ assert (template_dir / 'template.json').exists()
30
+ assert (template_dir / 'README.md').exists()
31
+
32
+
33
+ @pytest.mark.vcr
34
+ def test_get_released_custom_dir(tmp_path: pathlib.Path, dsw_env: dict):
35
+ runner = click.testing.CliRunner()
36
+ with runner.isolated_filesystem(temp_dir=tmp_path) as isolated_dir:
37
+ root_dir = pathlib.Path(isolated_dir)
38
+ template_dir = root_dir / 'foo'
39
+ result = runner.invoke(main, args=['get', 'dsw:questionnaire-report:2.8.0', 'foo'], env=dsw_env)
40
+ assert result.exit_code == 0
41
+ assert (template_dir / 'template.json').exists()
42
+ assert (template_dir / 'README.md').exists()
43
+
44
+
45
+ @pytest.mark.vcr
46
+ def test_get_draft_custom_dir(tmp_path: pathlib.Path, dsw_env: dict):
47
+ runner = click.testing.CliRunner()
48
+ with runner.isolated_filesystem(temp_dir=tmp_path) as isolated_dir:
49
+ root_dir = pathlib.Path(isolated_dir)
50
+ template_dir = root_dir / 'foo'
51
+ result = runner.invoke(main, args=['get', 'myorg:questionnaire-report:2.9.0', 'foo'], env=dsw_env)
52
+ assert result.exit_code == 0
53
+ assert (template_dir / 'template.json').exists()
54
+ assert (template_dir / 'README.md').exists()
55
+
56
+
57
+ @pytest.mark.vcr
58
+ def test_put_bad_token(dsw_api_url: str):
59
+ env_vars = {
60
+ 'DSW_API': dsw_api_url,
61
+ 'DSW_API_KEY': 'foo',
62
+ }
63
+ runner = click.testing.CliRunner()
64
+ result = runner.invoke(main, args=['list'], env=env_vars)
65
+ assert result.exit_code == 1
66
+
67
+
68
+ @pytest.mark.vcr
69
+ def test_put_bad_url(dsw_api_key: str):
70
+ env_vars = {
71
+ 'DSW_API': 'http://localhost:33333',
72
+ 'DSW_API_KEY': dsw_api_key,
73
+ }
74
+ runner = click.testing.CliRunner()
75
+ result = runner.invoke(main, args=['list'], env=env_vars)
76
+ assert result.exit_code == 1
@@ -0,0 +1,53 @@
1
+ import pytest
2
+ import click.testing
3
+
4
+ from dsw.tdk import main
5
+
6
+
7
+ @pytest.mark.vcr
8
+ def test_list_simple(dsw_env: dict):
9
+ runner = click.testing.CliRunner()
10
+ result = runner.invoke(main, args=['list'], env=dsw_env)
11
+ assert result.exit_code == 0
12
+ assert 'dsw:questionnaire-report:2.8.0' in result.output
13
+ assert 'myorg:questionnaire-report:2.9.0' in result.output
14
+
15
+
16
+ @pytest.mark.vcr
17
+ def test_list_drafts_only(dsw_env: dict):
18
+ runner = click.testing.CliRunner()
19
+ result = runner.invoke(main, args=['list', '-d'], env=dsw_env)
20
+ assert result.exit_code == 0
21
+ assert 'dsw:questionnaire-report:2.8.0' not in result.output
22
+ assert 'myorg:questionnaire-report:2.9.0' in result.output
23
+
24
+
25
+ @pytest.mark.vcr
26
+ def test_list_released_only(dsw_env: dict):
27
+ runner = click.testing.CliRunner()
28
+ result = runner.invoke(main, args=['list', '-r'], env=dsw_env)
29
+ assert result.exit_code == 0
30
+ assert 'dsw:questionnaire-report:2.8.0' in result.output
31
+ assert 'myorg:questionnaire-report:2.9.0' not in result.output
32
+
33
+
34
+ @pytest.mark.vcr
35
+ def test_put_bad_token(dsw_api_url: str):
36
+ env_vars = {
37
+ 'DSW_API': dsw_api_url,
38
+ 'DSW_API_KEY': 'foo',
39
+ }
40
+ runner = click.testing.CliRunner()
41
+ result = runner.invoke(main, args=['list'], env=env_vars)
42
+ assert result.exit_code == 1
43
+
44
+
45
+ @pytest.mark.vcr
46
+ def test_put_bad_url(dsw_api_key: str):
47
+ env_vars = {
48
+ 'DSW_API': 'http://localhost:33333',
49
+ 'DSW_API_KEY': dsw_api_key,
50
+ }
51
+ runner = click.testing.CliRunner()
52
+ result = runner.invoke(main, args=['list'], env=env_vars)
53
+ assert result.exit_code == 1
@@ -0,0 +1,75 @@
1
+ import pathlib
2
+
3
+ import click.testing
4
+
5
+ from dsw.tdk import main
6
+
7
+
8
+ def test_new_no_dir(tmp_path: pathlib.Path):
9
+ runner = click.testing.CliRunner()
10
+ inputs = ['Test template', 'dsw', 'test-template', '0.1.0', 'some description', 'CC0',
11
+ 'y', 'HTML', 'html', 'text/html', 'src/template.html.j2', 'n']
12
+ with runner.isolated_filesystem(temp_dir=tmp_path) as isolated_dir:
13
+ result = runner.invoke(main, args=['new'], input='\n'.join(inputs))
14
+ assert not result.exception
15
+ assert result.exit_code == 0
16
+ paths = frozenset(map(lambda x: str(x.relative_to(isolated_dir).as_posix()), tmp_path.rglob('*')))
17
+ assert 'dsw_test-template_0.1.0/template.json' in paths
18
+ assert 'dsw_test-template_0.1.0/README.md' in paths
19
+ assert 'dsw_test-template_0.1.0/src' in paths
20
+ assert 'dsw_test-template_0.1.0/src/template.html.j2' in paths
21
+
22
+
23
+ def test_new_dir(tmp_path: pathlib.Path):
24
+ runner = click.testing.CliRunner()
25
+ inputs = ['Test template', 'dsw', 'test-template', '0.1.0', 'some description', 'CC0',
26
+ 'y', 'HTML', 'html', 'text/html', 'src/template.html.j2', 'n']
27
+ with runner.isolated_filesystem(temp_dir=tmp_path) as isolated_dir:
28
+ result = runner.invoke(main, args=['new', 'my-template'], input='\n'.join(inputs))
29
+ assert not result.exception
30
+ assert result.exit_code == 0
31
+ paths = frozenset(map(lambda x: str(x.relative_to(isolated_dir).as_posix()), tmp_path.rglob('*')))
32
+ assert 'my-template/template.json' in paths
33
+ assert 'my-template/README.md' in paths
34
+ assert 'my-template/src' in paths
35
+ assert 'my-template/src/template.html.j2' in paths
36
+
37
+
38
+ def test_new_without_force(tmp_path: pathlib.Path):
39
+ runner = click.testing.CliRunner()
40
+ inputs = ['Test template', 'dsw', 'test-template', '0.1.0', 'some description', 'CC0',
41
+ 'y', 'HTML', 'html', 'text/html', 'src/template.html.j2', 'n']
42
+ with runner.isolated_filesystem(temp_dir=tmp_path) as isolated_dir:
43
+ root_dir = pathlib.Path(isolated_dir)
44
+ template_json = root_dir / 'template.json'
45
+ template_json.write_text('foo', encoding='utf-8')
46
+
47
+ result = runner.invoke(main, args=['new', '.'], input='\n'.join(inputs))
48
+ assert not result.exception
49
+ assert result.exit_code == 0
50
+ paths = frozenset(map(lambda x: str(x.relative_to(isolated_dir).as_posix()), tmp_path.rglob('*')))
51
+ assert 'template.json' in paths
52
+ assert 'README.md' in paths
53
+ assert 'src' in paths
54
+ assert 'src/template.html.j2' in paths
55
+ assert template_json.read_text(encoding='utf-8') == 'foo'
56
+
57
+
58
+ def test_new_with_force(tmp_path: pathlib.Path):
59
+ runner = click.testing.CliRunner()
60
+ inputs = ['Test template', 'dsw', 'test-template', '0.1.0', 'some description', 'CC0',
61
+ 'y', 'HTML', 'html', 'text/html', 'src/template.html.j2', 'n']
62
+ with runner.isolated_filesystem(temp_dir=tmp_path) as isolated_dir:
63
+ root_dir = pathlib.Path(isolated_dir)
64
+ template_json = root_dir / 'template.json'
65
+ template_json.write_text('foo', encoding='utf-8')
66
+
67
+ result = runner.invoke(main, args=['new', '--force', '.'], input='\n'.join(inputs))
68
+ assert not result.exception
69
+ assert result.exit_code == 0
70
+ paths = frozenset(map(lambda x: str(x.relative_to(isolated_dir).as_posix()), tmp_path.rglob('*')))
71
+ assert 'template.json' in paths
72
+ assert 'README.md' in paths
73
+ assert 'src' in paths
74
+ assert 'src/template.html.j2' in paths
75
+ assert template_json.read_text(encoding='utf-8') != 'foo'
@@ -0,0 +1,57 @@
1
+ import pathlib
2
+
3
+ import click.testing
4
+
5
+ from dsw.tdk import main
6
+
7
+
8
+ def test_package_ok(fixtures_path: pathlib.Path, tmp_path: pathlib.Path):
9
+ runner = click.testing.CliRunner()
10
+ template_path = fixtures_path / 'test_example01'
11
+ with runner.isolated_filesystem(temp_dir=tmp_path) as isolated_dir:
12
+ root_dir = pathlib.Path(isolated_dir)
13
+ zip_file = root_dir / 'my-template.zip'
14
+
15
+ result = runner.invoke(main, args=['package', template_path.as_posix(), '-o', 'my-template.zip'])
16
+ assert result.exit_code == 0
17
+ assert zip_file.exists() and zip_file.is_file()
18
+
19
+
20
+ def test_package_without_force(fixtures_path: pathlib.Path, tmp_path: pathlib.Path):
21
+ runner = click.testing.CliRunner()
22
+ template_path = fixtures_path / 'test_example01'
23
+ with runner.isolated_filesystem(temp_dir=tmp_path) as isolated_dir:
24
+ root_dir = pathlib.Path(isolated_dir)
25
+ zip_file = root_dir / 'my-template.zip'
26
+ zip_file.write_bytes(b'foo')
27
+
28
+ result = runner.invoke(main, args=['package', template_path.as_posix(), '-o', 'my-template.zip'])
29
+ assert result.exit_code == 1
30
+ assert zip_file.exists() and zip_file.is_file()
31
+ assert zip_file.read_bytes() == b'foo'
32
+
33
+
34
+ def test_package_with_force(fixtures_path: pathlib.Path, tmp_path: pathlib.Path):
35
+ runner = click.testing.CliRunner()
36
+ template_path = fixtures_path / 'test_example01'
37
+ with runner.isolated_filesystem(temp_dir=tmp_path) as isolated_dir:
38
+ root_dir = pathlib.Path(isolated_dir)
39
+ zip_file = root_dir / 'my-template.zip'
40
+ zip_file.write_bytes(b'foo')
41
+
42
+ result = runner.invoke(main, args=['package', template_path.as_posix(), '-o', 'my-template.zip', '-f'])
43
+ assert result.exit_code == 0
44
+ assert zip_file.exists() and zip_file.is_file()
45
+ assert zip_file.read_bytes() != b'foo'
46
+
47
+
48
+ def test_package_faulty01(fixtures_path: pathlib.Path, tmp_path: pathlib.Path):
49
+ runner = click.testing.CliRunner()
50
+ template_path = fixtures_path / 'test_faulty01'
51
+ with runner.isolated_filesystem(temp_dir=tmp_path) as isolated_dir:
52
+ root_dir = pathlib.Path(isolated_dir)
53
+ zip_file = root_dir / 'my-template.zip'
54
+
55
+ result = runner.invoke(main, args=['package', template_path.as_posix(), '-o', 'my-template.zip'])
56
+ assert result.exit_code == 1
57
+ assert not zip_file.exists()
@@ -0,0 +1,54 @@
1
+ import pathlib
2
+
3
+ import click.testing
4
+ import pytest
5
+
6
+ from dsw.tdk import main
7
+
8
+
9
+ @pytest.mark.vcr
10
+ def test_put_ok(fixtures_path: pathlib.Path, dsw_env: dict):
11
+ runner = click.testing.CliRunner()
12
+ template_path = fixtures_path / 'test_example01'
13
+ result = runner.invoke(main, args=['put', template_path.as_posix()], env=dsw_env)
14
+ assert result.exit_code == 0
15
+
16
+
17
+ @pytest.mark.vcr
18
+ def test_put_faulty(fixtures_path: pathlib.Path, dsw_env: dict):
19
+ runner = click.testing.CliRunner()
20
+ template_path = fixtures_path / 'test_faulty01'
21
+ result = runner.invoke(main, args=['put', template_path.as_posix()], env=dsw_env)
22
+ assert result.exit_code == 1
23
+
24
+
25
+ @pytest.mark.vcr
26
+ def test_put_published(fixtures_path: pathlib.Path, dsw_env: dict):
27
+ runner = click.testing.CliRunner()
28
+ template_path = fixtures_path / 'test_example02'
29
+ result = runner.invoke(main, args=['put', template_path.as_posix()], env=dsw_env)
30
+ assert result.exit_code == 1
31
+
32
+
33
+ @pytest.mark.vcr
34
+ def test_put_bad_token(fixtures_path: pathlib.Path, dsw_api_url: str):
35
+ runner = click.testing.CliRunner()
36
+ template_path = fixtures_path / 'test_example01'
37
+ env_vars = {
38
+ 'DSW_API': dsw_api_url,
39
+ 'DSW_API_KEY': 'foo',
40
+ }
41
+ result = runner.invoke(main, args=['put', template_path.as_posix()], env=env_vars)
42
+ assert result.exit_code == 1
43
+
44
+
45
+ @pytest.mark.vcr
46
+ def test_put_bad_url(fixtures_path: pathlib.Path, dsw_api_key: str):
47
+ runner = click.testing.CliRunner()
48
+ template_path = fixtures_path / 'test_example01'
49
+ env_vars = {
50
+ 'DSW_API': 'http://localhost:33333',
51
+ 'DSW_API_KEY': dsw_api_key,
52
+ }
53
+ result = runner.invoke(main, args=['put', template_path.as_posix()], env=env_vars)
54
+ assert result.exit_code == 1
@@ -0,0 +1,57 @@
1
+ import pathlib
2
+
3
+ import click.testing
4
+
5
+ from dsw.tdk import main
6
+
7
+
8
+ def test_unpackage_ok(fixtures_path: pathlib.Path, tmp_path: pathlib.Path):
9
+ runner = click.testing.CliRunner()
10
+ zip_file = fixtures_path / 'my-template.zip'
11
+ with runner.isolated_filesystem(temp_dir=tmp_path) as isolated_dir:
12
+ root_dir = pathlib.Path(isolated_dir)
13
+ template_dir = root_dir / 'extracted'
14
+
15
+ result = runner.invoke(main, args=['unpackage', zip_file.as_posix(), '-o', template_dir.as_posix()])
16
+ assert result.exit_code == 0
17
+ assert (template_dir / 'template.json').exists()
18
+ assert (template_dir / 'README.md').exists()
19
+
20
+
21
+ def test_unpackage_without_force(fixtures_path: pathlib.Path, tmp_path: pathlib.Path):
22
+ runner = click.testing.CliRunner()
23
+ zip_file = fixtures_path / 'my-template.zip'
24
+ with runner.isolated_filesystem(temp_dir=tmp_path) as isolated_dir:
25
+ root_dir = pathlib.Path(isolated_dir)
26
+ template_dir = root_dir / 'extracted'
27
+ template_dir.mkdir()
28
+
29
+ result = runner.invoke(main, args=['unpackage', zip_file.as_posix(), '-o', template_dir.as_posix()])
30
+ assert result.exit_code == 1
31
+ assert not (template_dir / 'template.json').exists()
32
+ assert not (template_dir / 'README.md').exists()
33
+
34
+
35
+ def test_unpackage_with_force(fixtures_path: pathlib.Path, tmp_path: pathlib.Path):
36
+ runner = click.testing.CliRunner()
37
+ zip_file = fixtures_path / 'my-template.zip'
38
+ with runner.isolated_filesystem(temp_dir=tmp_path) as isolated_dir:
39
+ root_dir = pathlib.Path(isolated_dir)
40
+ template_dir = root_dir / 'extracted'
41
+ template_dir.mkdir()
42
+
43
+ result = runner.invoke(main, args=['unpackage', zip_file.as_posix(), '-f', '-o', template_dir.as_posix()])
44
+ assert result.exit_code == 0
45
+ assert (template_dir / 'template.json').exists()
46
+ assert (template_dir / 'README.md').exists()
47
+
48
+
49
+ def test_unpackage_with_faulty(fixtures_path: pathlib.Path, tmp_path: pathlib.Path):
50
+ runner = click.testing.CliRunner()
51
+ zip_file = fixtures_path / 'my-template-faulty.zip'
52
+ with runner.isolated_filesystem(temp_dir=tmp_path) as isolated_dir:
53
+ root_dir = pathlib.Path(isolated_dir)
54
+ template_dir = root_dir / 'extracted'
55
+
56
+ result = runner.invoke(main, args=['unpackage', zip_file.as_posix(), '-o', template_dir.as_posix()])
57
+ assert result.exit_code == 1
@@ -0,0 +1,46 @@
1
+ import pathlib
2
+
3
+ import click.testing
4
+
5
+ from dsw.tdk import main
6
+
7
+
8
+ def test_verify_ok(fixtures_path: pathlib.Path):
9
+ runner = click.testing.CliRunner()
10
+ template_path = fixtures_path / 'test_example01'
11
+ result = runner.invoke(main, args=['verify', template_path.as_posix()])
12
+ assert result.exit_code == 0
13
+ assert 'The template is valid!' in result.output
14
+ assert 'Test template 01' in result.output
15
+
16
+
17
+ def test_verify_missing_attrs(fixtures_path: pathlib.Path):
18
+ runner = click.testing.CliRunner()
19
+ template_path = fixtures_path / 'test_faulty01'
20
+ result = runner.invoke(main, args=['verify', template_path.as_posix()])
21
+ assert result.exit_code == 1
22
+ assert 'Could not load local template' in result.output
23
+ assert 'Unable to load template using' in result.output
24
+
25
+
26
+ def test_verify_no_readme(fixtures_path: pathlib.Path):
27
+ runner = click.testing.CliRunner()
28
+ template_path = fixtures_path / 'test_faulty02'
29
+ result = runner.invoke(main, args=['verify', template_path.as_posix()])
30
+ assert result.exit_code == 1
31
+ assert 'Could not load local template' in result.output
32
+ assert 'README file "README.md" cannot be loaded' in result.output
33
+
34
+
35
+ def test_verify_invalid(fixtures_path: pathlib.Path):
36
+ runner = click.testing.CliRunner()
37
+ template_path = fixtures_path / 'test_faulty03'
38
+ result = runner.invoke(main, args=['verify', template_path.as_posix()])
39
+ assert result.exit_code == 0
40
+ assert 'The template is invalid!' in result.output
41
+ assert 'template_id: Template ID may contain only letters, numbers, and dash' in result.output
42
+ assert 'organization_id: Organization ID may contain only letters, numbers, and period' in result.output
43
+ assert 'version: Version must be in semver format' in result.output
44
+ assert 'name: Missing but it is required' in result.output
45
+ assert 'description: Missing but it is required' in result.output
46
+ assert 'metamodel_version: It must be positive integer' in result.output
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes