dsw-tdk 4.21.0__py2.py3-none-any.whl → 4.22.1__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 +211 -54
- dsw/tdk/config.py +154 -0
- dsw/tdk/consts.py +4 -2
- dsw/tdk/core.py +127 -114
- 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.1.dist-info}/METADATA +1 -1
- dsw_tdk-4.22.1.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.1.dist-info}/WHEEL +0 -0
- {dsw_tdk-4.21.0.dist-info → dsw_tdk-4.22.1.dist-info}/entry_points.txt +0 -0
- {dsw_tdk-4.21.0.dist-info → dsw_tdk-4.22.1.dist-info}/licenses/LICENSE +0 -0
- {dsw_tdk-4.21.0.dist-info → dsw_tdk-4.22.1.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.1'
|
|
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,54 +33,71 @@ 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
|
|
|
55
|
-
# pylint: disable=too-many-public-methods
|
|
56
|
+
# pylint: disable-next=too-many-public-methods
|
|
56
57
|
class TDKCore:
|
|
57
58
|
|
|
59
|
+
# pylint: disable-next=too-many-locals
|
|
58
60
|
def _check_metamodel_version(self):
|
|
59
|
-
|
|
61
|
+
hint = 'Fix your metamodelVersion in template.json and/or visit docs'
|
|
62
|
+
mm_ver = str(self.safe_template.metamodel_version)
|
|
63
|
+
try:
|
|
64
|
+
if '.' not in mm_ver:
|
|
65
|
+
mm_ver = f'{mm_ver}.0'
|
|
66
|
+
mm_major, mm_minor = map(int, mm_ver.split('.'))
|
|
67
|
+
except ValueError as e:
|
|
68
|
+
raise TDKProcessingError(f'Invalid metamodel version format: {mm_ver}', hint) from e
|
|
60
69
|
api_version = self.remote_version.split('~', maxsplit=1)[0]
|
|
61
70
|
if '-' in api_version:
|
|
62
71
|
api_version = api_version.split('-', maxsplit=1)[0]
|
|
63
72
|
if 'v' == api_version[0]:
|
|
64
73
|
api_version = api_version[1:]
|
|
65
74
|
if not re.match(REGEX_SEMVER, api_version):
|
|
66
|
-
self.logger.warning('Using non-stable release of API: %s',
|
|
75
|
+
self.logger.warning('Using non-stable release of API: %s',
|
|
76
|
+
self.remote_version)
|
|
67
77
|
return
|
|
68
78
|
parts = api_version.split('.')
|
|
69
79
|
ver = (int(parts[0]), int(parts[1]), int(parts[2]))
|
|
70
80
|
vtag = f'v{ver[0]}.{ver[1]}.{ver[2]}'
|
|
71
|
-
hint = 'Fix your metamodelVersion in template.json and/or visit docs'
|
|
72
81
|
if mm_ver not in METAMODEL_VERSION_SUPPORT:
|
|
73
82
|
raise TDKProcessingError(f'Unknown metamodel version: {mm_ver}', hint)
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
83
|
+
map_reverse = {v: k for k, v in METAMODEL_VERSION_SUPPORT.items()}
|
|
84
|
+
nearest_leq = unknown_version = (0, 0, 0)
|
|
85
|
+
for known_version in map_reverse.keys():
|
|
86
|
+
if known_version <= ver:
|
|
87
|
+
nearest_leq = known_version
|
|
88
|
+
else:
|
|
89
|
+
break
|
|
90
|
+
if nearest_leq == unknown_version:
|
|
91
|
+
raise TDKProcessingError(f'Unsupported API version: {vtag}', hint)
|
|
92
|
+
api_mm_major, api_mm_minor = map(int, map_reverse[nearest_leq].split('.'))
|
|
93
|
+
if mm_major != api_mm_major or mm_minor < api_mm_minor:
|
|
94
|
+
raise TDKProcessingError(
|
|
95
|
+
f'Unsupported metamodel version {mm_ver} for API version {api_version}',
|
|
96
|
+
hint,
|
|
97
|
+
)
|
|
81
98
|
|
|
82
99
|
def __init__(self, template: Template | None = None, project: TemplateProject | None = None,
|
|
83
|
-
client:
|
|
100
|
+
client: WizardAPIClient | None = None, logger: logging.Logger | None = None):
|
|
84
101
|
self.template = template
|
|
85
102
|
self.project = project
|
|
86
103
|
self.client = client
|
|
@@ -106,14 +123,14 @@ class TDKCore:
|
|
|
106
123
|
return self.project
|
|
107
124
|
|
|
108
125
|
@property
|
|
109
|
-
def safe_client(self) ->
|
|
126
|
+
def safe_client(self) -> WizardAPIClient:
|
|
110
127
|
if self.client is None:
|
|
111
128
|
raise RuntimeError('No DSW API client specified')
|
|
112
129
|
return self.client
|
|
113
130
|
|
|
114
131
|
async def init_client(self, api_url: str, api_key: str):
|
|
115
132
|
self.logger.info('Connecting to %s', api_url)
|
|
116
|
-
self.client =
|
|
133
|
+
self.client = WizardAPIClient(api_url=api_url, api_key=api_key)
|
|
117
134
|
self.remote_version = await self.client.get_api_version()
|
|
118
135
|
user = await self.client.get_current_user()
|
|
119
136
|
self.logger.info('Successfully authenticated as %s %s (%s)',
|
|
@@ -135,13 +152,13 @@ class TDKCore:
|
|
|
135
152
|
self.logger.debug('Retrieving template draft files')
|
|
136
153
|
files = await self.safe_client.get_template_draft_files(remote_id=template_id)
|
|
137
154
|
self.logger.info('Retrieved %s file(s)', len(files))
|
|
138
|
-
for
|
|
139
|
-
self.safe_template.files[
|
|
155
|
+
for file in files:
|
|
156
|
+
self.safe_template.files[file.filename.as_posix()] = file
|
|
140
157
|
self.logger.debug('Retrieving template draft assets')
|
|
141
158
|
assets = await self.safe_client.get_template_draft_assets(remote_id=template_id)
|
|
142
159
|
self.logger.info('Retrieved %s asset(s)', len(assets))
|
|
143
|
-
for
|
|
144
|
-
self.safe_template.files[
|
|
160
|
+
for asset in assets:
|
|
161
|
+
self.safe_template.files[asset.filename.as_posix()] = asset
|
|
145
162
|
|
|
146
163
|
async def download_bundle(self, template_id: str) -> bytes:
|
|
147
164
|
self.logger.info('Retrieving template %s bundle', template_id)
|
|
@@ -215,103 +232,105 @@ class TDKCore:
|
|
|
215
232
|
)
|
|
216
233
|
await self.store_remote_files()
|
|
217
234
|
|
|
218
|
-
async def _update_template_file(self,
|
|
235
|
+
async def _update_template_file(self, remote_file: TemplateFile, local_file: TemplateFile,
|
|
219
236
|
project_update: bool = False):
|
|
220
237
|
try:
|
|
221
238
|
self.logger.debug('Updating existing remote %s %s (%s) started',
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
if
|
|
239
|
+
remote_file.remote_type.value, remote_file.filename.as_posix(),
|
|
240
|
+
remote_file.remote_id)
|
|
241
|
+
local_file.remote_id = remote_file.remote_id
|
|
242
|
+
if remote_file.remote_type == TemplateFileType.ASSET:
|
|
226
243
|
result = await self.safe_client.put_template_draft_asset_content(
|
|
227
244
|
remote_id=self.remote_id,
|
|
228
|
-
|
|
245
|
+
file=local_file,
|
|
229
246
|
)
|
|
230
247
|
else:
|
|
231
248
|
result = await self.safe_client.put_template_draft_file_content(
|
|
232
249
|
remote_id=self.remote_id,
|
|
233
|
-
|
|
250
|
+
file=local_file,
|
|
234
251
|
)
|
|
235
252
|
self.logger.debug('Updating existing remote %s %s (%s) finished: %s',
|
|
236
|
-
|
|
237
|
-
|
|
253
|
+
remote_file.remote_type.value, remote_file.filename.as_posix(),
|
|
254
|
+
remote_file.remote_id, 'ok' if result else 'failed')
|
|
238
255
|
if project_update and result:
|
|
239
256
|
self.safe_project.update_template_file(result)
|
|
240
257
|
except Exception as e1:
|
|
241
258
|
try:
|
|
242
259
|
self.logger.debug('Trying to delete/create due to: %s', str(e1))
|
|
243
|
-
await self._delete_template_file(
|
|
244
|
-
await self._create_template_file(
|
|
260
|
+
await self._delete_template_file(file=remote_file)
|
|
261
|
+
await self._create_template_file(file=local_file, project_update=True)
|
|
245
262
|
except Exception as e2:
|
|
246
263
|
self.logger.error('Failed to update existing remote %s %s: %s',
|
|
247
|
-
|
|
248
|
-
|
|
264
|
+
remote_file.remote_type.value,
|
|
265
|
+
remote_file.filename.as_posix(), e2)
|
|
249
266
|
|
|
250
|
-
async def _delete_template_file(self,
|
|
267
|
+
async def _delete_template_file(self, file: TemplateFile, project_update: bool = False):
|
|
251
268
|
try:
|
|
252
269
|
self.logger.debug('Deleting existing remote %s %s (%s) started',
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
if
|
|
270
|
+
file.remote_type.value, file.filename.as_posix(),
|
|
271
|
+
file.remote_id)
|
|
272
|
+
if file.remote_type == TemplateFileType.ASSET:
|
|
256
273
|
result = await self.safe_client.delete_template_draft_asset(
|
|
257
274
|
remote_id=self.remote_id,
|
|
258
|
-
asset_id=
|
|
275
|
+
asset_id=file.remote_id,
|
|
259
276
|
)
|
|
260
277
|
else:
|
|
261
278
|
result = await self.safe_client.delete_template_draft_file(
|
|
262
279
|
remote_id=self.remote_id,
|
|
263
|
-
file_id=
|
|
280
|
+
file_id=file.remote_id,
|
|
264
281
|
)
|
|
265
282
|
self.logger.debug('Deleting existing remote %s %s (%s) finished: %s',
|
|
266
|
-
|
|
267
|
-
|
|
283
|
+
file.remote_type.value,
|
|
284
|
+
file.filename.as_posix(),
|
|
285
|
+
file.remote_id, 'ok' if result else 'failed')
|
|
268
286
|
if project_update and result:
|
|
269
|
-
self.safe_project.remove_template_file(
|
|
287
|
+
self.safe_project.remove_template_file(file.filename)
|
|
270
288
|
except Exception as e:
|
|
271
289
|
self.logger.error('Failed to delete existing remote %s %s: %s',
|
|
272
|
-
|
|
290
|
+
file.remote_type.value, file.filename.as_posix(), e)
|
|
273
291
|
|
|
274
292
|
async def cleanup_remote_files(self, remote_assets: list[TemplateFile],
|
|
275
293
|
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,
|
|
294
|
+
for file in self.safe_project.safe_template.files.values():
|
|
295
|
+
self.logger.debug('Cleaning up remote %s', file.filename.as_posix())
|
|
296
|
+
for asset in remote_assets:
|
|
297
|
+
if asset.filename == file.filename:
|
|
298
|
+
await self._delete_template_file(file=asset, project_update=False)
|
|
299
|
+
for file in remote_files:
|
|
300
|
+
if file.filename == file.filename:
|
|
301
|
+
await self._delete_template_file(file=file, project_update=False)
|
|
302
|
+
|
|
303
|
+
async def _create_template_file(self, file: TemplateFile, project_update: bool = False):
|
|
286
304
|
try:
|
|
287
305
|
self.logger.debug('Storing remote %s %s started',
|
|
288
|
-
|
|
289
|
-
if
|
|
306
|
+
file.remote_type.value, file.filename.as_posix())
|
|
307
|
+
if file.remote_type == TemplateFileType.ASSET:
|
|
290
308
|
result = await self.safe_client.post_template_draft_asset(
|
|
291
309
|
remote_id=self.remote_id,
|
|
292
|
-
|
|
310
|
+
file=file,
|
|
293
311
|
)
|
|
294
312
|
else:
|
|
295
313
|
result = await self.safe_client.post_template_draft_file(
|
|
296
314
|
remote_id=self.remote_id,
|
|
297
|
-
|
|
315
|
+
file=file,
|
|
298
316
|
)
|
|
299
317
|
self.logger.debug('Storing remote %s %s finished: %s',
|
|
300
|
-
|
|
318
|
+
file.remote_type.value, file.filename.as_posix(),
|
|
319
|
+
result.remote_id)
|
|
301
320
|
if project_update and result is not None:
|
|
302
321
|
self.safe_project.update_template_file(result)
|
|
303
322
|
except Exception as e:
|
|
304
323
|
self.logger.error('Failed to store remote %s %s: %s',
|
|
305
|
-
|
|
324
|
+
file.remote_type.value, file.filename.as_posix(), e)
|
|
306
325
|
|
|
307
326
|
async def store_remote_files(self):
|
|
308
327
|
if len(self.safe_project.safe_template.files) == 0:
|
|
309
328
|
self.logger.warning('No files to store, maybe you forgot to '
|
|
310
329
|
'update _tdk.files patterns in template.json?')
|
|
311
|
-
for
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
await self._create_template_file(
|
|
330
|
+
for file in self.safe_project.safe_template.files.values():
|
|
331
|
+
file.remote_id = None
|
|
332
|
+
file.remote_type = TemplateFileType.FILE if file.is_text else TemplateFileType.ASSET
|
|
333
|
+
await self._create_template_file(file=file, project_update=True)
|
|
315
334
|
|
|
316
335
|
def create_package(self, output: pathlib.Path, force: bool):
|
|
317
336
|
if output.exists() and not force:
|
|
@@ -321,23 +340,26 @@ class TDKCore:
|
|
|
321
340
|
descriptor = self.safe_project.safe_template.serialize_remote()
|
|
322
341
|
files = []
|
|
323
342
|
assets = []
|
|
324
|
-
for
|
|
325
|
-
if
|
|
326
|
-
self.logger.info('Adding template file %s',
|
|
343
|
+
for file in self.safe_project.safe_template.files.values():
|
|
344
|
+
if file.is_text:
|
|
345
|
+
self.logger.info('Adding template file %s', file.filename.as_posix())
|
|
327
346
|
files.append({
|
|
328
347
|
'uuid': str(UUIDGen.generate()),
|
|
329
|
-
'content':
|
|
330
|
-
'fileName': str(
|
|
348
|
+
'content': file.content.decode(encoding=DEFAULT_ENCODING),
|
|
349
|
+
'fileName': str(file.filename.as_posix()),
|
|
331
350
|
})
|
|
332
351
|
else:
|
|
333
|
-
self.logger.info('Adding template asset %s',
|
|
352
|
+
self.logger.info('Adding template asset %s',
|
|
353
|
+
file.filename.as_posix())
|
|
334
354
|
assets.append({
|
|
335
355
|
'uuid': str(UUIDGen.generate()),
|
|
336
|
-
'contentType':
|
|
337
|
-
'fileName': str(
|
|
356
|
+
'contentType': file.content_type,
|
|
357
|
+
'fileName': str(file.filename.as_posix()),
|
|
338
358
|
})
|
|
339
|
-
self.logger.debug('Packaging template asset %s',
|
|
340
|
-
|
|
359
|
+
self.logger.debug('Packaging template asset %s',
|
|
360
|
+
file.filename.as_posix())
|
|
361
|
+
pkg.writestr(f'template/assets/{file.filename.as_posix()}',
|
|
362
|
+
file.content)
|
|
341
363
|
descriptor['files'] = files
|
|
342
364
|
descriptor['assets'] = assets
|
|
343
365
|
if len(files) == 0 and len(assets) == 0:
|
|
@@ -347,7 +369,8 @@ class TDKCore:
|
|
|
347
369
|
descriptor['createdAt'] = timestamp
|
|
348
370
|
descriptor['updatedAt'] = timestamp
|
|
349
371
|
self.logger.debug('Packaging template.json file')
|
|
350
|
-
pkg.writestr('template/template.json',
|
|
372
|
+
pkg.writestr('template/template.json',
|
|
373
|
+
data=json.dumps(descriptor, indent=4))
|
|
351
374
|
self.logger.debug('ZIP packaging done')
|
|
352
375
|
|
|
353
376
|
# pylint: disable=too-many-locals
|
|
@@ -358,12 +381,12 @@ class TDKCore:
|
|
|
358
381
|
pkg.extractall(tmp_dir)
|
|
359
382
|
del io_zip
|
|
360
383
|
tmp_root = pathlib.Path(tmp_dir) / 'template'
|
|
361
|
-
|
|
384
|
+
file = tmp_root / 'template.json'
|
|
362
385
|
assets_dir = tmp_root / 'assets'
|
|
363
386
|
self.logger.debug('Extracting template data')
|
|
364
|
-
if not
|
|
387
|
+
if not file.exists():
|
|
365
388
|
raise RuntimeError('Malformed package: missing template.json file')
|
|
366
|
-
data = json.loads(
|
|
389
|
+
data = json.loads(file.read_text(encoding=DEFAULT_ENCODING))
|
|
367
390
|
template = Template.load_local(data)
|
|
368
391
|
template.tdk_config.use_default_files()
|
|
369
392
|
self.logger.warning('Using default _tdk.files in template.json, you may want '
|
|
@@ -406,17 +429,6 @@ class TDKCore:
|
|
|
406
429
|
target_file.write_text(data=content, encoding=DEFAULT_ENCODING)
|
|
407
430
|
self.logger.debug('Extracting package done')
|
|
408
431
|
|
|
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
432
|
async def watch_project(self, callback, stop_event: asyncio.Event):
|
|
421
433
|
async for changes in watchfiles.awatch(
|
|
422
434
|
self.safe_project.template_dir,
|
|
@@ -442,10 +454,11 @@ class TDKCore:
|
|
|
442
454
|
remote_id=self.remote_id,
|
|
443
455
|
)
|
|
444
456
|
else:
|
|
445
|
-
self.logger.info('Document template draft %s does
|
|
457
|
+
self.logger.info('Document template draft %s does '
|
|
458
|
+
'not exist on remote - full sync',
|
|
446
459
|
self.safe_project.safe_template.id)
|
|
447
460
|
await self.store_remote(force=False)
|
|
448
|
-
except
|
|
461
|
+
except WizardCommunicationError as e:
|
|
449
462
|
self.logger.error('Failed to update document template draft %s: %s',
|
|
450
463
|
self.safe_project.safe_template.id, e.message)
|
|
451
464
|
except Exception as e:
|
|
@@ -458,12 +471,12 @@ class TDKCore:
|
|
|
458
471
|
filepath.as_posix())
|
|
459
472
|
return
|
|
460
473
|
try:
|
|
461
|
-
|
|
462
|
-
if
|
|
474
|
+
file = self.safe_project.get_template_file(filepath=filepath)
|
|
475
|
+
if file is None:
|
|
463
476
|
self.logger.info('File %s not tracked currently - skipping',
|
|
464
477
|
filepath.as_posix())
|
|
465
478
|
return
|
|
466
|
-
await self._delete_template_file(
|
|
479
|
+
await self._delete_template_file(file=file, project_update=True)
|
|
467
480
|
except Exception as e:
|
|
468
481
|
self.logger.error('Failed to delete file %s: %s',
|
|
469
482
|
filepath.as_posix(), e)
|
|
@@ -474,12 +487,12 @@ class TDKCore:
|
|
|
474
487
|
filepath.as_posix())
|
|
475
488
|
return
|
|
476
489
|
try:
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
if
|
|
480
|
-
await self._update_template_file(
|
|
490
|
+
remote_file = self.safe_project.get_template_file(filepath=filepath)
|
|
491
|
+
local_file = self.safe_project.load_file(filepath=filepath)
|
|
492
|
+
if remote_file is not None:
|
|
493
|
+
await self._update_template_file(remote_file, local_file, project_update=True)
|
|
481
494
|
else:
|
|
482
|
-
await self._create_template_file(
|
|
495
|
+
await self._create_template_file(file=local_file, project_update=True)
|
|
483
496
|
except Exception as e:
|
|
484
497
|
self.logger.error('Failed to update file %s: %s', filepath.as_posix(), e)
|
|
485
498
|
|
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