cmem-cmemc 24.3.0rc1__py3-none-any.whl → 24.3.2__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/__init__.py CHANGED
@@ -95,7 +95,7 @@ def cli(
95
95
 
96
96
  https://eccenca.com/go/cmemc
97
97
 
98
- cmemc is © 2024 eccenca GmbH, licensed under the Apache License 2.0.
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 as Path
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 Path(file_path).exists():
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)}.{file_extension}"
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 = Path(output_dir) / "catalog-v001.xml"
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="application/n-triples",
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
- _names = _get_export_names(app, iris, template, "ttl.gz" if compress else "ttl")
593
- _graph_file_names = _get_export_names(app, iris, template, "ttl.graph")
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 Path(output_dir).exists():
599
+ if not SmartPath(output_dir).exists():
596
600
  app.echo_warning("Output directory does not exist: " + "will create it.")
597
- Path(output_dir).mkdir(parents=True)
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(Path(output_dir) / _names[iri])
602
- graph_file_name = os.path.normpath(Path(output_dir) / _graph_file_names[iri])
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
- Path(triple_file_name).parent.mkdir(parents=True, exist_ok=True)
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: tuple[str, ...],
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
- graphs: list
717
- if Path(input_path).is_dir():
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 = [(file, iri) for file in Path(input_path).glob("*.ttl")]
726
- graphs += [(file, iri) for file in Path(input_path).glob("*.ttl.gz")]
727
- elif Path(input_path).is_file():
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 = io.BytesIO()
752
- transport_prams = {}
753
- if Path(str(triple_file)).schema in ["http", "https"]:
754
- transport_prams["headers"] = {
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
  )
cmem_cmemc/completion.py CHANGED
@@ -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="RDF Turtle file")
604
- + file_list(incomplete=incomplete, suffix=".nt", description="RDF NTriples file")
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=".ttl.gz", description="Compressed RDF Turtle file"
609
+ incomplete=incomplete, suffix=".nt.gz", description="NTriples file (compressed)"
607
610
  )
