dsw-tdk 3.13.0__py3-none-any.whl → 4.27.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
dsw/tdk/validation.py ADDED
@@ -0,0 +1,290 @@
1
+ import re
2
+
3
+ from . import consts
4
+ from .model import Format, PackageFilter, Step
5
+
6
+
7
+ class ValidationError(BaseException):
8
+ def __init__(self, field_name: str, message: str):
9
+ self.field_name = field_name
10
+ self.message = message
11
+
12
+
13
+ def _validate_required(field_name: str, value) -> list[ValidationError]:
14
+ if value is None:
15
+ return [ValidationError(
16
+ field_name=field_name,
17
+ message='Missing but it is required',
18
+ )]
19
+ return []
20
+
21
+
22
+ def _validate_non_empty(field_name: str, value) -> list[ValidationError]:
23
+ if value is not None and len(value.strip()) == 0:
24
+ return [ValidationError(
25
+ field_name=field_name,
26
+ message='Cannot be empty or only-whitespace',
27
+ )]
28
+ return []
29
+
30
+
31
+ def _validate_content_type(field_name: str, value) -> list[ValidationError]:
32
+ if value is not None and re.match(consts.REGEX_MIME_TYPE, value) is None:
33
+ return [ValidationError(
34
+ field_name=field_name,
35
+ message='Content type should be valid IANA media type',
36
+ )]
37
+ return []
38
+
39
+
40
+ def _validate_extension(field_name: str, value) -> list[ValidationError]:
41
+ if value is not None and re.match(consts.REGEX_ORGANIZATION_ID, value) is None:
42
+ return [ValidationError(
43
+ field_name=field_name,
44
+ message='File extension should contain only letters, numbers, '
45
+ 'and periods (inside-only)',
46
+ )]
47
+ return []
48
+
49
+
50
+ def _validate_organization_id(field_name: str, value) -> list[ValidationError]:
51
+ if value is not None and re.match(consts.REGEX_ORGANIZATION_ID, value) is None:
52
+ return [ValidationError(
53
+ field_name=field_name,
54
+ message='Organization ID may contain only letters, numbers, '
55
+ 'periods, dashes, and underscores',
56
+ )]
57
+ return []
58
+
59
+
60
+ def _validate_template_id(field_name: str, value) -> list[ValidationError]:
61
+ if value is not None and re.match(consts.REGEX_TEMPLATE_ID, value) is None:
62
+ return [ValidationError(
63
+ field_name=field_name,
64
+ message='Template ID may contain only letters, numbers, '
65
+ 'periods, dashes, and underscores',
66
+ )]
67
+ return []
68
+
69
+
70
+ def _validate_km_id(field_name: str, value) -> list[ValidationError]:
71
+ if value is not None and re.match(consts.REGEX_KM_ID, value) is None:
72
+ return [ValidationError(
73
+ field_name=field_name,
74
+ message='KM ID may contain only letters, numbers, '
75
+ 'periods, dashes, and underscores',
76
+ )]
77
+ return []
78
+
79
+
80
+ def _validate_version(field_name: str, value) -> list[ValidationError]:
81
+ if value is not None and re.match(consts.REGEX_SEMVER, value) is None:
82
+ return [ValidationError(
83
+ field_name=field_name,
84
+ message='Version must be in semver format <NUM>.<NUM>.<NUM>',
85
+ )]
86
+ return []
87
+
88
+
89
+ def _validate_natural(field_name: str, value) -> list[ValidationError]:
90
+ if value is not None and (not isinstance(value, int) or value < 1):
91
+ return [ValidationError(
92
+ field_name=field_name,
93
+ message='It must be positive integer',
94
+ )]
95
+ return []
96
+
97
+
98
+ def _validate_metamodel_version(field_name: str, value) -> list[ValidationError]:
99
+ if isinstance(value, int) and value > 0:
100
+ return []
101
+ if isinstance(value, str) and '.' in value:
102
+ parts = value.split('.')
103
+ if len(parts) != 2:
104
+ return [ValidationError(
105
+ field_name=field_name,
106
+ message='It must be in format <MAJOR>.<MINOR>',
107
+ )]
108
+ try:
109
+ major = int(parts[0])
110
+ minor = int(parts[1])
111
+ if major < 1 or minor < 0:
112
+ return [ValidationError(
113
+ field_name=field_name,
114
+ message='It must be in format <MAJOR>.<MINOR> '
115
+ 'with MAJOR >= 1 and MINOR >= 0',
116
+ )]
117
+ return []
118
+ except ValueError:
119
+ return [ValidationError(
120
+ field_name=field_name,
121
+ message='It must be in format <MAJOR>.<MINOR> '
122
+ 'with MAJOR >= 1 and MINOR >= 0',
123
+ )]
124
+ return [ValidationError(
125
+ field_name=field_name,
126
+ message='It must be a positive integer or in format <MAJOR>.<MINOR>',
127
+ )]
128
+
129
+
130
+ def _validate_package_id(field_name: str, value: str) -> list[ValidationError]:
131
+ res = []
132
+ if value is None:
133
+ return res
134
+ if not isinstance(value, str):
135
+ return [ValidationError(
136
+ field_name=field_name,
137
+ message='Package ID is not a string',
138
+ )]
139
+ parts = value.split(':')
140
+ if len(parts) != 3:
141
+ res.append(ValidationError(
142
+ field_name=field_name,
143
+ message=f'Package ID is not valid (only {len(parts)} parts)',
144
+ ))
145
+ if re.match(consts.REGEX_ORGANIZATION_ID, parts[0]) is None:
146
+ res.append(ValidationError(
147
+ field_name=field_name,
148
+ message='Package ID contains invalid organization id',
149
+ ))
150
+ if re.match(consts.REGEX_KM_ID, parts[1]) is None:
151
+ res.append(ValidationError(
152
+ field_name=field_name,
153
+ message='Package ID contains invalid KM id',
154
+ ))
155
+ if re.match(consts.REGEX_SEMVER, parts[2]) is None:
156
+ res.append(ValidationError(
157
+ field_name=field_name,
158
+ message='Package ID contains invalid version',
159
+ ))
160
+ return res
161
+
162
+
163
+ def _validate_jinja_options(field_name: str, value: dict[str, str]) -> list[ValidationError]:
164
+ res = []
165
+ if value is None:
166
+ return res
167
+ for k in ('template', 'content-type', 'extension'):
168
+ if k not in value:
169
+ res.append(ValidationError(
170
+ field_name=field_name,
171
+ message='Jinja option cannot be left out',
172
+ ))
173
+ elif value[k] is None or not isinstance(value[k], str) or len(value[k]) == 0:
174
+ res.append(ValidationError(
175
+ field_name=field_name,
176
+ message='Jinja option cannot be empty',
177
+ ))
178
+ if 'content-type' in value:
179
+ res.extend(_validate_content_type(
180
+ field_name=f'{field_name}.content-type',
181
+ value=value['content-type'],
182
+ ))
183
+ return res
184
+
185
+
186
+ class GenericValidator:
187
+
188
+ def __init__(self, rules):
189
+ self.rules = rules
190
+
191
+ def validate_field(self, entity, field_name: str):
192
+ for validator in self.rules.get(field_name, []):
193
+ err = validator(field_name, getattr(entity, field_name))
194
+ if len(err) != 0:
195
+ raise err[0]
196
+
197
+ def validate(self, entity, field_name_prefix: str = ''):
198
+ for field_name, validators in self.rules.items():
199
+ if field_name.startswith('__'):
200
+ continue
201
+ for validator in validators:
202
+ err = validator(field_name_prefix + field_name, getattr(entity, field_name))
203
+ if len(err) != 0:
204
+ raise err[0]
205
+ if '__all' in self.rules:
206
+ err = self.rules['__all'](field_name_prefix, entity)
207
+ if len(err) != 0:
208
+ raise err[0]
209
+
210
+ def collect_errors(self, entity, field_name_prefix: str = '') -> list[ValidationError]:
211
+ result = []
212
+ for field_name, validators in self.rules.items():
213
+ if field_name.startswith('__'):
214
+ continue
215
+ for validator in validators:
216
+ result.extend(validator(
217
+ field_name=f'{field_name_prefix}{field_name}',
218
+ value=getattr(entity, field_name),
219
+ ))
220
+ if '__all' in self.rules:
221
+ result.extend(self.rules['__all'](field_name_prefix, entity))
222
+ return result
223
+
224
+
225
+ PackageFilterValidator = GenericValidator({
226
+ 'organization_id': [_validate_organization_id],
227
+ 'km_id': [_validate_km_id],
228
+ 'min_version': [_validate_version],
229
+ 'max_version': [_validate_version],
230
+ })
231
+
232
+
233
+ def _validate_package_filters(field_name: str, value: list[PackageFilter]) -> list[ValidationError]:
234
+ res = []
235
+ for v in value:
236
+ res.extend(PackageFilterValidator.collect_errors(v, field_name_prefix=f'{field_name}.'))
237
+ return res
238
+
239
+
240
+ def _validate_step(field_name_prefix: str, value: Step) -> list[ValidationError]:
241
+ if value.name == 'jinja':
242
+ return _validate_jinja_options(f'{field_name_prefix}options', value.options)
243
+ return []
244
+
245
+
246
+ StepValidator = GenericValidator({
247
+ 'name': [_validate_non_empty],
248
+ 'options': [],
249
+ '__all': _validate_step,
250
+ })
251
+
252
+
253
+ def _validate_steps(field_name: str, value: list[Step]) -> list[ValidationError]:
254
+ res = []
255
+ for v in value:
256
+ res.extend(StepValidator.collect_errors(v, field_name_prefix=f'{field_name}.'))
257
+ return res
258
+
259
+
260
+ FormatValidator = GenericValidator({
261
+ 'uuid': [_validate_required, _validate_non_empty],
262
+ 'name': [_validate_required, _validate_non_empty],
263
+ 'icon': [_validate_required, _validate_non_empty],
264
+ 'steps': [_validate_required, _validate_steps],
265
+ })
266
+
267
+
268
+ def _validate_formats(field_name: str, value: list[Format]) -> list[ValidationError]:
269
+ res = []
270
+ uuids = set()
271
+ for v in value:
272
+ if v.uuid in uuids:
273
+ res.append(ValidationError(field_name, f'Duplicate format UUID {v.uuid}'))
274
+ uuids.add(v.uuid)
275
+ res.extend(FormatValidator.collect_errors(v, field_name_prefix=f'{field_name}.'))
276
+ return res
277
+
278
+
279
+ TemplateValidator = GenericValidator({
280
+ 'template_id': [_validate_required, _validate_template_id],
281
+ 'organization_id': [_validate_required, _validate_organization_id],
282
+ 'version': [_validate_required, _validate_version],
283
+ 'name': [_validate_required, _validate_non_empty],
284
+ 'description': [_validate_required, _validate_non_empty],
285
+ 'readme': [_validate_required, _validate_non_empty],
286
+ 'license': [_validate_required, _validate_non_empty],
287
+ 'metamodel_version': [_validate_metamodel_version, _validate_required],
288
+ 'allowed_packages': [_validate_package_filters],
289
+ 'formats': [_validate_required, _validate_formats],
290
+ })
@@ -1,50 +1,49 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: dsw-tdk
3
- Version: 3.13.0
3
+ Version: 4.27.0
4
4
  Summary: Data Stewardship Wizard Template Development Toolkit
