cmem-cmemc 24.3.0rc1__tar.gz → 24.3.2__tar.gz
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-24.3.0rc1 → cmem_cmemc-24.3.2}/PKG-INFO +4 -4
- {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.2}/cmem_cmemc/__init__.py +1 -1
- {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.2}/cmem_cmemc/commands/graph.py +78 -32
- {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.2}/cmem_cmemc/commands/migration.py +2 -0
- {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.2}/cmem_cmemc/commands/python.py +2 -2
- {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.2}/cmem_cmemc/completion.py +11 -5
- {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.2}/cmem_cmemc/context.py +2 -2
- {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.2}/cmem_cmemc/migrations/access_conditions_243.py +4 -0
- {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.2}/cmem_cmemc/migrations/bootstrap_data.py +2 -4
- {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.2}/cmem_cmemc/migrations/shapes_widget_integrations_243.py +80 -0
- {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.2}/cmem_cmemc/string_processor.py +7 -1
- {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.2}/cmem_cmemc/utils.py +22 -34
- {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.2}/pyproject.toml +2 -2
- {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.2}/LICENSE +0 -0
- {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.2}/README-public.md +0 -0
- {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.2}/cmem_cmemc/_cmemc.zsh +0 -0
- {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.2}/cmem_cmemc/command.py +0 -0
- {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.2}/cmem_cmemc/command_group.py +0 -0
- {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.2}/cmem_cmemc/commands/__init__.py +0 -0
- {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.2}/cmem_cmemc/commands/acl.py +0 -0
- {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.2}/cmem_cmemc/commands/admin.py +0 -0
- {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.2}/cmem_cmemc/commands/client.py +0 -0
- {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.2}/cmem_cmemc/commands/config.py +0 -0
- {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.2}/cmem_cmemc/commands/dataset.py +0 -0
- {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.2}/cmem_cmemc/commands/metrics.py +0 -0
- {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.2}/cmem_cmemc/commands/project.py +0 -0
- {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.2}/cmem_cmemc/commands/query.py +0 -0
- {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.2}/cmem_cmemc/commands/resource.py +0 -0
- {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.2}/cmem_cmemc/commands/scheduler.py +0 -0
- {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.2}/cmem_cmemc/commands/store.py +0 -0
- {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.2}/cmem_cmemc/commands/user.py +0 -0
- {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.2}/cmem_cmemc/commands/validation.py +0 -0
- {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.2}/cmem_cmemc/commands/variable.py +0 -0
- {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.2}/cmem_cmemc/commands/vocabulary.py +0 -0
- {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.2}/cmem_cmemc/commands/workflow.py +0 -0
- {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.2}/cmem_cmemc/commands/workspace.py +0 -0
- {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.2}/cmem_cmemc/constants.py +0 -0
- {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.2}/cmem_cmemc/exceptions.py +0 -0
- {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.2}/cmem_cmemc/manual_helper/__init__.py +0 -0
- {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.2}/cmem_cmemc/manual_helper/graph.py +0 -0
- {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.2}/cmem_cmemc/manual_helper/multi_page.py +0 -0
- {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.2}/cmem_cmemc/manual_helper/single_page.py +0 -0
- {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.2}/cmem_cmemc/migrations/__init__.py +0 -0
- {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.2}/cmem_cmemc/migrations/abc.py +0 -0
- {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.2}/cmem_cmemc/migrations/workspace_configurations.py +0 -0
- {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.2}/cmem_cmemc/object_list.py +0 -0
- {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.2}/cmem_cmemc/parameter_types/__init__.py +0 -0
- {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.2}/cmem_cmemc/parameter_types/path.py +0 -0
- {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.2}/cmem_cmemc/smart_path/__init__.py +0 -0
- {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.2}/cmem_cmemc/smart_path/clients/__init__.py +0 -0
- {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.2}/cmem_cmemc/smart_path/clients/http.py +0 -0
- {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.2}/cmem_cmemc/title_helper.py +0 -0
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
2
|
Name: cmem-cmemc
|
|
3
|
-
Version: 24.3.
|
|
3
|
+
Version: 24.3.2
|
|
4
4
|
Summary: Command line client for eccenca Corporate Memory
|
|
5
|
-
Home-page: https://eccenca.com/go/cmemc
|
|
6
5
|
License: Apache-2.0
|
|
7
6
|
Author: eccenca
|
|
8
7
|
Author-email: cmempy-developer@eccenca.com
|
|
@@ -21,6 +20,7 @@ Classifier: Programming Language :: Python :: 3
|
|
|
21
20
|
Classifier: Programming Language :: Python :: 3.10
|
|
22
21
|
Classifier: Programming Language :: Python :: 3.11
|
|
23
22
|
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
24
24
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
25
25
|
Classifier: Topic :: Database
|
|
26
26
|
Classifier: Topic :: Software Development :: Testing
|
|
@@ -36,7 +36,6 @@ Requires-Dist: jinja2 (>=3.1.4,<4.0.0)
|
|
|
36
36
|
Requires-Dist: junit-xml (>=1.9,<2.0)
|
|
37
37
|
Requires-Dist: natsort (>=8.4.0,<9.0.0)
|
|
38
38
|
Requires-Dist: packaging (>=24.2,<25.0)
|
|
39
|
-
Requires-Dist: pip (>=24.3.1,<25.0.0)
|
|
40
39
|
Requires-Dist: prometheus-client (>=0.21.0,<0.22.0)
|
|
41
40
|
Requires-Dist: pygments (>=2.18.0,<3.0.0)
|
|
42
41
|
Requires-Dist: pyjwt (>=2.9.0,<3.0.0)
|
|
@@ -48,6 +47,7 @@ Requires-Dist: smart-open (>=7.0.5,<8.0.0)
|
|
|
48
47
|
Requires-Dist: timeago (>=1.0.16,<2.0.0)
|
|
49
48
|
Requires-Dist: treelib (>=1.7.0,<2.0.0)
|
|
50
49
|
Requires-Dist: urllib3 (>=2.2.3,<3.0.0)
|
|
50
|
+
Project-URL: Homepage, https://eccenca.com/go/cmemc
|
|
51
51
|
Description-Content-Type: text/markdown
|
|
52
52
|
|
|
53
53
|
# cmemc
|
|
@@ -95,7 +95,7 @@ def cli(
|
|
|
95
95
|
|
|
96
96
|
https://eccenca.com/go/cmemc
|
|
97
97
|
|
|
98
|
-
cmemc is ©
|
|
98
|
+
cmemc is © 2025 eccenca GmbH, licensed under the Apache License 2.0.
|
|
99
99
|
"""
|
|
100
100
|
ctx.obj = CONTEXT
|
|
101
101
|
# hidden feature: 'CMEMC_MANUAL=true cmemc -q config list' will output
|
|
@@ -4,6 +4,7 @@ import gzip
|
|
|
4
4
|
import hashlib
|
|
5
5
|
import io
|
|
6
6
|
import json
|
|
7
|
+
import mimetypes
|
|
7
8
|
import os
|
|
8
9
|
from xml.dom import minidom # nosec
|
|
9
10
|
from xml.etree.ElementTree import ( # nosec
|
|
@@ -29,7 +30,7 @@ from cmem_cmemc.command_group import CmemcGroup
|
|
|
29
30
|
from cmem_cmemc.commands.validation import validation_group
|
|
30
31
|
from cmem_cmemc.context import ApplicationContext
|
|
31
32
|
from cmem_cmemc.parameter_types.path import ClickSmartPath
|
|
32
|
-
from cmem_cmemc.smart_path import SmartPath
|
|
33
|
+
from cmem_cmemc.smart_path import SmartPath
|
|
33
34
|
from cmem_cmemc.utils import (
|
|
34
35
|
convert_uri_to_filename,
|
|
35
36
|
get_graphs,
|
|
@@ -69,7 +70,7 @@ def _get_graph_to_file( # noqa: PLR0913
|
|
|
69
70
|
|
|
70
71
|
numbers is a tuple of current and count (for output only).
|
|
71
72
|
"""
|
|
72
|
-
if
|
|
73
|
+
if SmartPath(file_path).exists():
|
|
73
74
|
if overwrite is True:
|
|
74
75
|
app.echo_warning(f"Output file {file_path} does exist: will overwrite it.")
|
|
75
76
|
else:
|
|
@@ -103,7 +104,7 @@ def _get_graph_to_file( # noqa: PLR0913
|
|
|
103
104
|
|
|
104
105
|
|
|
105
106
|
def _get_export_names(
|
|
106
|
-
app: ApplicationContext, iris: list[str], template: str, file_extension: str = "ttl"
|
|
107
|
+
app: ApplicationContext, iris: list[str], template: str, file_extension: str = ".ttl"
|
|
107
108
|
) -> dict:
|
|
108
109
|
"""Get a dictionary of generated file names based on a template.
|
|
109
110
|
|
|
@@ -131,7 +132,7 @@ def _get_export_names(
|
|
|
131
132
|
hash=hashlib.sha256(iri.encode("utf-8")).hexdigest(),
|
|
132
133
|
iriname=convert_uri_to_filename(iri),
|
|
133
134
|
)
|
|
134
|
-
_name_created = f"{Template(template).render(template_data)}
|
|
135
|
+
_name_created = f"{Template(template).render(template_data)}{file_extension}"
|
|
135
136
|
_names[iri] = _name_created
|
|
136
137
|
if len(_names.values()) != len(set(_names.values())):
|
|
137
138
|
raise ValueError(
|
|
@@ -303,7 +304,7 @@ def _create_xml_catalog_file(app: ApplicationContext, names: dict, output_dir: s
|
|
|
303
304
|
output_dir: path where to create the XML file
|
|
304
305
|
|
|
305
306
|
"""
|
|
306
|
-
file_name =
|
|
307
|
+
file_name = SmartPath(output_dir) / "catalog-v001.xml"
|
|
307
308
|
catalog = Element("catalog")
|
|
308
309
|
catalog.set("prefer", "public")
|
|
309
310
|
catalog.set("xmlns", "urn:oasis:names:tc:entity:xmlns:xml:catalog")
|
|
@@ -542,9 +543,9 @@ def _validate_export_command_input_parameters(
|
|
|
542
543
|
)
|
|
543
544
|
@click.option(
|
|
544
545
|
"--mime-type",
|
|
545
|
-
default="
|
|
546
|
+
default="text/turtle",
|
|
546
547
|
show_default=True,
|
|
547
|
-
type=click.Choice(["application/n-triples", "text/turtle"]),
|
|
548
|
+
type=click.Choice(["application/n-triples", "text/turtle", "application/rdf+xml"]),
|
|
548
549
|
help="Define the requested mime type",
|
|
549
550
|
)
|
|
550
551
|
@click.option(
|
|
@@ -589,19 +590,22 @@ def export_command( # noqa: PLR0913
|
|
|
589
590
|
app.echo_debug("output is directory")
|
|
590
591
|
# pre-calculate all filenames with the template,
|
|
591
592
|
# in order to output errors on naming clashes as early as possible
|
|
592
|
-
|
|
593
|
-
|
|
593
|
+
extension = mimetypes.guess_extension(mime_type)
|
|
594
|
+
_names = _get_export_names(
|
|
595
|
+
app, iris, template, f"{extension}.gz" if compress else f"{extension}"
|
|
596
|
+
)
|
|
597
|
+
_graph_file_names = _get_export_names(app, iris, template, f"{extension}.graph")
|
|
594
598
|
# create directory
|
|
595
|
-
if not
|
|
599
|
+
if not SmartPath(output_dir).exists():
|
|
596
600
|
app.echo_warning("Output directory does not exist: " + "will create it.")
|
|
597
|
-
|
|
601
|
+
SmartPath(output_dir).mkdir(parents=True)
|
|
598
602
|
# one .graph, one .ttl file per named graph
|
|
599
603
|
for current, iri in enumerate(iris, start=1):
|
|
600
604
|
# join with given output directory and normalize full path
|
|
601
|
-
triple_file_name = os.path.normpath(
|
|
602
|
-
graph_file_name = os.path.normpath(
|
|
605
|
+
triple_file_name = os.path.normpath(SmartPath(output_dir) / _names[iri])
|
|
606
|
+
graph_file_name = os.path.normpath(SmartPath(output_dir) / _graph_file_names[iri])
|
|
603
607
|
# output directory is created lazy
|
|
604
|
-
|
|
608
|
+
SmartPath(triple_file_name).parent.mkdir(parents=True, exist_ok=True)
|
|
605
609
|
# create and write the .ttl.graph metadata file
|
|
606
610
|
graph_file = click.open_file(graph_file_name, "w")
|
|
607
611
|
graph_file.write(iri + "\n")
|
|
@@ -661,6 +665,49 @@ def validate_input_path(input_path: str) -> None:
|
|
|
661
665
|
)
|
|
662
666
|
|
|
663
667
|
|
|
668
|
+
def _get_graph_supported_formats() -> dict[str, str]:
|
|
669
|
+
return {
|
|
670
|
+
"application/rdf+xml": "xml",
|
|
671
|
+
"application/ld+json": "jsonld",
|
|
672
|
+
"text/turtle": "turtle",
|
|
673
|
+
"application/n-triples": "nt",
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
|
|
677
|
+
def _get_buffer_and_content_type(
|
|
678
|
+
triple_file: str, app: ApplicationContext
|
|
679
|
+
) -> tuple[io.BytesIO, str]:
|
|
680
|
+
"""Get the io.BytesIO buffer and the content type of triple_file"""
|
|
681
|
+
smart_file = SmartPath(triple_file)
|
|
682
|
+
content_type, encoding = mimetypes.guess_type(triple_file)
|
|
683
|
+
if content_type is None:
|
|
684
|
+
content_type = "text/turtle"
|
|
685
|
+
for supported_type, supported_suffix in _get_graph_supported_formats().items():
|
|
686
|
+
if smart_file.name.endswith(f".{supported_suffix}") or smart_file.name.endswith(
|
|
687
|
+
f".{supported_suffix}.gz"
|
|
688
|
+
):
|
|
689
|
+
content_type = supported_type
|
|
690
|
+
elif content_type not in _get_graph_supported_formats():
|
|
691
|
+
app.echo_warning(
|
|
692
|
+
f"Content type {content_type} of {triple_file} is "
|
|
693
|
+
f"not one of {', '.join(_get_graph_supported_formats().keys())} "
|
|
694
|
+
"(but will try to import anyways)."
|
|
695
|
+
)
|
|
696
|
+
|
|
697
|
+
transport_params = {}
|
|
698
|
+
if smart_file.schema in ["http", "https"]:
|
|
699
|
+
transport_params["headers"] = {
|
|
700
|
+
"Accept": "text/turtle; q=1.0, application/x-turtle; q=0.9, text/n3;"
|
|
701
|
+
" q=0.8, application/rdf+xml; q=0.5, text/plain; q=0.1"
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
buffer = io.BytesIO()
|
|
705
|
+
with ClickSmartPath.open(triple_file, transport_params=transport_params) as file_obj:
|
|
706
|
+
buffer.write(file_obj.read())
|
|
707
|
+
buffer.seek(0)
|
|
708
|
+
return buffer, content_type
|
|
709
|
+
|
|
710
|
+
|
|
664
711
|
@click.command(cls=CmemcCommand, name="import")
|
|
665
712
|
@click.option(
|
|
666
713
|
"--replace",
|
|
@@ -688,7 +735,7 @@ def import_command(
|
|
|
688
735
|
input_path: str,
|
|
689
736
|
replace: bool,
|
|
690
737
|
skip_existing: bool,
|
|
691
|
-
iri:
|
|
738
|
+
iri: str,
|
|
692
739
|
) -> None:
|
|
693
740
|
"""Import graph(s) to the store.
|
|
694
741
|
|
|
@@ -707,14 +754,14 @@ def import_command(
|
|
|
707
754
|
|
|
708
755
|
Note: Directories are scanned on the first level only (not recursively).
|
|
709
756
|
"""
|
|
710
|
-
# is an array of tuples like this [('path/to/triple.file', 'graph IRI')]
|
|
711
757
|
if replace and skip_existing:
|
|
712
758
|
raise ValueError(
|
|
713
759
|
"The options --replace and --skip-existing are mutually "
|
|
714
760
|
"exclusive, so please remove one of them."
|
|
715
761
|
)
|
|
716
|
-
|
|
717
|
-
|
|
762
|
+
# is an array of tuples like this [('path/to/triple.file', 'graph IRI')]
|
|
763
|
+
graphs: list[tuple[str, str]]
|
|
764
|
+
if SmartPath(input_path).is_dir():
|
|
718
765
|
validate_input_path(input_path)
|
|
719
766
|
if iri is None:
|
|
720
767
|
# in case a directory is the source (and no IRI is given),
|
|
@@ -722,9 +769,15 @@ def import_command(
|
|
|
722
769
|
graphs = read_rdf_graph_files(input_path)
|
|
723
770
|
else:
|
|
724
771
|
# in case a directory is the source AND IRI is given
|
|
725
|
-
graphs = [
|
|
726
|
-
|
|
727
|
-
|
|
772
|
+
graphs = []
|
|
773
|
+
for _ in _get_graph_supported_formats():
|
|
774
|
+
extension = mimetypes.guess_extension(_)
|
|
775
|
+
graphs += [(str(file), iri) for file in SmartPath(input_path).glob(f"*{extension}")]
|
|
776
|
+
graphs += [
|
|
777
|
+
(str(file), iri) for file in SmartPath(input_path).glob(f"*{extension}.gz")
|
|
778
|
+
]
|
|
779
|
+
|
|
780
|
+
elif SmartPath(input_path).is_file():
|
|
728
781
|
if iri is None:
|
|
729
782
|
raise ValueError(
|
|
730
783
|
"Either specify an input file AND a graph IRI or an input directory ONLY."
|
|
@@ -748,17 +801,10 @@ def import_command(
|
|
|
748
801
|
continue
|
|
749
802
|
# prevents re-replacing of graphs in a single run
|
|
750
803
|
_replace = False if graph_iri in processed_graphs else replace
|
|
751
|
-
_buffer =
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
"Accept": "text/turtle; q=1.0, application/x-turtle; q=0.9, text/n3;"
|
|
756
|
-
" q=0.8, application/rdf+xml; q=0.5, text/plain; q=0.1"
|
|
757
|
-
}
|
|
758
|
-
with ClickSmartPath.open(triple_file, transport_params=transport_prams) as _:
|
|
759
|
-
_buffer.write(_.read())
|
|
760
|
-
_buffer.seek(0)
|
|
761
|
-
response = graph_api.post_streamed(graph_iri, _buffer, replace=_replace)
|
|
804
|
+
_buffer, content_type = _get_buffer_and_content_type(triple_file, app)
|
|
805
|
+
response = graph_api.post_streamed(
|
|
806
|
+
graph_iri, _buffer, replace=_replace, content_type=content_type
|
|
807
|
+
)
|
|
762
808
|
request_headers = response.request.headers
|
|
763
809
|
request_headers.pop("Authorization")
|
|
764
810
|
app.echo_debug(f"cmemc request headers: {request_headers}")
|
|
@@ -18,6 +18,7 @@ from cmem_cmemc.migrations.bootstrap_data import MigrateBootstrapData
|
|
|
18
18
|
from cmem_cmemc.migrations.shapes_widget_integrations_243 import (
|
|
19
19
|
ChartsOnNodeShapesToToWidgetIntegrations,
|
|
20
20
|
ChartsOnPropertyShapesToWidgetIntegrations,
|
|
21
|
+
TableReportPropertyShapesToWidgetIntegrations,
|
|
21
22
|
WorkflowTriggerPropertyShapesToWidgetIntegrations,
|
|
22
23
|
)
|
|
23
24
|
from cmem_cmemc.migrations.workspace_configurations import MigrateWorkspaceConfiguration
|
|
@@ -54,6 +55,7 @@ def get_migrations(ctx: click.Context) -> list[dict]: # noqa: ARG001
|
|
|
54
55
|
ChartsOnNodeShapesToToWidgetIntegrations(),
|
|
55
56
|
ChartsOnPropertyShapesToWidgetIntegrations(),
|
|
56
57
|
WorkflowTriggerPropertyShapesToWidgetIntegrations(),
|
|
58
|
+
TableReportPropertyShapesToWidgetIntegrations(),
|
|
57
59
|
]
|
|
58
60
|
]
|
|
59
61
|
data.sort(key=lambda x: x["first_version"])
|
|
@@ -181,11 +181,11 @@ def list_command(app: ApplicationContext, raw: bool, id_only: bool, available: b
|
|
|
181
181
|
return
|
|
182
182
|
|
|
183
183
|
table_published = [
|
|
184
|
-
(_.name, str(_.published)[:10], _.description) for _ in published_packages
|
|
184
|
+
(_.name, _.version, str(_.published)[:10], _.description) for _ in published_packages
|
|
185
185
|
]
|
|
186
186
|
app.echo_info_table(
|
|
187
187
|
table_published,
|
|
188
|
-
headers=["Name", "Published", "Description"],
|
|
188
|
+
headers=["Name", "Version", "Published", "Description"],
|
|
189
189
|
sort_column=0,
|
|
190
190
|
empty_table_message="No available python packages found.",
|
|
191
191
|
)
|
|
@@ -460,7 +460,7 @@ def installed_package_names(ctx: Context, param: Argument, incomplete: str) -> l
|
|
|
460
460
|
|
|
461
461
|
def published_package_names(ctx: Context, param: Argument, incomplete: str) -> list[CompletionItem]:
|
|
462
462
|
"""List of plugin packages scraped from pypi.org."""
|
|
463
|
-
options = [(_.name, f"{_.description}") for _ in get_published_packages()]
|
|
463
|
+
options = [(_.name, f"{_.version}: {_.description}") for _ in get_published_packages()]
|
|
464
464
|
return finalize_completion(candidates=options, incomplete=incomplete, sort_by=SORT_BY_KEY)
|
|
465
465
|
|
|
466
466
|
|
|
@@ -600,13 +600,19 @@ def sparql_files(ctx: Context, param: Argument, incomplete: str) -> list[Complet
|
|
|
600
600
|
def triple_files(ctx: Context, param: Argument, incomplete: str) -> list[CompletionItem]:
|
|
601
601
|
"""Prepare a list of triple files."""
|
|
602
602
|
return (
|
|
603
|
-
file_list(incomplete=incomplete, suffix=".ttl", description="
|
|
604
|
-
+ file_list(incomplete=incomplete, suffix=".nt", description="
|
|
603
|
+
file_list(incomplete=incomplete, suffix=".ttl", description="Turtle file")
|
|
604
|
+
+ file_list(incomplete=incomplete, suffix=".nt", description="NTriples file")
|
|
605
|
+
+ file_list(incomplete=incomplete, suffix=".rdf", description="RDF/XML file")
|
|
606
|
+
+ file_list(incomplete=incomplete, suffix=".jsonld", description="JSON-LD file")
|
|
607
|
+
+ file_list(incomplete=incomplete, suffix=".ttl.gz", description="Turtle file (compressed)")
|
|
605
608
|
+ file_list(
|
|
606
|
-
incomplete=incomplete, suffix=".
|
|
609
|
+
incomplete=incomplete, suffix=".nt.gz", description="NTriples file (compressed)"
|
|
607
610
|
)
|
|
608
611
|
+ file_list(
|
|
609
|
-
incomplete=incomplete, suffix=".
|
|
612
|
+
incomplete=incomplete, suffix=".rdf.gz", description="RDF/XML file (compressed)"
|
|
613
|
+
)
|
|
614
|
+
+ file_list(
|
|
615
|
+
incomplete=incomplete, suffix=".jsonld.gz", description="JSON-LD file (compressed)"
|
|
610
616
|
)
|
|
611
617
|
)
|
|
612
618
|
|
|
@@ -27,9 +27,9 @@ from urllib3.exceptions import InsecureRequestWarning
|
|
|
27
27
|
from cmem_cmemc.exceptions import InvalidConfigurationError
|
|
28
28
|
from cmem_cmemc.string_processor import StringProcessor, process_row
|
|
29
29
|
|
|
30
|
-
DI_TARGET_VERSION = "v24.
|
|
30
|
+
DI_TARGET_VERSION = "v24.3.0"
|
|
31
31
|
|
|
32
|
-
EXPLORE_TARGET_VERSION = "v24.
|
|
32
|
+
EXPLORE_TARGET_VERSION = "v24.3.0"
|
|
33
33
|
|
|
34
34
|
KNOWN_CONFIG_KEYS = {
|
|
35
35
|
"CMEM_BASE_URI": cmempy_config.get_cmem_base_uri,
|
|
@@ -20,6 +20,7 @@ SELECT ?subject
|
|
|
20
20
|
FROM <urn:elds-backend-access-conditions-graph>
|
|
21
21
|
WHERE {
|
|
22
22
|
?subject a ?class .
|
|
23
|
+
FILTER NOT EXISTS { ?subject shui:isSystemResource true }
|
|
23
24
|
FILTER (?class IN (auth:AccessCondition, shui:SparqlQuery) ) .
|
|
24
25
|
}
|
|
25
26
|
"""
|
|
@@ -31,6 +32,7 @@ WHERE {
|
|
|
31
32
|
?s ?p ?o .
|
|
32
33
|
?s a ?class .
|
|
33
34
|
FILTER (?class IN (auth:AccessCondition, shui:SparqlQuery) ) .
|
|
35
|
+
FILTER NOT EXISTS { ?s shui:isSystemResource true }
|
|
34
36
|
}
|
|
35
37
|
}
|
|
36
38
|
"""
|
|
@@ -59,6 +61,7 @@ SELECT DISTINCT ?acl_id
|
|
|
59
61
|
WHERE {
|
|
60
62
|
GRAPH ?acl_graph {
|
|
61
63
|
?acl_id a auth:AccessCondition .
|
|
64
|
+
FILTER NOT EXISTS { ?acl_id shui:isSystemResource true }
|
|
62
65
|
?acl_id ?property ?old_term .
|
|
63
66
|
FILTER (?old_term in (
|
|
64
67
|
<urn:elds-backend-all-actions>,
|
|
@@ -85,6 +88,7 @@ INSERT { GRAPH ?acl_graph { ?s ?p <{{NEW_IRI}}> . } }
|
|
|
85
88
|
WHERE {
|
|
86
89
|
GRAPH ?acl_graph {
|
|
87
90
|
?s ?p ?o .
|
|
91
|
+
FILTER NOT EXISTS { ?s shui:isSystemResource true }
|
|
88
92
|
FILTER (?o = <{{OLD_IRI}}> )
|
|
89
93
|
}
|
|
90
94
|
FILTER (?acl_graph IN (<urn:elds-backend-access-conditions-graph>, <https://ns.eccenca.com/data/ac/>))
|
|
@@ -20,10 +20,8 @@ class MigrateBootstrapData(MigrationRecipe):
|
|
|
20
20
|
def is_applicable(self) -> bool:
|
|
21
21
|
"""Test if the recipe can be applied."""
|
|
22
22
|
status_info = get_complete_status_info()
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
"UNKNOWN",
|
|
26
|
-
)
|
|
23
|
+
shapes_status = status_info["shapes"]["version"]
|
|
24
|
+
return bool(shapes_status != status_info["explore"]["version"])
|
|
27
25
|
|
|
28
26
|
def apply(self) -> None:
|
|
29
27
|
"""Apply the recipe to the current version."""
|
{cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.2}/cmem_cmemc/migrations/shapes_widget_integrations_243.py
RENAMED
|
@@ -192,3 +192,83 @@ WHERE {
|
|
|
192
192
|
def apply(self) -> None:
|
|
193
193
|
"""Apply the recipe to the current version."""
|
|
194
194
|
self._update(self.move_query)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
class TableReportPropertyShapesToWidgetIntegrations(MigrationRecipe):
|
|
198
|
+
"""24.3 Migrate Table Report Property Shapes to Widget Integrations"""
|
|
199
|
+
|
|
200
|
+
id = "table-reports-on-pshapes-24.3"
|
|
201
|
+
description = (
|
|
202
|
+
"Migrate Table Reports (which use shui:null) on Property Shapes to Widget Integrations"
|
|
203
|
+
)
|
|
204
|
+
component: components = "explore"
|
|
205
|
+
first_version = "24.3"
|
|
206
|
+
tags: ClassVar[list[str]] = ["shapes", "user"]
|
|
207
|
+
check_query = """{{DEFAULT_PREFIXES}}
|
|
208
|
+
SELECT ?propertyShape
|
|
209
|
+
WHERE {
|
|
210
|
+
GRAPH ?shapeCatalog {
|
|
211
|
+
?shapeCatalog a shui:ShapeCatalog .
|
|
212
|
+
?propertyShape a sh:PropertyShape ;
|
|
213
|
+
shui:valueQuery ?valueQuery ;
|
|
214
|
+
sh:path shui:null .
|
|
215
|
+
FILTER NOT EXISTS { ?propertyShape shui:isSystemResource true }
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
"""
|
|
219
|
+
move_query = """{{DEFAULT_PREFIXES}}
|
|
220
|
+
DELETE {
|
|
221
|
+
GRAPH ?shapeCatalog {
|
|
222
|
+
?propertyShape ?p ?o .
|
|
223
|
+
?nodeShape sh:property ?propertyShape .
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
INSERT {
|
|
227
|
+
GRAPH ?shapeCatalog {
|
|
228
|
+
?propertyShape a shui:WidgetIntegration ;
|
|
229
|
+
shui:WidgetIntegration_widget ?newTableReport ;
|
|
230
|
+
rdfs:label ?name ;
|
|
231
|
+
rdfs:comment ?description ;
|
|
232
|
+
sh:order ?order ;
|
|
233
|
+
shui:WidgetIntegration_group ?group .
|
|
234
|
+
?nodeShape shui:WidgetIntegration_integrate ?propertyShape .
|
|
235
|
+
|
|
236
|
+
?newTableReport a shui:TableReport ;
|
|
237
|
+
rdfs:label ?newTableReportLabel ;
|
|
238
|
+
shui:TableReport_hideFooter ?hideFooter ;
|
|
239
|
+
shui:TableReport_hideHeader ?hideHeader ;
|
|
240
|
+
shui:TableReport_query ?valueQuery .
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
WHERE {
|
|
244
|
+
GRAPH ?shapeCatalog {
|
|
245
|
+
?shapeCatalog a shui:ShapeCatalog .
|
|
246
|
+
?propertyShape a sh:PropertyShape ;
|
|
247
|
+
sh:path shui:null ;
|
|
248
|
+
shui:valueQuery ?valueQuery .
|
|
249
|
+
FILTER NOT EXISTS { ?propertyShape shui:isSystemResource true }
|
|
250
|
+
?propertyShape ?p ?o .
|
|
251
|
+
OPTIONAL {?propertyShape sh:name ?name }
|
|
252
|
+
OPTIONAL {?propertyShape sh:description ?description }
|
|
253
|
+
OPTIONAL {?propertyShape sh:order ?order }
|
|
254
|
+
OPTIONAL {?propertyShape sh:group ?group }
|
|
255
|
+
OPTIONAL {?propertyShape sh:name ?name }
|
|
256
|
+
OPTIONAL {?propertyShape shui:valueQueryHideHeader ?hideHeaderValue }
|
|
257
|
+
OPTIONAL {?propertyShape shui:valueQueryHideFooter ?hideFooterValue }
|
|
258
|
+
OPTIONAL {?nodeShape sh:property ?propertyShape }
|
|
259
|
+
BIND ( IRI(CONCAT(STR(?propertyShape), "-TableReport")) AS ?newTableReport )
|
|
260
|
+
BIND ( STRLANG(CONCAT("Table Report: ", ?name), lang(?name)) AS ?newTableReportLabel )
|
|
261
|
+
BIND ( COALESCE(?hideHeaderValue, false) as ?hideHeader)
|
|
262
|
+
BIND ( COALESCE(?hideFooterValue, false) as ?hideFooter)
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
"""
|
|
266
|
+
|
|
267
|
+
def is_applicable(self) -> bool:
|
|
268
|
+
"""Test if the recipe can be applied."""
|
|
269
|
+
table_report_property_shapes = self._select(self.check_query)
|
|
270
|
+
return len(table_report_property_shapes) > 0
|
|
271
|
+
|
|
272
|
+
def apply(self) -> None:
|
|
273
|
+
"""Apply the recipe to the current version."""
|
|
274
|
+
self._update(self.move_query)
|
|
@@ -24,7 +24,13 @@ class TimeAgo(StringProcessor):
|
|
|
24
24
|
|
|
25
25
|
def process(self, text: str) -> str:
|
|
26
26
|
"""Process a single string content and output the processed string."""
|
|
27
|
-
|
|
27
|
+
if text is None:
|
|
28
|
+
return ""
|
|
29
|
+
try:
|
|
30
|
+
text_as_int = int(text)
|
|
31
|
+
except ValueError:
|
|
32
|
+
return text
|
|
33
|
+
stamp = datetime.fromtimestamp(text_as_int / 1000, tz=timezone.utc)
|
|
28
34
|
return str(timeago.format(stamp, datetime.now(tz=timezone.utc)))
|
|
29
35
|
|
|
30
36
|
|
|
@@ -12,7 +12,6 @@ from typing import TYPE_CHECKING
|
|
|
12
12
|
from zipfile import BadZipFile, ZipFile
|
|
13
13
|
|
|
14
14
|
import requests
|
|
15
|
-
from bs4 import BeautifulSoup
|
|
16
15
|
from cmem.cmempy.dp.proxy.graph import get_graphs_list
|
|
17
16
|
from cmem.cmempy.queries import QueryCatalog
|
|
18
17
|
from cmem.cmempy.workspace.projects.project import get_projects
|
|
@@ -22,7 +21,7 @@ from cmem_cmemc.constants import NAMESPACES
|
|
|
22
21
|
|
|
23
22
|
if TYPE_CHECKING:
|
|
24
23
|
from cmem_cmemc.context import ApplicationContext
|
|
25
|
-
from cmem_cmemc.smart_path import SmartPath
|
|
24
|
+
from cmem_cmemc.smart_path import SmartPath
|
|
26
25
|
|
|
27
26
|
|
|
28
27
|
def get_version() -> str:
|
|
@@ -91,13 +90,13 @@ def read_rdf_graph_files(directory_path: str) -> list[tuple[str, str]]:
|
|
|
91
90
|
for _file in files:
|
|
92
91
|
if _file.endswith(".graph"):
|
|
93
92
|
continue
|
|
94
|
-
full_file_path =
|
|
93
|
+
full_file_path = SmartPath(root) / _file
|
|
95
94
|
# Handle compressed files (like .gz)
|
|
96
95
|
if _file.endswith(".gz"):
|
|
97
96
|
graph_file_name = _file.replace(".gz", ".graph")
|
|
98
97
|
else:
|
|
99
98
|
graph_file_name = f"{_file}.graph"
|
|
100
|
-
full_graph_file_name_path =
|
|
99
|
+
full_graph_file_name_path = SmartPath(root) / graph_file_name
|
|
101
100
|
if full_graph_file_name_path.exists():
|
|
102
101
|
graph_name = read_file_to_string(str(full_graph_file_name_path)).strip()
|
|
103
102
|
rdf_graphs.append((str(full_file_path.resolve()), graph_name))
|
|
@@ -106,7 +105,7 @@ def read_rdf_graph_files(directory_path: str) -> list[tuple[str, str]]:
|
|
|
106
105
|
|
|
107
106
|
def read_file_to_string(file_path: str) -> str:
|
|
108
107
|
"""Read file to string."""
|
|
109
|
-
with
|
|
108
|
+
with SmartPath(file_path).open(mode="rb") as _file:
|
|
110
109
|
return str(_file.read().decode("utf-8"))
|
|
111
110
|
|
|
112
111
|
|
|
@@ -288,41 +287,30 @@ class PublishedPackage:
|
|
|
288
287
|
name: str
|
|
289
288
|
description: str
|
|
290
289
|
published: str
|
|
290
|
+
version: str
|
|
291
291
|
|
|
292
292
|
|
|
293
293
|
def get_published_packages() -> list[PublishedPackage]:
|
|
294
294
|
"""Get a scraped list of plugin packages scraped from pypi.org."""
|
|
295
|
-
|
|
296
|
-
packages = []
|
|
295
|
+
url = "https://download.eccenca.com/cmem-plugin-index/cmem-plugin-index.json"
|
|
297
296
|
package_names = []
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
description = _.findChildren(class_="package-snippet__description")[0].getText()
|
|
314
|
-
except IndexError:
|
|
315
|
-
description = "no description"
|
|
316
|
-
published = _.findChildren(name="time")[0].attrs["datetime"]
|
|
317
|
-
if name not in package_names:
|
|
318
|
-
package_names.append(name)
|
|
319
|
-
packages.append(
|
|
320
|
-
PublishedPackage(
|
|
321
|
-
name=name,
|
|
322
|
-
description=description,
|
|
323
|
-
published=published,
|
|
324
|
-
)
|
|
297
|
+
packages = []
|
|
298
|
+
for _ in requests.get(url, timeout=5).json():
|
|
299
|
+
name = _["name"]
|
|
300
|
+
if name == "cmem-plugin-base":
|
|
301
|
+
continue
|
|
302
|
+
if not name.startswith("cmem-plugin-"):
|
|
303
|
+
continue
|
|
304
|
+
if name not in package_names:
|
|
305
|
+
package_names.append(name)
|
|
306
|
+
packages.append(
|
|
307
|
+
PublishedPackage(
|
|
308
|
+
name=name,
|
|
309
|
+
description=_["summary"],
|
|
310
|
+
published=_["latest_version_time"],
|
|
311
|
+
version=_["latest_version"],
|
|
325
312
|
)
|
|
313
|
+
)
|
|
326
314
|
return packages
|
|
327
315
|
|
|
328
316
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "cmem-cmemc"
|
|
3
|
-
version = "24.3.
|
|
3
|
+
version = "24.3.2"
|
|
4
4
|
description = "Command line client for eccenca Corporate Memory"
|
|
5
5
|
license = "Apache-2.0"
|
|
6
6
|
classifiers = [
|
|
@@ -39,7 +39,6 @@ configparser = "^6.0.1"
|
|
|
39
39
|
jinja2 = "^3.1.4"
|
|
40
40
|
junit-xml = "^1.9"
|
|
41
41
|
natsort = "^8.4.0"
|
|
42
|
-
pip = "^24.3.1"
|
|
43
42
|
prometheus-client = "^0.21.0"
|
|
44
43
|
pygments = "^2.18.0"
|
|
45
44
|
pyjwt = "^2.9.0"
|
|
@@ -59,6 +58,7 @@ cmemc = 'cmem_cmemc:main'
|
|
|
59
58
|
[tool.poetry.group.dev.dependencies]
|
|
60
59
|
genbadge = {extras = ["coverage"], version = "^1.1.1"}
|
|
61
60
|
mypy = "^1.13.0"
|
|
61
|
+
pip = "^25"
|
|
62
62
|
pytest = "^8.3.3"
|
|
63
63
|
pytest-cov = "^6.0.0"
|
|
64
64
|
pytest-dotenv = "^0.5.2"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.2}/cmem_cmemc/migrations/workspace_configurations.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|