development-framework-cli 1.7.3__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.
- development_framework_cli-1.7.3.dist-info/METADATA +141 -0
- development_framework_cli-1.7.3.dist-info/RECORD +71 -0
- development_framework_cli-1.7.3.dist-info/WHEEL +4 -0
- development_framework_cli-1.7.3.dist-info/entry_points.txt +2 -0
- development_framework_cli-1.7.3.dist-info/licenses/LICENSE +21 -0
- dm_cli/__init__.py +1 -0
- dm_cli/cli.py +180 -0
- dm_cli/command_group/__init__.py +0 -0
- dm_cli/command_group/data_source.py +176 -0
- dm_cli/command_group/entities.py +116 -0
- dm_cli/dmss.py +104 -0
- dm_cli/dmss_api/__init__.py +27 -0
- dm_cli/dmss_api/api/__init__.py +3 -0
- dm_cli/dmss_api/api/access_control_api.py +349 -0
- dm_cli/dmss_api/api/attribute_api.py +175 -0
- dm_cli/dmss_api/api/blob_api.py +344 -0
- dm_cli/dmss_api/api/blueprint_api.py +326 -0
- dm_cli/dmss_api/api/datasource_api.py +448 -0
- dm_cli/dmss_api/api/default_api.py +4439 -0
- dm_cli/dmss_api/api/document_api.py +922 -0
- dm_cli/dmss_api/api/entity_api.py +464 -0
- dm_cli/dmss_api/api/export_api.py +313 -0
- dm_cli/dmss_api/api/file_api.py +197 -0
- dm_cli/dmss_api/api/health_check_api.py +160 -0
- dm_cli/dmss_api/api/lookup_table_api.py +323 -0
- dm_cli/dmss_api/api/meta_api.py +185 -0
- dm_cli/dmss_api/api/personal_access_token_api.py +433 -0
- dm_cli/dmss_api/api/reference_api.py +174 -0
- dm_cli/dmss_api/api/search_api.py +183 -0
- dm_cli/dmss_api/api/whoami_api.py +163 -0
- dm_cli/dmss_api/api_client.py +896 -0
- dm_cli/dmss_api/apis/__init__.py +32 -0
- dm_cli/dmss_api/configuration.py +482 -0
- dm_cli/dmss_api/exceptions.py +158 -0
- dm_cli/dmss_api/model/__init__.py +5 -0
- dm_cli/dmss_api/model/access_control_list.py +287 -0
- dm_cli/dmss_api/model/access_level.py +290 -0
- dm_cli/dmss_api/model/acl.py +287 -0
- dm_cli/dmss_api/model/data_source_information.py +282 -0
- dm_cli/dmss_api/model/data_source_request.py +281 -0
- dm_cli/dmss_api/model/dependency.py +294 -0
- dm_cli/dmss_api/model/entity.py +275 -0
- dm_cli/dmss_api/model/error_response.py +278 -0
- dm_cli/dmss_api/model/export_meta_response.py +277 -0
- dm_cli/dmss_api/model/get_blueprint_response.py +280 -0
- dm_cli/dmss_api/model/lookup.py +283 -0
- dm_cli/dmss_api/model/pat_data.py +299 -0
- dm_cli/dmss_api/model/recipe.py +311 -0
- dm_cli/dmss_api/model/recipe_attribute.py +304 -0
- dm_cli/dmss_api/model/reference.py +280 -0
- dm_cli/dmss_api/model/reference_entity.py +288 -0
- dm_cli/dmss_api/model/repository.py +317 -0
- dm_cli/dmss_api/model/repository_type.py +290 -0
- dm_cli/dmss_api/model/storage_attribute.py +295 -0
- dm_cli/dmss_api/model/storage_data_types.py +292 -0
- dm_cli/dmss_api/model/storage_recipe.py +293 -0
- dm_cli/dmss_api/model_utils.py +2057 -0
- dm_cli/dmss_api/models/__init__.py +29 -0
- dm_cli/dmss_api/rest.py +358 -0
- dm_cli/domain.py +166 -0
- dm_cli/enums.py +48 -0
- dm_cli/import_entity.py +128 -0
- dm_cli/import_package.py +173 -0
- dm_cli/package_tree_from_zip.py +120 -0
- dm_cli/state.py +12 -0
- dm_cli/utils/__init__.py +0 -0
- dm_cli/utils/file_structure.py +31 -0
- dm_cli/utils/reference.py +129 -0
- dm_cli/utils/resolve_local_ids.py +142 -0
- dm_cli/utils/utils.py +161 -0
- dm_cli/utils/zip.py +70 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def search_in_list(
|
|
5
|
+
document: list, pattern: str, target_attr: str, path: str, parent_was_dict: bool, targets: dict, references: dict
|
|
6
|
+
):
|
|
7
|
+
attribute_types = map(lambda attr: (attr, type(attr)), document)
|
|
8
|
+
for i, at in enumerate(attribute_types):
|
|
9
|
+
a = at[0]
|
|
10
|
+
t = at[1]
|
|
11
|
+
if is_primitive(t):
|
|
12
|
+
result = search_in_primitive(a, pattern)
|
|
13
|
+
if result:
|
|
14
|
+
references[f"{path}[{i}]"] = result
|
|
15
|
+
else:
|
|
16
|
+
action = type_to_action[t]
|
|
17
|
+
action(a, pattern, target_attr, f"{path}[{i}]", False, targets, references)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def search_in_primitive(val, pattern: str):
|
|
21
|
+
match = re.match(pattern, str(val))
|
|
22
|
+
if match:
|
|
23
|
+
return match.group(1)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def is_primitive(t: type):
|
|
27
|
+
return t == str or t == int or t == float or t == bool
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def is_list(t: type):
|
|
31
|
+
return t == list
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def is_dict(t: type):
|
|
35
|
+
return t == dict
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def search_in_dict(
|
|
39
|
+
document: dict, pattern: str, target_attr: str, path: str, parent_was_dict: bool, targets: dict, references: dict
|
|
40
|
+
):
|
|
41
|
+
"""Search for internal references in 'document', referencing to a target attribute.
|
|
42
|
+
|
|
43
|
+
Uses the methods 'search_in_list' and 'search_in_primitive' to continue searching in attributes of these types
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
document: the input document which contains internal references
|
|
47
|
+
pattern: (what does a 'reference' look like?) regex which tries to match the reference structure, defaults to
|
|
48
|
+
^.$<some_id>. Is used to populate the lookup_of_references table
|
|
49
|
+
target_attr: (where is the reference pointing to?) the attribute which contains the id which is being referred
|
|
50
|
+
to is used to populate the lookup_of_targets table
|
|
51
|
+
path: the absolute path at each level in the search
|
|
52
|
+
parent_was_dict: flag to track the type of the parent
|
|
53
|
+
targets: dict to track discovered attributes that define local ids
|
|
54
|
+
references: dict to track discovered references to local ids
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
Two lookup tables: 'references' dict, which mapping from the location of the reference (path), to the
|
|
58
|
+
local id, and 'targets' dict, which is a mapping from the local id to the location of the element which owns
|
|
59
|
+
that id (absolute local path)
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
attribute_types = map(lambda attr: (attr, type(document[attr])), list(document.keys()))
|
|
63
|
+
for a, t in attribute_types:
|
|
64
|
+
val = document[a]
|
|
65
|
+
if is_primitive(t):
|
|
66
|
+
if a == target_attr:
|
|
67
|
+
parent_path = path.rsplit("[", 1)[0]
|
|
68
|
+
final_path = path if parent_was_dict else f"{parent_path}({target_attr}={val})"
|
|
69
|
+
targets[val] = final_path
|
|
70
|
+
result = search_in_primitive(val, pattern)
|
|
71
|
+
if result:
|
|
72
|
+
references[f"{path}.{a}"] = result
|
|
73
|
+
else:
|
|
74
|
+
action = type_to_action[t]
|
|
75
|
+
action(document[a], pattern, target_attr, f"{path}.{a}", True, targets, references)
|
|
76
|
+
return targets, references
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _dig_and_replace(document, path_iterator: iter, replace: str):
|
|
80
|
+
attr = next(path_iterator, None)
|
|
81
|
+
if not attr:
|
|
82
|
+
return True
|
|
83
|
+
attr = int(attr) if is_list(type(document)) else attr
|
|
84
|
+
last_attribute = _dig_and_replace(document[attr], path_iterator, replace)
|
|
85
|
+
if last_attribute:
|
|
86
|
+
document[attr] = replace
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def dig_and_replace(targets: dict, references: dict, document):
|
|
90
|
+
"""Transform references in 'references' to the absolute (local) path found in 'targets'
|
|
91
|
+
|
|
92
|
+
Traverses the document based on the path found in 'references' lookup table, and updates the entry with the
|
|
93
|
+
corresponding value found in the 'targets' lookup table (the local path to the target attribute)
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
references: mapping from the location of the reference (path), to the local id
|
|
97
|
+
targets: mapping from the local id to the location of the element which owns that id
|
|
98
|
+
document: dictionary document which contains references we want to transform/replace
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
The final document with all the local id references resolved into local path references
|
|
102
|
+
|
|
103
|
+
"""
|
|
104
|
+
for path, ref in references.items():
|
|
105
|
+
path_items = re.split("[\.\[\]]+", path)[1:] # splits path on the '.', '[' and ']' characters
|
|
106
|
+
path_iterator = iter(path_items)
|
|
107
|
+
_dig_and_replace(document, path_iterator, targets[ref])
|
|
108
|
+
return document
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def resolve_local_ids_in_document(
|
|
112
|
+
document: dict,
|
|
113
|
+
pattern: str = r"\^\.\$(\w+)",
|
|
114
|
+
target_attr: str = "_id",
|
|
115
|
+
path: str = "^",
|
|
116
|
+
parent_was_dict: bool = True,
|
|
117
|
+
):
|
|
118
|
+
"""Transform local_id references in 'document' to absolute (local) paths
|
|
119
|
+
|
|
120
|
+
e.g.: customers[0].car.address points to cars[0][0]
|
|
121
|
+
|
|
122
|
+
(local id reference) (absolute path reference)
|
|
123
|
+
"address": ^.$12345 -> "address": ^.cars[0](_id=12345)
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
document: the input document which contains internal references
|
|
127
|
+
pattern: (what does a 'reference' look like?) regex which tries to match the reference structure, defaults to
|
|
128
|
+
^.$<some_id>. Is used to populate the lookup_of_references table
|
|
129
|
+
target_attr: (where is the reference pointing to?) the attribute which contains the id which is being referred
|
|
130
|
+
to is used to populate the lookup_of_targets table
|
|
131
|
+
path: the absolute path at each level in the search
|
|
132
|
+
parent_was_dict: flag to track the type of the parent
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
The final document with all the local id references resolved into local path references
|
|
136
|
+
"""
|
|
137
|
+
|
|
138
|
+
targets, references = search_in_dict(document, pattern, target_attr, path, parent_was_dict, {}, {})
|
|
139
|
+
return dig_and_replace(targets, references, document)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
type_to_action = {list: search_in_list, dict: search_in_dict}
|
dm_cli/utils/utils.py
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
import pprint
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Callable, Dict, List
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
from rich import print
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
|
|
11
|
+
from dm_cli.dmss import ApplicationException, dmss_api
|
|
12
|
+
from dm_cli.dmss_api.exceptions import ApiException, NotFoundException
|
|
13
|
+
from dm_cli.utils.file_structure import get_app_dir_structure, get_json_files_in_dir
|
|
14
|
+
|
|
15
|
+
from ..domain import Dependency, Package
|
|
16
|
+
from ..enums import SIMOS
|
|
17
|
+
|
|
18
|
+
console = Console()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def resolve_dependency(type_ref: str, dependencies: Dict[str, Dependency]) -> str:
|
|
22
|
+
"""Takes a type reference and dependencies. Returns the address for
|
|
23
|
+
the Blueprint, prefixed with which protocol should be used to fetch it.
|
|
24
|
+
Expected format is ALIAS:ADDRESS"""
|
|
25
|
+
tag, path = type_ref.split(":", 1)
|
|
26
|
+
path = path.strip(" /")
|
|
27
|
+
dependency = dependencies.get(tag)
|
|
28
|
+
if not dependency:
|
|
29
|
+
raise ApplicationException(f"No dependency with alias '{tag}' was found in the entities dependencies list")
|
|
30
|
+
address = dependency.address.strip(" /")
|
|
31
|
+
|
|
32
|
+
if dependency.protocol == "dmss":
|
|
33
|
+
return f"dmss://{address}/{path}"
|
|
34
|
+
if dependency.protocol == "http":
|
|
35
|
+
return f"http://{address}/{path}"
|
|
36
|
+
|
|
37
|
+
raise ApplicationException(f"Protocol '{dependency.protocol}' is not a valid protocol for resolving dependencies")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def concat_dependencies(
|
|
41
|
+
new_dependencies: List[dict], old_dependencies: Dict[str, Dependency], filename: str
|
|
42
|
+
) -> Dict[str, Dependency]:
|
|
43
|
+
entity_dependencies = {dependency["alias"]: Dependency(**dependency) for dependency in new_dependencies}
|
|
44
|
+
alias_intersect = entity_dependencies.keys() & old_dependencies.keys()
|
|
45
|
+
|
|
46
|
+
# If there are duplicated aliases, raise error if they are not identical to the existing one
|
|
47
|
+
for duplicated_alias in alias_intersect:
|
|
48
|
+
if entity_dependencies[duplicated_alias] != old_dependencies[duplicated_alias]:
|
|
49
|
+
raise ApplicationException(f"Conflicting dependency alias(es) in file '{filename}'. '{alias_intersect}'")
|
|
50
|
+
old_dependencies.update(entity_dependencies)
|
|
51
|
+
return old_dependencies
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def document_already_exists(api_exception: ApiException) -> bool:
|
|
55
|
+
error = json.loads(api_exception.body)
|
|
56
|
+
if error.get("type") == "BadRequestException" and "already exists" in error.get("message"):
|
|
57
|
+
print(f"ERROR: {error.get('message')}")
|
|
58
|
+
return True
|
|
59
|
+
return False
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def add_package_to_path(name: str, path: Path):
|
|
63
|
+
package = Package(name, is_root=len(path.parts) == 2)
|
|
64
|
+
dmss_api.document_add(
|
|
65
|
+
str(path.parent),
|
|
66
|
+
json.dumps(package.to_dict()),
|
|
67
|
+
files=[],
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def replace_global_addresses(
|
|
72
|
+
document: dict, data_source_id: str, files_to_upload: dict, upload_global_file: Callable
|
|
73
|
+
) -> dict:
|
|
74
|
+
# This is for contained Files(blob data)? If so they should be embedded in the json document, not uploaded as a blob
|
|
75
|
+
if document["type"] == SIMOS.REFERENCE.value and document["address"] in files_to_upload:
|
|
76
|
+
blob_id = files_to_upload[document["address"]]
|
|
77
|
+
document["address"] = f"${blob_id}"
|
|
78
|
+
# This is for model contained, storage-uncontained documents. They are stored on disk in the "global" folder
|
|
79
|
+
if document["type"] == SIMOS.REFERENCE.value and document["referenceType"] == "storage":
|
|
80
|
+
global_id = upload_global_file(document["address"])
|
|
81
|
+
document["address"] = f"${global_id}"
|
|
82
|
+
for key, value in document.items():
|
|
83
|
+
if key == "_meta_": # meta data can never contain blob data.
|
|
84
|
+
return document
|
|
85
|
+
if isinstance(value, dict) and value:
|
|
86
|
+
document[key] = replace_global_addresses(value, data_source_id, files_to_upload, upload_global_file)
|
|
87
|
+
if isinstance(value, list) and value:
|
|
88
|
+
if len(value) > 0 and isinstance(value[0], dict):
|
|
89
|
+
document[key] = [
|
|
90
|
+
replace_global_addresses(item, data_source_id, files_to_upload, upload_global_file)
|
|
91
|
+
for item in document[key]
|
|
92
|
+
]
|
|
93
|
+
return document
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def destination_is_root(destination: Path) -> bool:
|
|
97
|
+
if len(destination.parts) > 1:
|
|
98
|
+
return False
|
|
99
|
+
return True
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def ensure_package_structure(path: Path):
|
|
103
|
+
"""Create any missing packages in the provided path (mkdir -R)"""
|
|
104
|
+
try:
|
|
105
|
+
dmss_api.document_get(f"dmss://{path}/")
|
|
106
|
+
except NotFoundException as e:
|
|
107
|
+
error = json.loads(e.body)
|
|
108
|
+
if error["status"] != 404:
|
|
109
|
+
print(f"Target package '{path}' is likely corrupt!")
|
|
110
|
+
pprint.pformat(error)
|
|
111
|
+
raise typer.Exit(code=1)
|
|
112
|
+
|
|
113
|
+
if len(path.parts) > 2: # We're at root package level. Do not check for datasource.
|
|
114
|
+
ensure_package_structure(path.parent)
|
|
115
|
+
|
|
116
|
+
add_package_to_path(path.name, path)
|
|
117
|
+
print(f"Target folder '{path.name}' was missing in '{path.parent}'. Created: ✓")
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def get_root_packages_in_data_sources(path: str) -> dict:
|
|
121
|
+
"""
|
|
122
|
+
Generate a dict that contains what root packages are included in a data source. Example structure:
|
|
123
|
+
{
|
|
124
|
+
"dataSourceA": [rootPackageA, rootPackageB],
|
|
125
|
+
"dataSourceB": [rootPackageC]
|
|
126
|
+
}
|
|
127
|
+
"""
|
|
128
|
+
data_sources_dir, data_dir = get_app_dir_structure(Path(path))
|
|
129
|
+
data_source_definitions: list[str] = get_json_files_in_dir(data_sources_dir)
|
|
130
|
+
root_packages_in_data_sources = {}
|
|
131
|
+
for data_source_definition in data_source_definitions:
|
|
132
|
+
with open(os.path.join(data_sources_dir, data_source_definition)) as file:
|
|
133
|
+
data_source_document = json.load(file)
|
|
134
|
+
# Global folders should be uploaded directly to the data source
|
|
135
|
+
# and should not be included in the root packages of the data source.
|
|
136
|
+
global_folders = data_source_document.get("global_folders", [])
|
|
137
|
+
data_source = data_source_document["name"]
|
|
138
|
+
data_source_path = os.path.join(data_dir, data_source)
|
|
139
|
+
root_packages: list[str] = [
|
|
140
|
+
folder
|
|
141
|
+
for folder in os.listdir(data_source_path)
|
|
142
|
+
if os.path.isdir(os.path.join(data_source_path, folder)) and folder not in global_folders
|
|
143
|
+
]
|
|
144
|
+
root_packages_in_data_sources[data_source] = root_packages
|
|
145
|
+
return root_packages_in_data_sources
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def validate_entities_in_data_sources(data_source_contents: dict):
|
|
149
|
+
"""Run validation on entities in data sources.
|
|
150
|
+
data_source_contents is a dict that contains what root packages are included in a data source. Example structure:
|
|
151
|
+
{
|
|
152
|
+
"dataSourceA": [rootPackageA, rootPackageB],
|
|
153
|
+
"dataSourceB": [rootPackageC]
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
"""
|
|
157
|
+
for data_source_name in data_source_contents:
|
|
158
|
+
root_packages = data_source_contents[data_source_name]
|
|
159
|
+
for root_package_name in root_packages:
|
|
160
|
+
print("Validating entities in: ", f"{os.path.join(data_source_name, root_package_name)}")
|
|
161
|
+
dmss_api.validate_existing_entity(f"{os.path.join(data_source_name, root_package_name)}")
|
dm_cli/utils/zip.py
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from zipfile import ZipFile
|
|
4
|
+
|
|
5
|
+
import emoji
|
|
6
|
+
|
|
7
|
+
from ..dmss import ApplicationException
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def zip_all(zip_file: ZipFile, path: str, real_name="", write_folder: bool = True):
|
|
11
|
+
basename = os.path.basename(path)
|
|
12
|
+
if os.path.isdir(path):
|
|
13
|
+
if real_name == "":
|
|
14
|
+
real_name = basename
|
|
15
|
+
if write_folder:
|
|
16
|
+
zip_file.write(path, os.path.join(real_name))
|
|
17
|
+
for root, dirs, files in os.walk(path):
|
|
18
|
+
for d in dirs:
|
|
19
|
+
zip_all(
|
|
20
|
+
zip_file,
|
|
21
|
+
os.path.join(root, d),
|
|
22
|
+
os.path.join(real_name, d),
|
|
23
|
+
write_folder=write_folder,
|
|
24
|
+
)
|
|
25
|
+
for f in files:
|
|
26
|
+
zip_file.write(os.path.join(root, f), os.path.join(real_name, f))
|
|
27
|
+
break
|
|
28
|
+
elif os.path.isfile(path):
|
|
29
|
+
zip_file.write(path, os.path.join(real_name, basename))
|
|
30
|
+
else:
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def unpack_and_save_zipfile(export_location: str, zip_file: ZipFile):
|
|
35
|
+
"""Unpack zipfile and save it to export_location. It is assumed that zip file only contains json files and folders.
|
|
36
|
+
If file or folder to export already exists, an exception is raised.
|
|
37
|
+
"""
|
|
38
|
+
zip_file_unpacked_path = f"{export_location}/{zip_file.filename.rstrip('.zip')}"
|
|
39
|
+
|
|
40
|
+
zip_has_single_file_and_no_folders = (
|
|
41
|
+
len(zip_file.filelist) == 1 and zip_file.filelist[0].filename.split("/")[0] == ""
|
|
42
|
+
)
|
|
43
|
+
if zip_has_single_file_and_no_folders:
|
|
44
|
+
# If single file in the zip file (and the file is not inside a folder), the unpacked path has .json ending
|
|
45
|
+
# (we assume the zip file always contains json files)
|
|
46
|
+
zip_file_unpacked_path += ".json"
|
|
47
|
+
|
|
48
|
+
if Path(zip_file_unpacked_path).exists():
|
|
49
|
+
print(emoji.emojize(f"\t:error: File or folder '{zip_file_unpacked_path}' already exists. Exiting."))
|
|
50
|
+
raise ApplicationException("Path already exists")
|
|
51
|
+
|
|
52
|
+
zip_file.extractall(path=export_location)
|
|
53
|
+
print(f"Saved unpacked zip file to '{zip_file_unpacked_path}'.")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def save_as_zip_file(export_location: str, filename: str, data: str):
|
|
57
|
+
"""Save binary data into a zip file on the local disk.
|
|
58
|
+
If file or folder to export already exists, an exception is raised.
|
|
59
|
+
"""
|
|
60
|
+
if not filename.endswith(".zip"):
|
|
61
|
+
raise ApplicationException(message="file ending .zip must be included in filename!")
|
|
62
|
+
saved_zip_file_path = f"{export_location}/{filename}"
|
|
63
|
+
|
|
64
|
+
if Path(saved_zip_file_path).exists():
|
|
65
|
+
print(emoji.emojize(f"\t:error: File or folder '{saved_zip_file_path}' already exists. Exiting."))
|
|
66
|
+
raise ApplicationException("Path already exists")
|
|
67
|
+
|
|
68
|
+
with open(saved_zip_file_path, "wb") as file:
|
|
69
|
+
file.write(data)
|
|
70
|
+
print(f"Wrote zip file to '{saved_zip_file_path}'")
|