5
- Home-page: https://github.com/ds-wizard/dsw-tdk
5
+ Keywords: documents,dsw,jinja2,template,toolkit
6
6
  Author: Marek Suchánek
7
- Author-email: marek.suchanek@ds-wizard.org
8
- License: Apache-2.0
9
- Keywords: dsw template toolkit jinja documents
10
- Platform: UNKNOWN
7
+ Author-email: Marek Suchánek <marek.suchanek@ds-wizard.org>
8
+ License: Apache License 2.0
11
9
  Classifier: Framework :: AsyncIO
10
+ Classifier: Development Status :: 5 - Production/Stable
12
11
  Classifier: License :: OSI Approved :: Apache Software License
13
12
  Classifier: Programming Language :: Python
14
- Classifier: Programming Language :: Python :: Implementation :: CPython
15
- Classifier: Programming Language :: Python :: 3
16
- Classifier: Programming Language :: Python :: 3.6
17
- Classifier: Programming Language :: Python :: 3.8
18
- Classifier: Programming Language :: Python :: 3.7
19
- Classifier: Programming Language :: Python :: 3.9
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
20
17
  Classifier: Topic :: Internet :: WWW/HTTP
21
- Classifier: Topic :: Internet :: WWW/HTTP :: Indexing/Search
22
- Classifier: Topic :: Internet :: WWW/HTTP :: Site Management
23
18
  Classifier: Topic :: Utilities