608
611
  + file_list(
609
- incomplete=incomplete, suffix=".nt.gz", description="Compressed RDF NTriples file"
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
 
cmem_cmemc/context.py CHANGED
@@ -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.2.0"
30
+ DI_TARGET_VERSION = "v24.3.0"
31
31
 
32
- EXPLORE_TARGET_VERSION = "v24.2.0"
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
- return status_info["shapes"]["version"] not in (
24
- status_info["explore"]["version"],
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."""
@@ -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
- stamp = datetime.fromtimestamp(int(text) / 1000, tz=timezone.utc)
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
 
cmem_cmemc/utils.py CHANGED
@@ -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 as Path
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 = Path(root) / _file
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 = Path(root) / graph_file_name
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 Path(file_path).open(mode="rb") as _file:
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
- url_pattern = "https://pypi.org/search/?q=%22cmem-plugin-%22&page={}"
296
- packages = []
295
+ url = "https://download.eccenca.com/cmem-plugin-index/cmem-plugin-index.json"
297
296
  package_names = []
298
- page = 0
299
- while True:
300
- page += 1
301
- url = url_pattern.format(page)
302
- soup = BeautifulSoup(requests.get(url, timeout=5).content, "html.parser")
303
- snippets = soup.find_all("a", class_="package-snippet")
304
- if len(snippets) == 0:
305
- break
306
- for _ in snippets:
307
- name = _.findChildren(class_="package-snippet__name")[0].getText()
308
- if name == "cmem-plugin-base":
309
- continue
310
- if not name.startswith("cmem-plugin-"):
311
- continue
312
- try:
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,8 +1,7 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: cmem-cmemc
3
- Version: 24.3.0rc1
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
@@ -1,4 +1,4 @@
1
- cmem_cmemc/__init__.py,sha256=JP__el3oR8pNwDH4_D3mu6jE6ChdeBDnIgu5Vf_VHUg,5516
1
+ cmem_cmemc/__init__.py,sha256=JI0MMWttUaMATsR_9Sw2Y5I6PX3NCxogVX73Hjo6g7M,5516
2
2
  cmem_cmemc/_cmemc.zsh,sha256=fmkrBHIQxus8cp2AgO1tzZ5mNZdGL_83cYz3a9uAdsg,1326
3
3
  cmem_cmemc/command.py,sha256=OsrQOqHpMjiucdpyac8xzxBaSWehYIA7xkO651z8iTs,702
4
4
  cmem_cmemc/command_group.py,sha256=0ltd8xY0yN6_ARR2kkwx7Hibj4glT9M0_bnjKlMhz8g,3371
@@ -8,11 +8,11 @@ cmem_cmemc/commands/admin.py,sha256=IrhWPNJxdE5GO9UumsI8lv9PqFc_0oUh72FmQIKdl9c,
8
8
  cmem_cmemc/commands/client.py,sha256=gKh22OvYDi95q2U4czH4UPYyDRw_Ngk2K0frDywl3B4,5074
9
9
  cmem_cmemc/commands/config.py,sha256=j0zc9EFWW7Kw9bzSb_r16m-A0LZersKbZ9j5GE0mGcQ,5734
10
10
  cmem_cmemc/commands/dataset.py,sha256=xsIUiC2aMDiESjnZvjDY5Mh2fvSooOijQmo5A7RvY40,30397
11
- cmem_cmemc/commands/graph.py,sha256=lYu0M3HKCeDhrv_cswDbyzYKqy5_9OPU548zbJZVQ1o,30743
11
+ cmem_cmemc/commands/graph.py,sha256=OHBMHLU5PNrbnxinvjE5Z7FQv8bU8WW-0RCfdoovXl0,32412
12
12
  cmem_cmemc/commands/metrics.py,sha256=zsyHezoYwig6jIdn7g9NZMqt9DxG6dQRoO0vP3Eu0Rc,12176
13
- cmem_cmemc/commands/migration.py,sha256=KXxKnk4MxzUtc7iSiLipOJKCX4bkiUABXdA-PQPN6r8,9300
13
+ cmem_cmemc/commands/migration.py,sha256=IubT8uA-cyn5frofUcknMWEBLMPeEoKMb0xNbJRXNn4,9412
14
14
  cmem_cmemc/commands/project.py,sha256=1BwAAZvupImqkW3Q3dXdkMir8-wnWRMsewnOyVui3QY,20545
15
- cmem_cmemc/commands/python.py,sha256=nIxBgiznR7tarrw7JFE1XkGrVlFxQQQmxj2lyExxhtI,10738
15
+ cmem_cmemc/commands/python.py,sha256=80k-z6AcL1Ibp5CqVHATjvK0t9wv24QsepDB0vrfDFU,10760
16
16
  cmem_cmemc/commands/query.py,sha256=KUck4w7PD0e5JYg5Eykf2QAMBfpwmgskEiIA-X_PqZs,26973
17
17
  cmem_cmemc/commands/resource.py,sha256=ZC_0PM0YnEB3Xf4i0siaTKxHT0Ixjz2Y3QTXuVPuhRE,7739
18
18
  cmem_cmemc/commands/scheduler.py,sha256=yKVGcewhrXFfSKEOlvEFwaIfWCOK1x7VLhm-i-SSw68,8729
@@ -23,9 +23,9 @@ cmem_cmemc/commands/variable.py,sha256=DSNxiS5PlexzNdYz9f0-O0hxqakbsxv847U7I9UhK
23
23
  cmem_cmemc/commands/vocabulary.py,sha256=pAxRM-Y9lzmZ0dT3NBpBGKa2nlbY0thWBdfN2FESXQ8,17767
24
24
  cmem_cmemc/commands/workflow.py,sha256=vQiaH3L1ZXjf5SuorwdjrD6nF_5nxZ9Xkd7eCNGwxk8,25512
25
25
  cmem_cmemc/commands/workspace.py,sha256=IcZgBsvtulLRFofS70qpln6oKQIZunrVLfSAUeiFhCA,4579
26
- cmem_cmemc/completion.py,sha256=gI64uJS1pMZS_iR8VgOldF4sNMTeV0AV51bF7lF2dlo,43925
26
+ cmem_cmemc/completion.py,sha256=zgT0ihoePxl8RdmH-9wn1LBAYbeD3Scj9-nX0FcFBTY,44330
27
27
  cmem_cmemc/constants.py,sha256=VKNF5-6fzZXWzPgm1OAGhWvyL4YE8SRq52kkpJjUgB0,475
28
- cmem_cmemc/context.py,sha256=fy2gwoW-15hl-yCCY9f0t59j4Sp2Mj2GNatZ2G-mrxA,19482
28
+ cmem_cmemc/context.py,sha256=JJUCfhLT9xKQScSXL1a7US9YE6c1fpFKLLjE7kxCieM,19482
29
29
  cmem_cmemc/exceptions.py,sha256=SpUHdmVM8cZpSjBv6ICgr9NLr3OJ5XO42DlvjohprVo,232
30
30
  cmem_cmemc/manual_helper/__init__.py,sha256=G3Lqw2aPxo8x63Tg7L0aa5VD9BMaRzZDmhrog7IuEPg,43
31
31
  cmem_cmemc/manual_helper/graph.py,sha256=qDchHdjRRDW2oZ66by81NxhoDgNxXaAUxq2keewEiVU,3598
@@ -33,9 +33,9 @@ cmem_cmemc/manual_helper/multi_page.py,sha256=5nvN1P7zTgzrnuoT7yA7abyT7EOVa24Jvp
33
33
  cmem_cmemc/manual_helper/single_page.py,sha256=sVSeaZmPa-Cs6dtp27MqyiO6rIrskY9BtDyeAZhBWXM,1477
34
34
  cmem_cmemc/migrations/__init__.py,sha256=i6Ri7qN58ou_MwOzm2KibPkXOD7u-1ELky-nUE5LjAA,24
35
35
  cmem_cmemc/migrations/abc.py,sha256=Q4G0tRiFruawm6qvvSC_02FkDTC-Jav4VP2Suoy5hzE,3512
36
- cmem_cmemc/migrations/access_conditions_243.py,sha256=kqSEDZkcj4FQcE_BkYA0MN8sAg8EjeDCEa18DoErVcc,4850
37
- cmem_cmemc/migrations/bootstrap_data.py,sha256=4BLzwRAG5Jc9X1hzXvS1vq2_4dZ2IcKLIVHthgue8hE,971
38
- cmem_cmemc/migrations/shapes_widget_integrations_243.py,sha256=hgNImornRV9dMJWyEWd0IsTQbHtqXTpT0MWQSXuAzDk,5941
36
+ cmem_cmemc/migrations/access_conditions_243.py,sha256=IXcvSuo9pLaTTo4XNBB6_ln-2TzOV5PU5ugti0BWbxA,5083
37
+ cmem_cmemc/migrations/bootstrap_data.py,sha256=RF0vyFTGUQ_RcpTTWZmm3XLAJAJX2gSYcGwcBmRmU8A,963
38
+ cmem_cmemc/migrations/shapes_widget_integrations_243.py,sha256=jRWtTFeMom-xUI7UaoTa3DoLmmdwFmaw9XYxvnelxMo,8746
39
39
  cmem_cmemc/migrations/workspace_configurations.py,sha256=tFmCdfEL10ICjqMXQEIf-9fveE41HBQ_jaWNQJENz50,998
40
40
  cmem_cmemc/object_list.py,sha256=NYArisZxCV4pws_Tgk_xyltLN6TStkxQAgy-WLlzOxc,14712
41
41
  cmem_cmemc/parameter_types/__init__.py,sha256=Jqhwnw5a2oPNMClzUyovWiieK60RCl3rvSNr-t3wP84,36
@@ -43,11 +43,11 @@ cmem_cmemc/parameter_types/path.py,sha256=hrisrXA1V8AtlWv-zxMYFGyf3RBCet4CW-Z2yT
43
43
  cmem_cmemc/smart_path/__init__.py,sha256=zDgm1kDrzLyCuIcNb8VXSdnb_CcVNjGkjgiIDVlsh74,3023
44
44
  cmem_cmemc/smart_path/clients/__init__.py,sha256=YFOm69BfTCRvAcJjN_CoUmCv3kzEciyYOPUG337p_pA,1696
45
45
  cmem_cmemc/smart_path/clients/http.py,sha256=3clZu2v4uuOvPY4MY_8SVSy7hIXJDNooahFRBRpy0ok,2347
46
- cmem_cmemc/string_processor.py,sha256=266Mk9a2zTNzndlyOAsz0mYgpHkYvz9KC0NU-tcIN30,2730
46
+ cmem_cmemc/string_processor.py,sha256=kSVePdgFmf2ekurKj6TbDJn6ur82VGLwCsTJ9ODfBEU,2879
47
47
  cmem_cmemc/title_helper.py,sha256=7frjAR54_Xc1gszOWXfzSmKFTawNJQ7kkXhZcHmQLyw,1250
48
- cmem_cmemc/utils.py,sha256=svkyYTq41SI7BCSaeyfJCe_O3gAdsKGw0IrqQYiLhrc,12782
49
- cmem_cmemc-24.3.0rc1.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
50
- cmem_cmemc-24.3.0rc1.dist-info/METADATA,sha256=sd9s-V-2tMI0Obi5v_ceKstENrCdr-cjpXWz4hw5zb0,5619
51
- cmem_cmemc-24.3.0rc1.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
52
- cmem_cmemc-24.3.0rc1.dist-info/entry_points.txt,sha256=znWUTG-zgDITu6Frsd-OtNxBxj6Uo8Fa7bz6gaZYMrA,41
53
- cmem_cmemc-24.3.0rc1.dist-info/RECORD,,
48
+ cmem_cmemc/utils.py,sha256=DXcJo6F2rYP1xeaI3wSvUVlS6GXbGTDJZLci7T5Rgd4,12220
49
+ cmem_cmemc-24.3.2.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
50
+ cmem_cmemc-24.3.2.dist-info/METADATA,sha256=6_dKhAqyFK1a62Ju8ZyLDfMAXENC-ARAo05Oc2SK5fc,5641
51
+ cmem_cmemc-24.3.2.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
52
+ cmem_cmemc-24.3.2.dist-info/entry_points.txt,sha256=znWUTG-zgDITu6Frsd-OtNxBxj6Uo8Fa7bz6gaZYMrA,41
53
+ cmem_cmemc-24.3.2.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 1.9.0
2
+ Generator: poetry-core 2.0.1
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any