dsw-tdk 4.21.0__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 +35 -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.21.0.dist-info → dsw_tdk-4.22.0.dist-info}/METADATA +1 -1
- dsw_tdk-4.22.0.dist-info/RECORD +21 -0
- dsw_tdk-4.21.0.dist-info/RECORD +0 -20
- {dsw_tdk-4.21.0.dist-info → dsw_tdk-4.22.0.dist-info}/WHEEL +0 -0
- {dsw_tdk-4.21.0.dist-info → dsw_tdk-4.22.0.dist-info}/entry_points.txt +0 -0
- {dsw_tdk-4.21.0.dist-info → dsw_tdk-4.22.0.dist-info}/licenses/LICENSE +0 -0
- {dsw_tdk-4.21.0.dist-info → dsw_tdk-4.22.0.dist-info}/top_level.txt +0 -0
dsw/tdk/config.py
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import configparser
|
|
2
|
+
import dataclasses
|
|
3
|
+
import os
|
|
4
|
+
import pathlib
|
|
5
|
+
|
|
6
|
+
import dotenv
|
|
7
|
+
|
|
8
|
+
from .consts import DEFAULT_ENCODING
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _rectify_api_url(api_url: str | None) -> str:
|
|
12
|
+
if not api_url or not isinstance(api_url, str):
|
|
13
|
+
return ''
|
|
14
|
+
return api_url.rstrip('/')
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _rectify_api_key(api_key: str | None) -> str:
|
|
18
|
+
if not api_key or not isinstance(api_key, str):
|
|
19
|
+
return ''
|
|
20
|
+
return api_key.strip()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclasses.dataclass
|
|
24
|
+
class TDKWizardEnv:
|
|
25
|
+
api_url: str
|
|
26
|
+
api_key: str
|
|
27
|
+
|
|
28
|
+
def rectify(self):
|
|
29
|
+
self.api_url = _rectify_api_url(self.api_url)
|
|
30
|
+
self.api_key = _rectify_api_key(self.api_key)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class TDKConfig:
|
|
34
|
+
LOCAL_CONFIG = '_local'
|
|
35
|
+
HOME_CONFIG = pathlib.Path.home() / '.dsw-tdk' / 'config.cfg'
|
|
36
|
+
|
|
37
|
+
def __init__(self):
|
|
38
|
+
self.shared_envs = {} # type: dict[str, TDKWizardEnv]
|
|
39
|
+
self.local_env = TDKWizardEnv(
|
|
40
|
+
api_url='',
|
|
41
|
+
api_key='',
|
|
42
|
+
)
|
|
43
|
+
self.current_env_name = self.LOCAL_CONFIG
|
|
44
|
+
self.default_env_name = None # type: str | None
|
|
45
|
+
|
|
46
|
+
def load_dotenv(self, path: pathlib.Path):
|
|
47
|
+
try:
|
|
48
|
+
if path.exists():
|
|
49
|
+
dotenv.load_dotenv(path)
|
|
50
|
+
except Exception as e:
|
|
51
|
+
print(f"Error loading .env file: {e}")
|
|
52
|
+
api_url = os.getenv('DSW_API_URL', '')
|
|
53
|
+
api_key = os.getenv('DSW_API_KEY', '')
|
|
54
|
+
|
|
55
|
+
if not api_url or not api_key:
|
|
56
|
+
return
|
|
57
|
+
|
|
58
|
+
self.current_env_name = self.LOCAL_CONFIG
|
|
59
|
+
self.local_env = TDKWizardEnv(
|
|
60
|
+
api_url=api_url,
|
|
61
|
+
api_key=api_key,
|
|
62
|
+
)
|
|
63
|
+
self.local_env.rectify()
|
|
64
|
+
|
|
65
|
+
def load_home_config(self):
|
|
66
|
+
config_path = self.HOME_CONFIG
|
|
67
|
+
if not config_path.exists():
|
|
68
|
+
return
|
|
69
|
+
|
|
70
|
+
config = configparser.ConfigParser()
|
|
71
|
+
config.read(config_path)
|
|
72
|
+
for section in config.sections():
|
|
73
|
+
if section == 'default':
|
|
74
|
+
self.default_env_name = config.get(section, 'env', fallback=None)
|
|
75
|
+
elif section.startswith('env:'):
|
|
76
|
+
env_name = section[4:]
|
|
77
|
+
if env_name not in self.shared_envs:
|
|
78
|
+
self.shared_envs[env_name] = TDKWizardEnv(
|
|
79
|
+
api_url=config.get(section, 'api_url', fallback=''),
|
|
80
|
+
api_key=config.get(section, 'api_key', fallback=''),
|
|
81
|
+
)
|
|
82
|
+
self.shared_envs[env_name].rectify()
|
|
83
|
+
if self.default_env_name is not None:
|
|
84
|
+
self.current_env_name = self.default_env_name
|
|
85
|
+
|
|
86
|
+
@property
|
|
87
|
+
def env(self) -> TDKWizardEnv:
|
|
88
|
+
if self.current_env_name == self.LOCAL_CONFIG:
|
|
89
|
+
return self.local_env
|
|
90
|
+
return self.shared_envs[self.current_env_name]
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
def has_api_url(self) -> bool:
|
|
94
|
+
return self.env.api_url != ''
|
|
95
|
+
|
|
96
|
+
def set_api_url(self, api_url: str):
|
|
97
|
+
self.env.api_url = _rectify_api_url(api_url)
|
|
98
|
+
|
|
99
|
+
@property
|
|
100
|
+
def has_api_key(self) -> bool:
|
|
101
|
+
return self.env.api_key != ''
|
|
102
|
+
|
|
103
|
+
def set_api_key(self, api_key: str):
|
|
104
|
+
self.env.api_key = _rectify_api_key(api_key)
|
|
105
|
+
|
|
106
|
+
def use_local_env(self):
|
|
107
|
+
self.current_env_name = self.LOCAL_CONFIG
|
|
108
|
+
|
|
109
|
+
def switch_current_env(self, env_name: str | None):
|
|
110
|
+
if env_name not in self.shared_envs:
|
|
111
|
+
raise ValueError(f'Environment "{env_name}" does not exist')
|
|
112
|
+
self.current_env_name = env_name
|
|
113
|
+
|
|
114
|
+
@property
|
|
115
|
+
def is_default_env(self) -> bool:
|
|
116
|
+
return self.current_env_name == self.default_env_name
|
|
117
|
+
|
|
118
|
+
@property
|
|
119
|
+
def env_names(self) -> list[str]:
|
|
120
|
+
return sorted(env_name for env_name in self.shared_envs)
|
|
121
|
+
|
|
122
|
+
def add_shared_env(self, name: str, api_url: str, api_key: str):
|
|
123
|
+
if name == self.LOCAL_CONFIG:
|
|
124
|
+
raise ValueError(f'Environment name "{self.LOCAL_CONFIG}" is reserved')
|
|
125
|
+
if name in self.shared_envs:
|
|
126
|
+
raise ValueError(f'Environment "{name}" already exists')
|
|
127
|
+
self.shared_envs[name] = TDKWizardEnv(
|
|
128
|
+
api_url=api_url,
|
|
129
|
+
api_key=api_key,
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
def persist(self, force: bool):
|
|
133
|
+
output = self.HOME_CONFIG
|
|
134
|
+
if output.exists() and not force:
|
|
135
|
+
raise FileExistsError(f'File {output.as_posix()} already exists (not forced)')
|
|
136
|
+
if not output.parent.exists():
|
|
137
|
+
output.parent.mkdir(parents=True, exist_ok=True)
|
|
138
|
+
|
|
139
|
+
config = configparser.ConfigParser()
|
|
140
|
+
config.add_section('default')
|
|
141
|
+
if self.default_env_name:
|
|
142
|
+
config.set('default', 'env', self.default_env_name)
|
|
143
|
+
|
|
144
|
+
for env_name, env in self.shared_envs.items():
|
|
145
|
+
section_name = f'env:{env_name}'
|
|
146
|
+
config.add_section(section_name)
|
|
147
|
+
config.set(section_name, 'api_url', env.api_url)
|
|
148
|
+
config.set(section_name, 'api_key', env.api_key)
|
|
149
|
+
|
|
150
|
+
with open(output, 'w', encoding=DEFAULT_ENCODING) as configfile:
|
|
151
|
+
config.write(configfile)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
CONFIG = TDKConfig()
|
dsw/tdk/consts.py
CHANGED
|
@@ -4,8 +4,10 @@ import re
|
|
|
4
4
|
import pathspec
|
|
5
5
|
|
|
6
6
|
APP = 'dsw-tdk'
|
|
7
|
-
VERSION = '4.
|
|
8
|
-
|
|
7
|
+
VERSION = '4.22.0'
|
|
8
|
+
METAMODEL_VERSION_MAJOR = 17
|
|
9
|
+
METAMODEL_VERSION_MINOR = 0
|
|
10
|
+
METAMODEL_VERSION = f'{METAMODEL_VERSION_MAJOR}.{METAMODEL_VERSION_MINOR}'
|
|
9
11
|
|
|
10
12
|
REGEX_SEMVER = re.compile(r'^[0-9]+\.[0-9]+\.[0-9]+$')
|
|
11
13
|
REGEX_WIZARD_ID = re.compile(r'^[a-zA-Z0-9-_.]+$')
|
dsw/tdk/core.py
CHANGED
|
@@ -11,10 +11,10 @@ import zipfile
|
|
|
11
11
|
|
|
12
12
|
import watchfiles
|
|
13
13
|
|
|
14
|
-
from .api_client import
|
|
14
|
+
from .api_client import WizardAPIClient, WizardCommunicationError
|
|
15
15
|
from .consts import DEFAULT_ENCODING, REGEX_SEMVER
|
|
16
16
|
from .model import TemplateProject, Template, TemplateFile, TemplateFileType
|
|
17
|
-
from .utils import UUIDGen
|
|
17
|
+
from .utils import UUIDGen
|
|
18
18
|
from .validation import ValidationError, TemplateValidator
|
|
19
19
|
|
|
20
20
|
|
|
@@ -33,22 +33,23 @@ class TDKProcessingError(RuntimeError):
|
|
|
33
33
|
|
|
34
34
|
|
|
35
35
|
METAMODEL_VERSION_SUPPORT = {
|
|
36
|
-
1: (2, 5, 0),
|
|
37
|
-
2: (2, 6, 0),
|
|
38
|
-
3: (2, 12, 0),
|
|
39
|
-
4: (3, 2, 0),
|
|
40
|
-
5: (3, 5, 0),
|
|
41
|
-
6: (3, 6, 0),
|
|
42
|
-
7: (3, 7, 0),
|
|
43
|
-
8: (3, 8, 0),
|
|
44
|
-
9: (3, 10, 0),
|
|
45
|
-
10: (3, 12, 0),
|
|
46
|
-
11: (3, 20, 0),
|
|
47
|
-
12: (4, 1, 0),
|
|
48
|
-
13: (4, 3, 0),
|
|
49
|
-
14: (4, 10, 0),
|
|
50
|
-
15: (4, 12, 0),
|
|
51
|
-
16: (4, 13, 0),
|
|
36
|
+
'1.0': (2, 5, 0),
|
|
37
|
+
'2.0': (2, 6, 0),
|
|
38
|
+
'3.0': (2, 12, 0),
|
|
39
|
+
'4.0': (3, 2, 0),
|
|
40
|
+
'5.0': (3, 5, 0),
|
|
41
|
+
'6.0': (3, 6, 0),
|
|
42
|
+
'7.0': (3, 7, 0),
|
|
43
|
+
'8.0': (3, 8, 0),
|
|
44
|
+
'9.0': (3, 10, 0),
|
|
45
|
+
'10.0': (3, 12, 0),
|
|
46
|
+
'11.0': (3, 20, 0),
|
|
47
|
+
'12.0': (4, 1, 0),
|
|
48
|
+
'13.0': (4, 3, 0),
|
|
49
|
+
'14.0': (4, 10, 0),
|
|
50
|
+
'15.0': (4, 12, 0),
|
|
51
|
+
'16.0': (4, 13, 0),
|
|
52
|
+
'17.0': (4, 22, 0),
|
|
52
53
|
}
|
|
53
54
|
|
|
54
55
|
|
|
@@ -56,31 +57,40 @@ METAMODEL_VERSION_SUPPORT = {
|
|
|
56
57
|
class TDKCore:
|
|
57
58
|
|
|
58
59
|
def _check_metamodel_version(self):
|
|
59
|
-
|
|
60
|
+
hint = 'Fix your metamodelVersion in template.json and/or visit docs'
|
|
61
|
+
mm_ver = str(self.safe_template.metamodel_version)
|
|
62
|
+
try:
|
|
63
|
+
if '.' not in mm_ver:
|
|
64
|
+
mm_ver = f'{mm_ver}.0'
|
|
65
|
+
mm_major, mm_minor = map(int, mm_ver.split('.'))
|
|
66
|
+
except ValueError as e:
|
|
67
|
+
raise TDKProcessingError(f'Invalid metamodel version format: {mm_ver}', hint) from e
|
|
60
68
|
api_version = self.remote_version.split('~', maxsplit=1)[0]
|
|
61
69
|
if '-' in api_version:
|
|
62
70
|
api_version = api_version.split('-', maxsplit=1)[0]
|
|
63
71
|
if 'v' == api_version[0]:
|
|
64
72
|
api_version = api_version[1:]
|
|
65
73
|
if not re.match(REGEX_SEMVER, api_version):
|
|
66
|
-
self.logger.warning('Using non-stable release of API: %s',
|
|
74
|
+
self.logger.warning('Using non-stable release of API: %s',
|
|
75
|
+
self.remote_version)
|
|
67
76
|
return
|
|
68
77
|
parts = api_version.split('.')
|
|
69
78
|
ver = (int(parts[0]), int(parts[1]), int(parts[2]))
|
|
70
79
|
vtag = f'v{ver[0]}.{ver[1]}.{ver[2]}'
|
|
71
|
-
hint = 'Fix your metamodelVersion in template.json and/or visit docs'
|
|
72
80
|
if mm_ver not in METAMODEL_VERSION_SUPPORT:
|
|
73
81
|
raise TDKProcessingError(f'Unknown metamodel version: {mm_ver}', hint)
|
|
74
|
-
|
|
75
|
-
if
|
|
76
|
-
raise TDKProcessingError(f'Unsupported
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
82
|
+
map_reverse = {f'v{v[0]}.{v[1]}.{v[2]}': k for k, v in METAMODEL_VERSION_SUPPORT.items()}
|
|
83
|
+
if vtag not in map_reverse:
|
|
84
|
+
raise TDKProcessingError(f'Unsupported API version: {vtag}', hint)
|
|
85
|
+
api_mm_major, api_mm_minor = map(int, map_reverse[vtag].split('.'))
|
|
86
|
+
if mm_major != api_mm_major or mm_minor < api_mm_minor:
|
|
87
|
+
raise TDKProcessingError(
|
|
88
|
+
f'Unsupported metamodel version {mm_ver} for API version {api_version}',
|
|
89
|
+
hint,
|
|
90
|
+
)
|
|
81
91
|
|
|
82
92
|
def __init__(self, template: Template | None = None, project: TemplateProject | None = None,
|
|
83
|
-
client:
|
|
93
|
+
client: WizardAPIClient | None = None, logger: logging.Logger | None = None):
|
|
84
94
|
self.template = template
|
|
85
95
|
self.project = project
|
|
86
96
|
self.client = client
|
|
@@ -106,14 +116,14 @@ class TDKCore:
|
|
|
106
116
|
return self.project
|
|
107
117
|
|
|
108
118
|
@property
|
|
109
|
-
def safe_client(self) ->
|
|
119
|
+
def safe_client(self) -> WizardAPIClient:
|
|
110
120
|
if self.client is None:
|
|
111
121
|
raise RuntimeError('No DSW API client specified')
|
|
112
122
|
return self.client
|
|
113
123
|
|
|
114
124
|
async def init_client(self, api_url: str, api_key: str):
|
|
115
125
|
self.logger.info('Connecting to %s', api_url)
|
|
116
|
-
self.client =
|
|
126
|
+
self.client = WizardAPIClient(api_url=api_url, api_key=api_key)
|
|
117
127
|
self.remote_version = await self.client.get_api_version()
|
|
118
128
|
user = await self.client.get_current_user()
|
|
119
129
|
self.logger.info('Successfully authenticated as %s %s (%s)',
|
|
@@ -135,13 +145,13 @@ class TDKCore:
|
|
|
135
145
|
self.logger.debug('Retrieving template draft files')
|
|
136
146
|
files = await self.safe_client.get_template_draft_files(remote_id=template_id)
|
|
137
147
|
self.logger.info('Retrieved %s file(s)', len(files))
|
|
138
|
-
for
|
|
139
|
-
self.safe_template.files[
|
|
148
|
+
for file in files:
|
|
149
|
+
self.safe_template.files[file.filename.as_posix()] = file
|
|
140
150
|
self.logger.debug('Retrieving template draft assets')
|
|
141
151
|
assets = await self.safe_client.get_template_draft_assets(remote_id=template_id)
|
|
142
152
|
self.logger.info('Retrieved %s asset(s)', len(assets))
|
|
143
|
-
for
|
|
144
|
-
self.safe_template.files[
|
|
153
|
+
for asset in assets:
|
|
154
|
+
self.safe_template.files[asset.filename.as_posix()] = asset
|
|
145
155
|
|
|
146
156
|
async def download_bundle(self, template_id: str) -> bytes:
|
|
147
157
|
self.logger.info('Retrieving template %s bundle', template_id)
|
|
@@ -215,103 +225,105 @@ class TDKCore:
|
|
|
215
225
|
)
|
|
216
226
|
await self.store_remote_files()
|
|
217
227
|
|
|
218
|
-
async def _update_template_file(self,
|
|
228
|
+
async def _update_template_file(self, remote_file: TemplateFile, local_file: TemplateFile,
|
|
219
229
|
project_update: bool = False):
|
|
220
230
|
try:
|
|
221
231
|
self.logger.debug('Updating existing remote %s %s (%s) started',
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
if
|
|
232
|
+
remote_file.remote_type.value, remote_file.filename.as_posix(),
|
|
233
|
+
remote_file.remote_id)
|
|
234
|
+
local_file.remote_id = remote_file.remote_id
|
|
235
|
+
if remote_file.remote_type == TemplateFileType.ASSET:
|
|
226
236
|
result = await self.safe_client.put_template_draft_asset_content(
|
|
227
237
|
remote_id=self.remote_id,
|
|
228
|
-
|
|
238
|
+
file=local_file,
|
|
229
239
|
)
|
|
230
240
|
else:
|
|
231
241
|
result = await self.safe_client.put_template_draft_file_content(
|
|
232
242
|
remote_id=self.remote_id,
|
|
233
|
-
|
|
243
|
+
file=local_file,
|
|
234
244
|
)
|
|
235
245
|
self.logger.debug('Updating existing remote %s %s (%s) finished: %s',
|
|
236
|
-
|
|
237
|
-
|
|
246
|
+
remote_file.remote_type.value, remote_file.filename.as_posix(),
|
|
247
|
+
remote_file.remote_id, 'ok' if result else 'failed')
|
|
238
248
|
if project_update and result:
|
|
239
249
|
self.safe_project.update_template_file(result)
|
|
240
250
|
except Exception as e1:
|
|
241
251
|
try:
|
|
242
252
|
self.logger.debug('Trying to delete/create due to: %s', str(e1))
|
|
243
|
-
await self._delete_template_file(
|
|
244
|
-
await self._create_template_file(
|
|
253
|
+
await self._delete_template_file(file=remote_file)
|
|
254
|
+
await self._create_template_file(file=local_file, project_update=True)
|
|
245
255
|
except Exception as e2:
|
|
246
256
|
self.logger.error('Failed to update existing remote %s %s: %s',
|
|
247
|
-
|
|
248
|
-
|
|
257
|
+
remote_file.remote_type.value,
|
|
258
|
+
remote_file.filename.as_posix(), e2)
|
|
249
259
|
|
|
250
|
-
async def _delete_template_file(self,
|
|
260
|
+
async def _delete_template_file(self, file: TemplateFile, project_update: bool = False):
|
|
251
261
|
try:
|
|
252
262
|
self.logger.debug('Deleting existing remote %s %s (%s) started',
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
if
|
|
263
|
+
file.remote_type.value, file.filename.as_posix(),
|
|
264
|
+
file.remote_id)
|
|
265
|
+
if file.remote_type == TemplateFileType.ASSET:
|
|
256
266
|
result = await self.safe_client.delete_template_draft_asset(
|
|
257
267
|
remote_id=self.remote_id,
|
|
258
|
-
asset_id=
|
|
268
|
+
asset_id=file.remote_id,
|
|
259
269
|
)
|
|
260
270
|
else:
|
|
261
271
|
result = await self.safe_client.delete_template_draft_file(
|
|
262
272
|
remote_id=self.remote_id,
|
|
263
|
-
file_id=
|
|
273
|
+
file_id=file.remote_id,
|
|
264
274
|
)
|
|
265
275
|
self.logger.debug('Deleting existing remote %s %s (%s) finished: %s',
|
|
266
|
-
|
|
267
|
-
|
|
276
|
+
file.remote_type.value,
|
|
277
|
+
file.filename.as_posix(),
|
|
278
|
+
file.remote_id, 'ok' if result else 'failed')
|
|
268
279
|
if project_update and result:
|
|
269
|
-
self.safe_project.remove_template_file(
|
|
280
|
+
self.safe_project.remove_template_file(file.filename)
|
|
270
281
|
except Exception as e:
|
|
271
282
|
self.logger.error('Failed to delete existing remote %s %s: %s',
|
|
272
|
-
|
|
283
|
+
file.remote_type.value, file.filename.as_posix(), e)
|
|
273
284
|
|
|
274
285
|
async def cleanup_remote_files(self, remote_assets: list[TemplateFile],
|
|
275
286
|
remote_files: list[TemplateFile]):
|
|
276
|
-
for
|
|
277
|
-
self.logger.debug('Cleaning up remote %s',
|
|
278
|
-
for
|
|
279
|
-
if
|
|
280
|
-
await self._delete_template_file(
|
|
281
|
-
for
|
|
282
|
-
if
|
|
283
|
-
await self._delete_template_file(
|
|
284
|
-
|
|
285
|
-
async def _create_template_file(self,
|
|
287
|
+
for file in self.safe_project.safe_template.files.values():
|
|
288
|
+
self.logger.debug('Cleaning up remote %s', file.filename.as_posix())
|
|
289
|
+
for asset in remote_assets:
|
|
290
|
+
if asset.filename == file.filename:
|
|
291
|
+
await self._delete_template_file(file=asset, project_update=False)
|
|
292
|
+
for file in remote_files:
|
|
293
|
+
if file.filename == file.filename:
|
|
294
|
+
await self._delete_template_file(file=file, project_update=False)
|
|
295
|
+
|
|
296
|
+
async def _create_template_file(self, file: TemplateFile, project_update: bool = False):
|
|
286
297
|
try:
|
|
287
298
|
self.logger.debug('Storing remote %s %s started',
|
|
288
|
-
|
|
289
|
-
if
|
|
299
|
+
file.remote_type.value, file.filename.as_posix())
|
|
300
|
+
if file.remote_type == TemplateFileType.ASSET:
|
|
290
301
|
result = await self.safe_client.post_template_draft_asset(
|
|
291
302
|
remote_id=self.remote_id,
|
|
292
|
-
|
|
303
|
+
file=file,
|
|
293
304
|
)
|
|
294
305
|
else:
|
|
295
306
|
result = await self.safe_client.post_template_draft_file(
|
|
296
307
|
remote_id=self.remote_id,
|
|
297
|
-
|
|
308
|
+
file=file,
|
|
298
309
|
)
|
|
299
310
|
self.logger.debug('Storing remote %s %s finished: %s',
|
|
300
|
-
|
|
311
|
+
file.remote_type.value, file.filename.as_posix(),
|
|
312
|
+
result.remote_id)
|
|
301
313
|
if project_update and result is not None:
|
|
302
314
|
self.safe_project.update_template_file(result)
|
|
303
315
|
except Exception as e:
|
|
304
316
|
self.logger.error('Failed to store remote %s %s: %s',
|
|
305
|
-
|
|
317
|
+
file.remote_type.value, file.filename.as_posix(), e)
|
|
306
318
|
|
|
307
319
|
async def store_remote_files(self):
|
|
308
320
|
if len(self.safe_project.safe_template.files) == 0:
|
|
309
321
|
self.logger.warning('No files to store, maybe you forgot to '
|
|
310
322
|
'update _tdk.files patterns in template.json?')
|
|
311
|
-
for
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
await self._create_template_file(
|
|
323
|
+
for file in self.safe_project.safe_template.files.values():
|
|
324
|
+
file.remote_id = None
|
|
325
|
+
file.remote_type = TemplateFileType.FILE if file.is_text else TemplateFileType.ASSET
|
|
326
|
+
await self._create_template_file(file=file, project_update=True)
|
|
315
327
|
|
|
316
328
|
def create_package(self, output: pathlib.Path, force: bool):
|
|
317
329
|
if output.exists() and not force:
|
|
@@ -321,23 +333,26 @@ class TDKCore:
|
|
|
321
333
|
descriptor = self.safe_project.safe_template.serialize_remote()
|
|
322
334
|
files = []
|
|
323
335
|
assets = []
|
|
324
|
-
for
|
|
325
|
-
if
|
|
326
|
-
self.logger.info('Adding template file %s',
|
|
336
|
+
for file in self.safe_project.safe_template.files.values():
|
|
337
|
+
if file.is_text:
|
|
338
|
+
self.logger.info('Adding template file %s', file.filename.as_posix())
|
|
327
339
|
files.append({
|
|
328
340
|
'uuid': str(UUIDGen.generate()),
|
|
329
|
-
'content':
|
|
330
|
-
'fileName': str(
|
|
341
|
+
'content': file.content.decode(encoding=DEFAULT_ENCODING),
|
|
342
|
+
'fileName': str(file.filename.as_posix()),
|
|
331
343
|
})
|
|
332
344
|
else:
|
|
333
|
-
self.logger.info('Adding template asset %s',
|
|
345
|
+
self.logger.info('Adding template asset %s',
|
|
346
|
+
file.filename.as_posix())
|
|
334
347
|
assets.append({
|
|
335
348
|
'uuid': str(UUIDGen.generate()),
|
|
336
|
-
'contentType':
|
|
337
|
-
'fileName': str(
|
|
349
|
+
'contentType': file.content_type,
|
|
350
|
+
'fileName': str(file.filename.as_posix()),
|
|
338
351
|
})
|
|
339
|
-
self.logger.debug('Packaging template asset %s',
|
|
340
|
-
|
|
352
|
+
self.logger.debug('Packaging template asset %s',
|
|
353
|
+
file.filename.as_posix())
|
|
354
|
+
pkg.writestr(f'template/assets/{file.filename.as_posix()}',
|
|
355
|
+
file.content)
|
|
341
356
|
descriptor['files'] = files
|
|
342
357
|
descriptor['assets'] = assets
|
|
343
358
|
if len(files) == 0 and len(assets) == 0:
|
|
@@ -347,7 +362,8 @@ class TDKCore:
|
|
|
347
362
|
descriptor['createdAt'] = timestamp
|
|
348
363
|
descriptor['updatedAt'] = timestamp
|
|
349
364
|
self.logger.debug('Packaging template.json file')
|
|
350
|
-
pkg.writestr('template/template.json',
|
|
365
|
+
pkg.writestr('template/template.json',
|
|
366
|
+
data=json.dumps(descriptor, indent=4))
|
|
351
367
|
self.logger.debug('ZIP packaging done')
|
|
352
368
|
|
|
353
369
|
# pylint: disable=too-many-locals
|
|
@@ -358,12 +374,12 @@ class TDKCore:
|
|
|
358
374
|
pkg.extractall(tmp_dir)
|
|
359
375
|
del io_zip
|
|
360
376
|
tmp_root = pathlib.Path(tmp_dir) / 'template'
|
|
361
|
-
|
|
377
|
+
file = tmp_root / 'template.json'
|
|
362
378
|
assets_dir = tmp_root / 'assets'
|
|
363
379
|
self.logger.debug('Extracting template data')
|
|
364
|
-
if not
|
|
380
|
+
if not file.exists():
|
|
365
381
|
raise RuntimeError('Malformed package: missing template.json file')
|
|
366
|
-
data = json.loads(
|
|
382
|
+
data = json.loads(file.read_text(encoding=DEFAULT_ENCODING))
|
|
367
383
|
template = Template.load_local(data)
|
|
368
384
|
template.tdk_config.use_default_files()
|
|
369
385
|
self.logger.warning('Using default _tdk.files in template.json, you may want '
|
|
@@ -406,17 +422,6 @@ class TDKCore:
|
|
|
406
422
|
target_file.write_text(data=content, encoding=DEFAULT_ENCODING)
|
|
407
423
|
self.logger.debug('Extracting package done')
|
|
408
424
|
|
|
409
|
-
def create_dot_env(self, output: pathlib.Path, force: bool, api_url: str, api_key: str):
|
|
410
|
-
if output.exists():
|
|
411
|
-
if force:
|
|
412
|
-
self.logger.warning('Overwriting %s (forced)', output.as_posix())
|
|
413
|
-
else:
|
|
414
|
-
raise RuntimeError(f'File {output} already exists (not forced)')
|
|
415
|
-
output.write_text(
|
|
416
|
-
data=create_dot_env(api_url=api_url, api_key=api_key),
|
|
417
|
-
encoding=DEFAULT_ENCODING,
|
|
418
|
-
)
|
|
419
|
-
|
|
420
425
|
async def watch_project(self, callback, stop_event: asyncio.Event):
|
|
421
426
|
async for changes in watchfiles.awatch(
|
|
422
427
|
self.safe_project.template_dir,
|
|
@@ -442,10 +447,11 @@ class TDKCore:
|
|
|
442
447
|
remote_id=self.remote_id,
|
|
443
448
|
)
|
|
444
449
|
else:
|
|
445
|
-
self.logger.info('Document template draft %s does
|
|
450
|
+
self.logger.info('Document template draft %s does '
|
|
451
|
+
'not exist on remote - full sync',
|
|
446
452
|
self.safe_project.safe_template.id)
|
|
447
453
|
await self.store_remote(force=False)
|
|
448
|
-
except
|
|
454
|
+
except WizardCommunicationError as e:
|
|
449
455
|
self.logger.error('Failed to update document template draft %s: %s',
|
|
450
456
|
self.safe_project.safe_template.id, e.message)
|
|
451
457
|
except Exception as e:
|
|
@@ -458,12 +464,12 @@ class TDKCore:
|
|
|
458
464
|
filepath.as_posix())
|
|
459
465
|
return
|
|
460
466
|
try:
|
|
461
|
-
|
|
462
|
-
if
|
|
467
|
+
file = self.safe_project.get_template_file(filepath=filepath)
|
|
468
|
+
if file is None:
|
|
463
469
|
self.logger.info('File %s not tracked currently - skipping',
|
|
464
470
|
filepath.as_posix())
|
|
465
471
|
return
|
|
466
|
-
await self._delete_template_file(
|
|
472
|
+
await self._delete_template_file(file=file, project_update=True)
|
|
467
473
|
except Exception as e:
|
|
468
474
|
self.logger.error('Failed to delete file %s: %s',
|
|
469
475
|
filepath.as_posix(), e)
|
|
@@ -474,12 +480,12 @@ class TDKCore:
|
|
|
474
480
|
filepath.as_posix())
|
|
475
481
|
return
|
|
476
482
|
try:
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
if
|
|
480
|
-
await self._update_template_file(
|
|
483
|
+
remote_file = self.safe_project.get_template_file(filepath=filepath)
|
|
484
|
+
local_file = self.safe_project.load_file(filepath=filepath)
|
|
485
|
+
if remote_file is not None:
|
|
486
|
+
await self._update_template_file(remote_file, local_file, project_update=True)
|
|
481
487
|
else:
|
|
482
|
-
await self._create_template_file(
|
|
488
|
+
await self._create_template_file(file=local_file, project_update=True)
|
|
483
489
|
except Exception as e:
|
|
484
490
|
self.logger.error('Failed to update file %s: %s', filepath.as_posix(), e)
|
|
485
491
|
|
dsw/tdk/model.py
CHANGED
|
@@ -141,9 +141,9 @@ class TemplateFile:
|
|
|
141
141
|
self.filename = filename
|
|
142
142
|
self.content = content
|
|
143
143
|
self.content_type: str = content_type or self.guess_type()
|
|
144
|
-
self.remote_type: TemplateFileType = remote_type or self.
|
|
144
|
+
self.remote_type: TemplateFileType = remote_type or self.guess_template_file_type()
|
|
145
145
|
|
|
146
|
-
def
|
|
146
|
+
def guess_template_file_type(self):
|
|
147
147
|
return TemplateFileType.FILE if self.is_text else TemplateFileType.ASSET
|
|
148
148
|
|
|
149
149
|
def guess_type(self) -> str:
|
|
@@ -180,7 +180,7 @@ class Template:
|
|
|
180
180
|
self.description = description # type: str
|
|
181
181
|
self.readme = readme # type: str
|
|
182
182
|
self.license = template_license # type: str
|
|
183
|
-
self.metamodel_version:
|
|
183
|
+
self.metamodel_version: str = metamodel_version or METAMODEL_VERSION
|
|
184
184
|
self.allowed_packages: list[PackageFilter] = []
|
|
185
185
|
self.formats: list[Format] = []
|
|
186
186
|
self.files: dict[str, TemplateFile] = {}
|
|
@@ -355,11 +355,11 @@ class TemplateProject:
|
|
|
355
355
|
try:
|
|
356
356
|
if filepath.is_absolute():
|
|
357
357
|
filepath = filepath.relative_to(self.template_dir)
|
|
358
|
-
|
|
358
|
+
template_file = TemplateFile(filename=filepath)
|
|
359
359
|
with open(self.template_dir / filepath, mode='rb') as f:
|
|
360
|
-
|
|
361
|
-
self.safe_template.files[filepath.as_posix()] =
|
|
362
|
-
return
|
|
360
|
+
template_file.content = f.read()
|
|
361
|
+
self.safe_template.files[filepath.as_posix()] = template_file
|
|
362
|
+
return template_file
|
|
363
363
|
except Exception as e:
|
|
364
364
|
raise RuntimeWarning(f'Failed to load template file {filepath}: {e}') from e
|
|
365
365
|
|
|
@@ -406,9 +406,9 @@ class TemplateProject:
|
|
|
406
406
|
if filename in self.safe_template.files:
|
|
407
407
|
del self.safe_template.files[filename]
|
|
408
408
|
|
|
409
|
-
def update_template_file(self,
|
|
410
|
-
filename =
|
|
411
|
-
self.safe_template.files[filename] =
|
|
409
|
+
def update_template_file(self, template_file: TemplateFile):
|
|
410
|
+
filename = template_file.filename.as_posix()
|
|
411
|
+
self.safe_template.files[filename] = template_file
|
|
412
412
|
|
|
413
413
|
def get_template_file(self, filepath: pathlib.Path) -> TemplateFile | None:
|
|
414
414
|
if filepath.is_absolute():
|
|
@@ -447,10 +447,10 @@ class TemplateProject:
|
|
|
447
447
|
)
|
|
448
448
|
|
|
449
449
|
def store_files(self, force: bool):
|
|
450
|
-
for
|
|
450
|
+
for template_file in self.safe_template.files.values():
|
|
451
451
|
self._write_file(
|
|
452
|
-
filepath=self.template_dir /
|
|
453
|
-
contents=
|
|
452
|
+
filepath=self.template_dir / template_file.filename,
|
|
453
|
+
contents=template_file.content,
|
|
454
454
|
force=force,
|
|
455
455
|
)
|
|
456
456
|
|
dsw/tdk/templates/env.j2
CHANGED