24
- Requires-Python: >=3.6, <4
25
- Description-Content-Type: text/markdown
26
- License-File: LICENSE
27
19
  Requires-Dist: aiohttp
28
20
  Requires-Dist: click
29
21
  Requires-Dist: colorama
30
22
  Requires-Dist: humanize
31
- Requires-Dist: Jinja2
23
+ Requires-Dist: jinja2
32
24
  Requires-Dist: multidict
33
25
  Requires-Dist: pathspec
34
26
  Requires-Dist: python-dotenv
35
27
  Requires-Dist: python-slugify
36
- Requires-Dist: watchgod
28
+ Requires-Dist: watchfiles
29
+ Requires-Dist: pytest ; extra == 'test'
30
+ Requires-Python: >=3.10, <4
31
+ Project-URL: Homepage, https://ds-wizard.org
32
+ Project-URL: Repository, https://github.com/ds-wizard/engine-tools
33
+ Project-URL: Documentation, https://guide.ds-wizard.org
34
+ Project-URL: Issues, https://github.com/ds-wizard/ds-wizard/issues
35
+ Provides-Extra: test
36
+ Description-Content-Type: text/markdown
37
37
 
38
38
  # dsw-tdk
39
39
 
40
40
  [![User Guide](https://img.shields.io/badge/docs-User%20Guide-informational)](https://guide.ds-wizard.org)
41
- [![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/ds-wizard/dsw-tdk)](https://github.com/ds-wizard/dsw-tdk/releases)
41
+ [![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/ds-wizard/engine-tools)](https://github.com/ds-wizard/engine-tools/releases)
42
42
  [![PyPI](https://img.shields.io/pypi/v/dsw-tdk)](https://pypi.org/project/dsw-tdk/)
43
- [![Codacy Badge](https://app.codacy.com/project/badge/Grade/6c98735aecb144abaaee19361d7c8976)](https://www.codacy.com/gh/ds-wizard/dsw-tdk/dashboard?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=ds-wizard/dsw-tdk&amp;utm_campaign=Badge_Grade)
44
- [![DSW TDK CI](https://github.com/ds-wizard/dsw-tdk/workflows/DSW%20TDK%20CI/badge.svg)](https://github.com/ds-wizard/dsw-tdk/actions)
45
43
  [![Docker Pulls](https://img.shields.io/docker/pulls/datastewardshipwizard/dsw-tdk)](https://hub.docker.com/r/datastewardshipwizard/dsw-tdk)
46
- [![LICENSE](https://img.shields.io/github/license/ds-wizard/dsw-tdk)](LICENSE)
44
+ [![LICENSE](https://img.shields.io/github/license/ds-wizard/engine-tools)](LICENSE)
47
45
  [![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/4975/badge)](https://bestpractices.coreinfrastructure.org/projects/4975)
46
+ [![Python Version](https://img.shields.io/badge/Python-%E2%89%A5%203.7-blue)](https://python.org)
48
47
 
49
48
  *Template Development Kit for [Data Stewardship Wizard](https://ds-wizard.org)*
50
49
 
@@ -102,12 +101,10 @@ For further information, visit our [documentation](https://docs.ds-wizard.org).
102
101
 
103
102
  You can use the following environment variables to avoid repeating CLI options.
104
103
 
105
- - `DSW_API` = URL of DSW API you want to use, e.g., https://api.demo.ds-wizard.org (notice that it is **not** the URL of client, you can find it out by clicking Help > About in DSW)
106
- - Used when `--api-server` not specified
107
- - `DSW_USERNAME` = username (email address) that you use to login with your admin account
108
- - Used when `--username` not specified
109
- - `DSW_PASSWORD` = corresponding password to login with your admin account
110
- - Used when `--password` not specified
104
+ - `DSW_API_URL` = URL of DSW API you want to use, e.g., https://api.demo.ds-wizard.org (notice that it is **not** the URL of client, you can find it out by clicking Help > About in DSW)
105
+ - Used when `--api-url` not specified
106
+ - `DSW_API_KEY` = API Key of the user authorized to manage document templates
107
+ - Used when `--api-key` not specified
111
108
 
112
109
  You can also use them in `.env` file which is automatically loaded from current directory or specify it using `--dot-env` option:
113
110
 
@@ -138,7 +135,7 @@ $ dsw-tdk --debug list
138
135
 
139
136
  ## Requirements
140
137
 
141
- - [Python 3.7+](https://www.python.org/downloads/)
138
+ - [Python 3.8+](https://www.python.org/downloads/)
142
139
  - DSW instance with matching version (e.g. a local one using [Docker](https://github.com/ds-wizard/dsw-deployment-example))
143
140
  - Admin credentials (email+password) to the DSW instance
144
141
 
@@ -155,5 +152,3 @@ For more information read [CONTRIBUTING](CONTRIBUTING.md).
155
152
  ## License
156
153
 
157
154
  This project is licensed under the Apache 2 License - see the [LICENSE](LICENSE) file for more details.
158
-
159
-
@@ -0,0 +1,20 @@
1
+ dsw/tdk/__init__.py,sha256=ofVgqzsHfzERknIDuS08mjNh6cCbchLcrbuYEtJBUl0,291
2
+ dsw/tdk/__main__.py,sha256=7atdpOyr41OPmApZ4ktsGSF31GSIfEY9xOBzJZ-azYQ,85
3
+ dsw/tdk/api_client.py,sha256=TBVzMNvOARPvRfFIOP9IXytkAeY6uovegfN9W1lCOzM,15377
4
+ dsw/tdk/build_info.py,sha256=StkP0pJCmLTOw9Bgu2hzdlQFIe6WTZkXtOP8HsXUjDw,381
5
+ dsw/tdk/cli.py,sha256=6Cu1movV2_LkA-pNTQB1dSREYqTNOnArqPiJIusQ9Do,28137
6
+ dsw/tdk/config.py,sha256=4G2VfJ2DIm-NLQdtCHg6y_cEi0tDf9m6mbAgqEGeNeY,4782
7
+ dsw/tdk/consts.py,sha256=f2tztF4m7s4mbj3qNH3uHpAgEOFIK_fzwZerIhtv814,714
8
+ dsw/tdk/core.py,sha256=7DC63ohGZ6ZZhuk3XjqH6NUHFvrg-hANSRlYmfI-wLA,27457
9
+ dsw/tdk/model.py,sha256=tJZnrQE88dXUZZ3b_UbQjeHm1W_qwbCeqscl9IK3r80,17527
10
+ dsw/tdk/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
+ dsw/tdk/templates/LICENSE.j2,sha256=6WzK06169rxZ4V_boYgejcZkw-1Up_WoU18iI3Gbkfs,60
12
+ dsw/tdk/templates/README.md.j2,sha256=FzUABeMM8To0oT48Kytoox64uAZ8F7FSAAXgpyKzqdU,247
13
+ dsw/tdk/templates/env.j2,sha256=yUaY7ZLbjdG45-_q6KWi-YnX9bjP7_lfXrowhJF1he0,98
14
+ dsw/tdk/templates/starter.j2,sha256=JKJIkiXsmBO4P6pKXM4IvSz3JgJhqCnaZI1NVfPPnXg,246
15
+ dsw/tdk/utils.py,sha256=qTqar4RUqRMvIO_RFyoSgeeTvZy8qmyKLWoE9XoPY9k,5678
16
+ dsw/tdk/validation.py,sha256=KD2smmH2uDBVxx35eczJmYNnzJcq8g8L5LDu1hkbxe0,10088
17
+ dsw_tdk-4.27.0.dist-info/WHEEL,sha256=fAguSjoiATBe7TNBkJwOjyL1Tt4wwiaQGtNtjRPNMQA,80
18
+ dsw_tdk-4.27.0.dist-info/entry_points.txt,sha256=-55ASKbqu423XqpytOZyuTZSNWus2MsQuFXl2p_nCfc,42
19
+ dsw_tdk-4.27.0.dist-info/METADATA,sha256=pl94tCMYLsQx9VWtQl2424NWAXSogP-mwWKI16EGp9M,6144
20
+ dsw_tdk-4.27.0.dist-info/RECORD,,
@@ -1,5 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.37.1)
2
+ Generator: uv 0.9.28
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
-
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ dsw-tdk = dsw.tdk:main
3
+
dsw_tdk/__main__.py DELETED
@@ -1,3 +0,0 @@
1
- from dsw_tdk.cli import main
2
-
3
- main(ctx=None)