cmem-cmemc 25.6.0__py3-none-any.whl → 26.1.0rc1__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.
- cmem_cmemc/cli.py +11 -6
- cmem_cmemc/command.py +1 -1
- cmem_cmemc/command_group.py +27 -0
- cmem_cmemc/commands/acl.py +388 -20
- cmem_cmemc/commands/admin.py +10 -10
- cmem_cmemc/commands/client.py +12 -5
- cmem_cmemc/commands/config.py +106 -12
- cmem_cmemc/commands/dataset.py +162 -118
- cmem_cmemc/commands/file.py +117 -73
- cmem_cmemc/commands/graph.py +200 -72
- cmem_cmemc/commands/graph_imports.py +12 -5
- cmem_cmemc/commands/graph_insights.py +61 -25
- cmem_cmemc/commands/metrics.py +15 -9
- cmem_cmemc/commands/migration.py +12 -4
- cmem_cmemc/commands/package.py +548 -0
- cmem_cmemc/commands/project.py +155 -22
- cmem_cmemc/commands/python.py +8 -4
- cmem_cmemc/commands/query.py +119 -25
- cmem_cmemc/commands/scheduler.py +6 -4
- cmem_cmemc/commands/store.py +2 -1
- cmem_cmemc/commands/user.py +124 -24
- cmem_cmemc/commands/validation.py +15 -10
- cmem_cmemc/commands/variable.py +264 -61
- cmem_cmemc/commands/vocabulary.py +18 -13
- cmem_cmemc/commands/workflow.py +21 -11
- cmem_cmemc/completion.py +105 -105
- cmem_cmemc/context.py +38 -8
- cmem_cmemc/exceptions.py +8 -2
- cmem_cmemc/manual_helper/multi_page.py +0 -1
- cmem_cmemc/object_list.py +234 -7
- cmem_cmemc/string_processor.py +142 -5
- cmem_cmemc/title_helper.py +50 -0
- cmem_cmemc/utils.py +8 -7
- {cmem_cmemc-25.6.0.dist-info → cmem_cmemc-26.1.0rc1.dist-info}/METADATA +6 -6
- cmem_cmemc-26.1.0rc1.dist-info/RECORD +62 -0
- {cmem_cmemc-25.6.0.dist-info → cmem_cmemc-26.1.0rc1.dist-info}/WHEEL +1 -1
- cmem_cmemc-25.6.0.dist-info/RECORD +0 -61
- {cmem_cmemc-25.6.0.dist-info → cmem_cmemc-26.1.0rc1.dist-info}/entry_points.txt +0 -0
- {cmem_cmemc-25.6.0.dist-info → cmem_cmemc-26.1.0rc1.dist-info}/licenses/LICENSE +0 -0
cmem_cmemc/commands/metrics.py
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
"""metrics commands for cmem command line interface."""
|
|
2
2
|
|
|
3
3
|
import click
|
|
4
|
-
from click import Argument,
|
|
4
|
+
from click import Argument, Context, UsageError
|
|
5
5
|
from click.shell_completion import CompletionItem
|
|
6
6
|
from cmem.cmempy.api import request
|
|
7
|
-
from cmem.cmempy.config import
|
|
7
|
+
from cmem.cmempy.config import get_di_api_endpoint, get_dp_api_endpoint
|
|
8
8
|
from prometheus_client.parser import text_string_to_metric_families
|
|
9
9
|
from requests import HTTPError
|
|
10
10
|
|
|
11
11
|
from cmem_cmemc import completion
|
|
12
12
|
from cmem_cmemc.command import CmemcCommand
|
|
13
13
|
from cmem_cmemc.command_group import CmemcGroup
|
|
14
|
-
from cmem_cmemc.
|
|
14
|
+
from cmem_cmemc.completion import suppress_completion_errors
|
|
15
|
+
from cmem_cmemc.context import ApplicationContext, build_caption
|
|
16
|
+
from cmem_cmemc.exceptions import CmemcError
|
|
15
17
|
from cmem_cmemc.object_list import (
|
|
16
18
|
DirectValuePropertyFilter,
|
|
17
19
|
ObjectList,
|
|
@@ -94,6 +96,7 @@ metrics_list = ObjectList(
|
|
|
94
96
|
)
|
|
95
97
|
|
|
96
98
|
|
|
99
|
+
@suppress_completion_errors
|
|
97
100
|
def _complete_metrics_id(ctx: Context, param: Argument, incomplete: str) -> list[CompletionItem]: # noqa: ARG001
|
|
98
101
|
"""Prepare a list of metric identifier."""
|
|
99
102
|
ApplicationContext.set_connection_from_params(ctx.find_root().params)
|
|
@@ -101,6 +104,7 @@ def _complete_metrics_id(ctx: Context, param: Argument, incomplete: str) -> list
|
|
|
101
104
|
return completion.finalize_completion(candidates=candidates, incomplete=incomplete)
|
|
102
105
|
|
|
103
106
|
|
|
107
|
+
@suppress_completion_errors
|
|
104
108
|
def _complete_metric_label_filter(
|
|
105
109
|
ctx: Context,
|
|
106
110
|
param: Argument, # noqa: ARG001
|
|
@@ -141,11 +145,11 @@ def _filter_samples(family: dict, label_filter: tuple[tuple[str, str], ...]) ->
|
|
|
141
145
|
sample_labels = sample[1]
|
|
142
146
|
for name, value in label_filter:
|
|
143
147
|
if name not in labels:
|
|
144
|
-
raise
|
|
148
|
+
raise CmemcError(
|
|
145
149
|
f"The metric '{family_name}' does " f"not have a label named '{name}'."
|
|
146
150
|
)
|
|
147
151
|
if value not in labels[name]:
|
|
148
|
-
raise
|
|
152
|
+
raise CmemcError(
|
|
149
153
|
f"The metric '{family_name}' does "
|
|
150
154
|
f"not have a label '{name}' with the value '{value}'."
|
|
151
155
|
)
|
|
@@ -157,7 +161,6 @@ def _filter_samples(family: dict, label_filter: tuple[tuple[str, str], ...]) ->
|
|
|
157
161
|
return samples
|
|
158
162
|
|
|
159
163
|
|
|
160
|
-
# pylint: disable-msg=too-many-arguments
|
|
161
164
|
@click.command(cls=CmemcCommand, name="get")
|
|
162
165
|
@click.argument("metric_id", required=True, type=click.STRING, shell_complete=_complete_metrics_id)
|
|
163
166
|
@click.option(
|
|
@@ -214,7 +217,7 @@ def get_command(
|
|
|
214
217
|
return
|
|
215
218
|
|
|
216
219
|
if len(samples) == 0:
|
|
217
|
-
raise
|
|
220
|
+
raise CmemcError(
|
|
218
221
|
"No data - the given label combination filtered out "
|
|
219
222
|
f"all available samples of the metric {metric_id}."
|
|
220
223
|
)
|
|
@@ -308,12 +311,15 @@ def list_command(
|
|
|
308
311
|
]
|
|
309
312
|
for _ in data
|
|
310
313
|
]
|
|
314
|
+
filtered = len(filter_) > 0
|
|
311
315
|
app.echo_info_table(
|
|
312
316
|
table,
|
|
313
317
|
headers=["ID", "Type", "L", "S", "Documentation"],
|
|
314
318
|
sort_column=0,
|
|
315
|
-
caption=
|
|
316
|
-
empty_table_message="No metrics
|
|
319
|
+
caption=build_caption(len(table), "metric", filtered=filtered),
|
|
320
|
+
empty_table_message="No metrics found for these filters."
|
|
321
|
+
if filtered
|
|
322
|
+
else "No metrics available.",
|
|
317
323
|
)
|
|
318
324
|
|
|
319
325
|
|
cmem_cmemc/commands/migration.py
CHANGED
|
@@ -8,8 +8,12 @@ from click.shell_completion import CompletionItem
|
|
|
8
8
|
|
|
9
9
|
from cmem_cmemc.command import CmemcCommand
|
|
10
10
|
from cmem_cmemc.command_group import CmemcGroup
|
|
11
|
-
from cmem_cmemc.completion import
|
|
12
|
-
|
|
11
|
+
from cmem_cmemc.completion import (
|
|
12
|
+
check_option_in_params,
|
|
13
|
+
finalize_completion,
|
|
14
|
+
suppress_completion_errors,
|
|
15
|
+
)
|
|
16
|
+
from cmem_cmemc.context import ApplicationContext, build_caption
|
|
13
17
|
from cmem_cmemc.migrations.access_conditions_243 import (
|
|
14
18
|
MoveAccessConditionsToNewGraph,
|
|
15
19
|
RenameAuthVocabularyResources,
|
|
@@ -65,6 +69,7 @@ def get_migrations(ctx: click.Context) -> list[dict]: # noqa: ARG001
|
|
|
65
69
|
return data
|
|
66
70
|
|
|
67
71
|
|
|
72
|
+
@suppress_completion_errors
|
|
68
73
|
def complete_migration_ids(ctx: Context, param: Argument, incomplete: str) -> list[CompletionItem]:
|
|
69
74
|
"""Prepare a list of migration recipe IDs"""
|
|
70
75
|
ApplicationContext.set_connection_from_params(ctx.find_root().params)
|
|
@@ -153,12 +158,15 @@ def list_command(
|
|
|
153
158
|
]
|
|
154
159
|
for _ in data
|
|
155
160
|
]
|
|
161
|
+
filtered = len(filter_) > 0
|
|
156
162
|
app.echo_info_table(
|
|
157
163
|
table,
|
|
158
164
|
headers=["ID", "Description", "Tags", "First Version"],
|
|
159
165
|
sort_column=3,
|
|
160
|
-
caption=
|
|
161
|
-
empty_table_message="No migrations
|
|
166
|
+
caption=build_caption(len(table), "migration", filtered=filtered),
|
|
167
|
+
empty_table_message="No migrations found for these filters."
|
|
168
|
+
if filtered
|
|
169
|
+
else "No migrations available.",
|
|
162
170
|
)
|
|
163
171
|
|
|
164
172
|
|
|
@@ -0,0 +1,548 @@
|
|
|
1
|
+
"""Package command group"""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
import requests
|
|
9
|
+
from click.shell_completion import CompletionItem
|
|
10
|
+
from cmem_client.client import Client
|
|
11
|
+
from cmem_client.repositories.marketplace_packages import (
|
|
12
|
+
MarketplacePackagesExportConfig,
|
|
13
|
+
MarketplacePackagesImportConfig,
|
|
14
|
+
MarketplacePackagesRepository,
|
|
15
|
+
)
|
|
16
|
+
from eccenca_marketplace_client.manifests import AbstractPackageManifest, ManifestMetadata
|
|
17
|
+
from eccenca_marketplace_client.package_version import PackageVersion
|
|
18
|
+
from pydantic_extra_types.semantic_version import SemanticVersion
|
|
19
|
+
|
|
20
|
+
from cmem_cmemc import completion
|
|
21
|
+
from cmem_cmemc.command import CmemcCommand
|
|
22
|
+
from cmem_cmemc.command_group import CmemcGroup
|
|
23
|
+
from cmem_cmemc.completion import suppress_completion_errors
|
|
24
|
+
from cmem_cmemc.context import ApplicationContext
|
|
25
|
+
from cmem_cmemc.exceptions import CmemcError
|
|
26
|
+
from cmem_cmemc.object_list import (
|
|
27
|
+
DirectValuePropertyFilter,
|
|
28
|
+
ObjectList,
|
|
29
|
+
compare_regex,
|
|
30
|
+
compare_str_equality,
|
|
31
|
+
)
|
|
32
|
+
from cmem_cmemc.parameter_types.path import ClickSmartPath
|
|
33
|
+
from cmem_cmemc.utils import struct_to_table
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def get_installed_packages_list(ctx: click.Context) -> list[dict]:
|
|
37
|
+
"""Get the list of installed packages"""
|
|
38
|
+
ApplicationContext.set_connection_from_params(ctx.find_root().params)
|
|
39
|
+
client = Client.from_cmempy()
|
|
40
|
+
packages: MarketplacePackagesRepository = client.marketplace_packages
|
|
41
|
+
return [
|
|
42
|
+
{
|
|
43
|
+
"id": package_.package_version.manifest.package_id,
|
|
44
|
+
"type": str(package_.package_version.manifest.package_type).replace(
|
|
45
|
+
"PackageTypes.", ""
|
|
46
|
+
),
|
|
47
|
+
"version": str(package_.package_version.manifest.package_version),
|
|
48
|
+
"name": package_.package_version.manifest.metadata.name,
|
|
49
|
+
"description": package_.package_version.manifest.metadata.description,
|
|
50
|
+
}
|
|
51
|
+
for package_ in packages.values()
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
installed_packages_list = ObjectList(
|
|
56
|
+
name="installed packages",
|
|
57
|
+
get_objects=get_installed_packages_list,
|
|
58
|
+
filters=[
|
|
59
|
+
DirectValuePropertyFilter(
|
|
60
|
+
name="type",
|
|
61
|
+
description="Filter list by package type.",
|
|
62
|
+
property_key="type",
|
|
63
|
+
),
|
|
64
|
+
DirectValuePropertyFilter(
|
|
65
|
+
name="name",
|
|
66
|
+
description="Filter list by regex matching the package name.",
|
|
67
|
+
property_key="name",
|
|
68
|
+
compare=compare_regex,
|
|
69
|
+
fixed_completion=[],
|
|
70
|
+
),
|
|
71
|
+
DirectValuePropertyFilter(
|
|
72
|
+
name="id",
|
|
73
|
+
description="Filter list by package ID.",
|
|
74
|
+
property_key="id",
|
|
75
|
+
compare=compare_str_equality,
|
|
76
|
+
),
|
|
77
|
+
],
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@suppress_completion_errors
|
|
82
|
+
def _complete_installed_package_ids(
|
|
83
|
+
ctx: click.Context,
|
|
84
|
+
param: click.Argument, # noqa: ARG001
|
|
85
|
+
incomplete: str,
|
|
86
|
+
) -> list[CompletionItem]:
|
|
87
|
+
"""Prepare a list of IDs of installed packages."""
|
|
88
|
+
ApplicationContext.set_connection_from_params(ctx.find_root().params)
|
|
89
|
+
candidates = [
|
|
90
|
+
(_["id"], f"{_['version']}: {_['name']}")
|
|
91
|
+
for _ in installed_packages_list.apply_filters(ctx=ctx)
|
|
92
|
+
]
|
|
93
|
+
return completion.finalize_completion(candidates=candidates, incomplete=incomplete)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@click.command(cls=CmemcCommand, name="create")
|
|
97
|
+
@click.argument("package_id", required=True)
|
|
98
|
+
@click.option(
|
|
99
|
+
"--name",
|
|
100
|
+
type=click.STRING,
|
|
101
|
+
help=ManifestMetadata.model_fields.get("name").description + " Defaults to package ID.",
|
|
102
|
+
)
|
|
103
|
+
@click.option(
|
|
104
|
+
"--version",
|
|
105
|
+
type=click.STRING,
|
|
106
|
+
default="0.0.1",
|
|
107
|
+
show_default=True,
|
|
108
|
+
help=AbstractPackageManifest.model_fields.get("package_version").description,
|
|
109
|
+
)
|
|
110
|
+
@click.option(
|
|
111
|
+
"--description",
|
|
112
|
+
type=click.STRING,
|
|
113
|
+
help=ManifestMetadata.model_fields.get("description").description,
|
|
114
|
+
default="This is the first version of a wonderful eccenca Corporate Memory package 🤓",
|
|
115
|
+
show_default=True,
|
|
116
|
+
)
|
|
117
|
+
@click.pass_obj
|
|
118
|
+
def create_command(
|
|
119
|
+
app: ApplicationContext, package_id: str, version: str, name: str | None, description: str
|
|
120
|
+
) -> None:
|
|
121
|
+
"""Initialize an empty package directory with a minimal manifest."""
|
|
122
|
+
if Path(package_id).exists():
|
|
123
|
+
raise click.UsageError(f"Package directory '{package_id}' already exists.")
|
|
124
|
+
if name is None:
|
|
125
|
+
name = package_id
|
|
126
|
+
manifest_src = {
|
|
127
|
+
"package_id": package_id,
|
|
128
|
+
"package_type": "project",
|
|
129
|
+
"package_version": version,
|
|
130
|
+
"metadata": {
|
|
131
|
+
"name": name,
|
|
132
|
+
"description": description,
|
|
133
|
+
},
|
|
134
|
+
"files": [],
|
|
135
|
+
}
|
|
136
|
+
package_version = PackageVersion.from_json(json.dumps(manifest_src))
|
|
137
|
+
directory = Path(package_id)
|
|
138
|
+
app.echo_info(f"Initialize package directory '{directory}' ... ", nl=False)
|
|
139
|
+
directory.mkdir(parents=True, exist_ok=True)
|
|
140
|
+
manifest = directory / "manifest.json"
|
|
141
|
+
manifest.write_text(package_version.manifest.model_dump_json(indent=2))
|
|
142
|
+
app.echo_success("done")
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
@click.command(cls=CmemcCommand, name="inspect")
|
|
146
|
+
@click.argument(
|
|
147
|
+
"PACKAGE_PATH",
|
|
148
|
+
required=True,
|
|
149
|
+
type=ClickSmartPath(
|
|
150
|
+
allow_dash=False,
|
|
151
|
+
dir_okay=True,
|
|
152
|
+
readable=True,
|
|
153
|
+
exists=True,
|
|
154
|
+
remote_okay=True,
|
|
155
|
+
),
|
|
156
|
+
)
|
|
157
|
+
@click.option(
|
|
158
|
+
"--key",
|
|
159
|
+
"key",
|
|
160
|
+
help="Get a specific key only from the manifest.",
|
|
161
|
+
)
|
|
162
|
+
@click.option("--raw", is_flag=True, help="Outputs raw JSON.")
|
|
163
|
+
@click.pass_obj
|
|
164
|
+
def inspect_command(app: ApplicationContext, package_path: Path, key: str, raw: str) -> None:
|
|
165
|
+
"""Inspect the manifest of a package."""
|
|
166
|
+
path = Path(package_path)
|
|
167
|
+
package_version = (
|
|
168
|
+
PackageVersion.from_directory(path) if path.is_dir() else PackageVersion.from_archive(path)
|
|
169
|
+
)
|
|
170
|
+
manifest = package_version.manifest
|
|
171
|
+
manifest_data = json.loads(manifest.model_dump_json(indent=2))
|
|
172
|
+
if raw:
|
|
173
|
+
app.echo_info_json(manifest_data)
|
|
174
|
+
return
|
|
175
|
+
if key:
|
|
176
|
+
table = [
|
|
177
|
+
line
|
|
178
|
+
for line in struct_to_table(manifest_data)
|
|
179
|
+
if line[0].startswith(key) or key == "all"
|
|
180
|
+
]
|
|
181
|
+
if len(table) == 1:
|
|
182
|
+
app.echo_info(table[0][1])
|
|
183
|
+
return
|
|
184
|
+
if len(table) == 0:
|
|
185
|
+
raise CmemcError(f"No values for key '{key}'.")
|
|
186
|
+
app.echo_info_table(table, headers=["Key", "Value"], sort_column=0)
|
|
187
|
+
return
|
|
188
|
+
table = struct_to_table(manifest_data)
|
|
189
|
+
app.echo_info_table(
|
|
190
|
+
table,
|
|
191
|
+
headers=["Key", "Value"],
|
|
192
|
+
sort_column=0,
|
|
193
|
+
caption=f"Manifest of package '{manifest.package_id}' in"
|
|
194
|
+
f" {path.name} (v{manifest.package_version})",
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
@click.command(cls=CmemcCommand, name="list")
|
|
199
|
+
@click.option(
|
|
200
|
+
"--filter",
|
|
201
|
+
"filter_",
|
|
202
|
+
multiple=True,
|
|
203
|
+
type=(str, str),
|
|
204
|
+
shell_complete=installed_packages_list.complete_values,
|
|
205
|
+
help=installed_packages_list.get_filter_help_text(),
|
|
206
|
+
)
|
|
207
|
+
@click.option(
|
|
208
|
+
"--id-only",
|
|
209
|
+
is_flag=True,
|
|
210
|
+
help="Lists only package IDs. This is useful for piping the IDs into other commands.",
|
|
211
|
+
)
|
|
212
|
+
@click.option("--raw", is_flag=True, help="Outputs raw JSON.")
|
|
213
|
+
@click.pass_context
|
|
214
|
+
def list_command(
|
|
215
|
+
ctx: click.Context, filter_: tuple[tuple[str, str]], id_only: bool, raw: bool
|
|
216
|
+
) -> None:
|
|
217
|
+
"""List installed packages."""
|
|
218
|
+
app: ApplicationContext = ctx.obj
|
|
219
|
+
data = installed_packages_list.apply_filters(ctx=ctx, filter_=filter_)
|
|
220
|
+
if id_only:
|
|
221
|
+
for _ in sorted(data, key=lambda _: _["id"]):
|
|
222
|
+
app.echo_info(_["id"])
|
|
223
|
+
return
|
|
224
|
+
if raw:
|
|
225
|
+
app.echo_info_json(data)
|
|
226
|
+
return
|
|
227
|
+
table = [
|
|
228
|
+
(
|
|
229
|
+
_["id"],
|
|
230
|
+
_["version"],
|
|
231
|
+
_["type"],
|
|
232
|
+
_["name"],
|
|
233
|
+
)
|
|
234
|
+
for _ in data
|
|
235
|
+
]
|
|
236
|
+
app.echo_info_table(
|
|
237
|
+
table,
|
|
238
|
+
headers=["ID", "Version", "Type", "Name"],
|
|
239
|
+
sort_column=0,
|
|
240
|
+
empty_table_message="No installed packages found. "
|
|
241
|
+
"You can use the `package install` command to install packages.",
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
@click.command(cls=CmemcCommand, name="install")
|
|
246
|
+
@click.argument(
|
|
247
|
+
"PACKAGE_ID",
|
|
248
|
+
required=False,
|
|
249
|
+
type=click.STRING,
|
|
250
|
+
)
|
|
251
|
+
@click.option(
|
|
252
|
+
"--input",
|
|
253
|
+
"-i",
|
|
254
|
+
"input_path",
|
|
255
|
+
type=ClickSmartPath(allow_dash=False, dir_okay=True, file_okay=True, readable=True),
|
|
256
|
+
help="Install a package from a package archive (.cpa) or directory.",
|
|
257
|
+
)
|
|
258
|
+
@click.option(
|
|
259
|
+
"--replace", is_flag=True, help="Replace (overwrite) existing package version, if present."
|
|
260
|
+
)
|
|
261
|
+
@click.pass_obj
|
|
262
|
+
def install_command(
|
|
263
|
+
app: ApplicationContext, package_id: str, replace: bool, input_path: str
|
|
264
|
+
) -> None:
|
|
265
|
+
"""Install packages.
|
|
266
|
+
|
|
267
|
+
This command installs a package either from the marketplace or from local package
|
|
268
|
+
archives (.cpa) or directories.
|
|
269
|
+
"""
|
|
270
|
+
if not package_id and not input_path:
|
|
271
|
+
raise CmemcError(
|
|
272
|
+
"Nothing to install. Either specify a package ID from the marketplace, "
|
|
273
|
+
"or use the `--input` option to install a local package."
|
|
274
|
+
)
|
|
275
|
+
if package_id and input_path:
|
|
276
|
+
raise CmemcError(
|
|
277
|
+
"You can not install from the marketplace and local files at the same time."
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
packages = app.client.marketplace_packages
|
|
281
|
+
if input_path:
|
|
282
|
+
package_path = Path(input_path)
|
|
283
|
+
package_version = (
|
|
284
|
+
PackageVersion.from_directory(package_path)
|
|
285
|
+
if package_path.is_dir()
|
|
286
|
+
else PackageVersion.from_archive(package_path)
|
|
287
|
+
)
|
|
288
|
+
package_id = package_version.manifest.package_id
|
|
289
|
+
app.echo_info(f"Installing package '{package_id}' from '{input_path}' ... ", nl=False)
|
|
290
|
+
packages.import_item(
|
|
291
|
+
path=package_path,
|
|
292
|
+
replace=replace,
|
|
293
|
+
configuration=MarketplacePackagesImportConfig(install_from_marketplace=False),
|
|
294
|
+
)
|
|
295
|
+
else:
|
|
296
|
+
app.echo_info(f"Installing package '{package_id}' from marketplace ... ", nl=False)
|
|
297
|
+
packages.import_item(key=package_id, replace=replace)
|
|
298
|
+
app.echo_success("done")
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def _filter_installed_packages(
|
|
302
|
+
ctx: click.Context, package_id: str | None, filter_: tuple[tuple[str, str]], all_: bool
|
|
303
|
+
) -> list[dict]:
|
|
304
|
+
"""Filter installed packages."""
|
|
305
|
+
if package_id is None and not filter_ and not all_:
|
|
306
|
+
raise click.UsageError("Either provide a package ID or a filter, or use the --all flag.")
|
|
307
|
+
|
|
308
|
+
if all_:
|
|
309
|
+
packages_to_work_on = installed_packages_list.apply_filters(ctx=ctx)
|
|
310
|
+
else:
|
|
311
|
+
filter_to_apply = list(filter_) if filter_ else []
|
|
312
|
+
if package_id:
|
|
313
|
+
filter_to_apply.append(("id", package_id))
|
|
314
|
+
packages_to_work_on = installed_packages_list.apply_filters(
|
|
315
|
+
ctx=ctx, filter_=filter_to_apply
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
if not packages_to_work_on and package_id:
|
|
319
|
+
raise CmemcError(f"Package '{package_id}' is not installed.")
|
|
320
|
+
return packages_to_work_on
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
@click.command(cls=CmemcCommand, name="uninstall")
|
|
324
|
+
@click.argument(
|
|
325
|
+
"PACKAGE_ID",
|
|
326
|
+
type=click.STRING,
|
|
327
|
+
shell_complete=_complete_installed_package_ids,
|
|
328
|
+
required=False,
|
|
329
|
+
)
|
|
330
|
+
@click.option(
|
|
331
|
+
"--filter",
|
|
332
|
+
"filter_",
|
|
333
|
+
multiple=True,
|
|
334
|
+
type=(str, str),
|
|
335
|
+
shell_complete=installed_packages_list.complete_values,
|
|
336
|
+
help=installed_packages_list.get_filter_help_text(),
|
|
337
|
+
)
|
|
338
|
+
@click.option(
|
|
339
|
+
"-a",
|
|
340
|
+
"--all",
|
|
341
|
+
"all_",
|
|
342
|
+
is_flag=True,
|
|
343
|
+
help="Uninstall all packages. This is a dangerous option, so use it with care.",
|
|
344
|
+
)
|
|
345
|
+
@click.pass_context
|
|
346
|
+
def uninstall_command(
|
|
347
|
+
ctx: click.Context,
|
|
348
|
+
package_id: str | None,
|
|
349
|
+
filter_: tuple[tuple[str, str]],
|
|
350
|
+
all_: bool,
|
|
351
|
+
) -> None:
|
|
352
|
+
"""Uninstall installed packages."""
|
|
353
|
+
app: ApplicationContext = ctx.obj
|
|
354
|
+
packages_to_uninstall = _filter_installed_packages(
|
|
355
|
+
ctx=ctx, package_id=package_id, filter_=filter_, all_=all_
|
|
356
|
+
)
|
|
357
|
+
packages = app.client.marketplace_packages
|
|
358
|
+
for _ in packages_to_uninstall:
|
|
359
|
+
id_to_uninstall = _["id"]
|
|
360
|
+
app.echo_info(f"Uninstalling package {id_to_uninstall} ... ", nl=False)
|
|
361
|
+
packages.delete_item(id_to_uninstall)
|
|
362
|
+
app.echo_success("done")
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
@click.command(cls=CmemcCommand, name="export")
|
|
366
|
+
@click.argument(
|
|
367
|
+
"PACKAGE_ID",
|
|
368
|
+
type=click.STRING,
|
|
369
|
+
shell_complete=_complete_installed_package_ids,
|
|
370
|
+
required=False,
|
|
371
|
+
)
|
|
372
|
+
@click.option(
|
|
373
|
+
"--filter",
|
|
374
|
+
"filter_",
|
|
375
|
+
multiple=True,
|
|
376
|
+
type=(str, str),
|
|
377
|
+
shell_complete=installed_packages_list.complete_values,
|
|
378
|
+
help=installed_packages_list.get_filter_help_text(),
|
|
379
|
+
)
|
|
380
|
+
@click.option(
|
|
381
|
+
"-a",
|
|
382
|
+
"--all",
|
|
383
|
+
"all_",
|
|
384
|
+
is_flag=True,
|
|
385
|
+
help="Export all installed packages.",
|
|
386
|
+
)
|
|
387
|
+
@click.option("--replace", is_flag=True, help="Replace (overwrite) existing files, if present.")
|
|
388
|
+
@click.pass_context
|
|
389
|
+
def export_command(
|
|
390
|
+
ctx: click.Context,
|
|
391
|
+
package_id: str | None,
|
|
392
|
+
filter_: tuple[tuple[str, str]],
|
|
393
|
+
all_: bool,
|
|
394
|
+
replace: bool,
|
|
395
|
+
) -> None:
|
|
396
|
+
"""Export installed packages to package directories."""
|
|
397
|
+
app: ApplicationContext = ctx.obj
|
|
398
|
+
packages_to_export = _filter_installed_packages(
|
|
399
|
+
ctx=ctx, package_id=package_id, filter_=filter_, all_=all_
|
|
400
|
+
)
|
|
401
|
+
packages = app.client.marketplace_packages
|
|
402
|
+
for _ in packages_to_export:
|
|
403
|
+
id_to_export = _["id"]
|
|
404
|
+
app.echo_info(f"Exporting package `{id_to_export}` ... ", nl=False)
|
|
405
|
+
configuration = MarketplacePackagesExportConfig(export_as_zip=False)
|
|
406
|
+
packages.export_item(
|
|
407
|
+
id_to_export,
|
|
408
|
+
path=Path(id_to_export),
|
|
409
|
+
replace=replace,
|
|
410
|
+
configuration=configuration,
|
|
411
|
+
)
|
|
412
|
+
app.echo_success("done")
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
@click.command(cls=CmemcCommand, name="build")
|
|
416
|
+
@click.argument(
|
|
417
|
+
"PACKAGE_DIRECTORY",
|
|
418
|
+
required=True,
|
|
419
|
+
type=ClickSmartPath(
|
|
420
|
+
allow_dash=False,
|
|
421
|
+
dir_okay=True,
|
|
422
|
+
file_okay=False,
|
|
423
|
+
readable=True,
|
|
424
|
+
exists=True,
|
|
425
|
+
remote_okay=False,
|
|
426
|
+
),
|
|
427
|
+
)
|
|
428
|
+
@click.option("--version", help="Set the package version.")
|
|
429
|
+
@click.option("--replace", is_flag=True, help="Replace package archive, if present.")
|
|
430
|
+
@click.option(
|
|
431
|
+
"--output-dir",
|
|
432
|
+
type=ClickSmartPath(writable=True, file_okay=False, dir_okay=True),
|
|
433
|
+
help="Create the package archive in a specific directory.",
|
|
434
|
+
default=".",
|
|
435
|
+
show_default=True,
|
|
436
|
+
)
|
|
437
|
+
@click.pass_obj
|
|
438
|
+
def build_command(
|
|
439
|
+
app: ApplicationContext, package_directory: str, version: str, replace: bool, output_dir: str
|
|
440
|
+
) -> None:
|
|
441
|
+
"""Build a package archive from a package directory.
|
|
442
|
+
|
|
443
|
+
This command processes a package directory, validates its content including the manifest,
|
|
444
|
+
and creates a versioned Corporate Memory package archive (.cpa) with the following naming
|
|
445
|
+
convention: {package_id}-v{version}.cpa
|
|
446
|
+
|
|
447
|
+
Package archives can be published to the marketplace using the `package publish` command.
|
|
448
|
+
"""
|
|
449
|
+
package_path = Path(package_directory)
|
|
450
|
+
package_version = PackageVersion.from_directory(package_path)
|
|
451
|
+
if version:
|
|
452
|
+
if version.startswith("v"):
|
|
453
|
+
version = version[1:]
|
|
454
|
+
package_version.manifest.package_version = SemanticVersion.parse(version)
|
|
455
|
+
version_str = str(package_version.manifest.package_version)
|
|
456
|
+
package_id = package_version.manifest.package_id
|
|
457
|
+
|
|
458
|
+
if output_dir is not None:
|
|
459
|
+
cpa_file = Path(
|
|
460
|
+
os.path.normpath(str(Path(output_dir) / f"{package_id}-v{version_str}.cpa"))
|
|
461
|
+
)
|
|
462
|
+
else:
|
|
463
|
+
cpa_file = Path(f"{package_id}-v{version_str}.cpa")
|
|
464
|
+
|
|
465
|
+
Path(cpa_file).parent.mkdir(exist_ok=True, parents=True)
|
|
466
|
+
|
|
467
|
+
if version_str.endswith("dirty"):
|
|
468
|
+
app.echo_warning(
|
|
469
|
+
"Dirty Repository: Your version strings ends with 'dirty'."
|
|
470
|
+
"This indicates an unclean repository."
|
|
471
|
+
)
|
|
472
|
+
if cpa_file.exists() and not replace:
|
|
473
|
+
raise CmemcError(
|
|
474
|
+
f"Package archive `{cpa_file}` already exists. Use `--replace` to overwrite."
|
|
475
|
+
)
|
|
476
|
+
app.echo_info(f"Building package archive `{cpa_file.name}` ... ", nl=False)
|
|
477
|
+
package_version.build_archive(archive=cpa_file)
|
|
478
|
+
app.echo_success("done")
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
@click.command(cls=CmemcCommand, name="publish")
|
|
482
|
+
@click.argument(
|
|
483
|
+
"PACKAGE",
|
|
484
|
+
required=True,
|
|
485
|
+
type=ClickSmartPath(
|
|
486
|
+
allow_dash=False,
|
|
487
|
+
dir_okay=True,
|
|
488
|
+
readable=True,
|
|
489
|
+
exists=True,
|
|
490
|
+
remote_okay=True,
|
|
491
|
+
),
|
|
492
|
+
)
|
|
493
|
+
@click.option(
|
|
494
|
+
"--marketplace-url",
|
|
495
|
+
type=str,
|
|
496
|
+
help="Alternative Marketplace URL.",
|
|
497
|
+
default="https://marketplace.eccenca.dev/",
|
|
498
|
+
)
|
|
499
|
+
@click.pass_obj
|
|
500
|
+
def publish_command(app: ApplicationContext, package: str, marketplace_url: str) -> None:
|
|
501
|
+
"""Publish a package archive to the marketplace server."""
|
|
502
|
+
package_path = Path(package)
|
|
503
|
+
|
|
504
|
+
package_version = (
|
|
505
|
+
PackageVersion.from_directory(package_path)
|
|
506
|
+
if package_path.is_dir()
|
|
507
|
+
else PackageVersion.from_archive(package_path)
|
|
508
|
+
)
|
|
509
|
+
package_id = package_version.manifest.package_id
|
|
510
|
+
|
|
511
|
+
app.echo_info(f"Publishing package `{package_id}` ... ", nl=False)
|
|
512
|
+
|
|
513
|
+
if package_path.is_dir():
|
|
514
|
+
package_data = b"".join(PackageVersion.create_archive(package_path))
|
|
515
|
+
filename = f"{package_id}.cpa"
|
|
516
|
+
else:
|
|
517
|
+
package_data = package_path.read_bytes()
|
|
518
|
+
filename = package_path.name
|
|
519
|
+
|
|
520
|
+
if marketplace_url.endswith("/"):
|
|
521
|
+
marketplace_url = marketplace_url[:-1]
|
|
522
|
+
|
|
523
|
+
files = {"archive": (filename, package_data, "application/octet-stream")}
|
|
524
|
+
url = f"{marketplace_url}/api/packages/{package_id}/versions"
|
|
525
|
+
response = requests.post(
|
|
526
|
+
url=url,
|
|
527
|
+
timeout=30,
|
|
528
|
+
files=files,
|
|
529
|
+
headers={"accept": "application/json"},
|
|
530
|
+
)
|
|
531
|
+
|
|
532
|
+
response.raise_for_status()
|
|
533
|
+
app.echo_success("done")
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
@click.group(cls=CmemcGroup)
|
|
537
|
+
def package_group() -> CmemcGroup: # type: ignore[empty-body]
|
|
538
|
+
"""List, (un)install, export, create, or inspect packages."""
|
|
539
|
+
|
|
540
|
+
|
|
541
|
+
package_group.add_command(create_command)
|
|
542
|
+
package_group.add_command(inspect_command)
|
|
543
|
+
package_group.add_command(list_command)
|
|
544
|
+
package_group.add_command(install_command)
|
|
545
|
+
package_group.add_command(uninstall_command)
|
|
546
|
+
package_group.add_command(export_command)
|
|
547
|
+
package_group.add_command(build_command)
|
|
548
|
+
package_group.add_command(publish_command)
|