cmem-cmemc 24.3.0rc1__tar.gz → 24.3.0rc2__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.
Files changed (52) hide show
  1. {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.0rc2}/PKG-INFO +1 -1
  2. {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.0rc2}/cmem_cmemc/commands/graph.py +76 -19
  3. {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.0rc2}/cmem_cmemc/commands/migration.py +2 -0
  4. {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.0rc2}/cmem_cmemc/commands/python.py +2 -2
  5. {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.0rc2}/cmem_cmemc/completion.py +11 -5
  6. {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.0rc2}/cmem_cmemc/migrations/access_conditions_243.py +4 -0
  7. {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.0rc2}/cmem_cmemc/migrations/bootstrap_data.py +2 -4
  8. {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.0rc2}/cmem_cmemc/migrations/shapes_widget_integrations_243.py +80 -0
  9. {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.0rc2}/cmem_cmemc/string_processor.py +7 -1
  10. {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.0rc2}/cmem_cmemc/utils.py +18 -30
  11. {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.0rc2}/pyproject.toml +1 -1
  12. {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.0rc2}/LICENSE +0 -0
  13. {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.0rc2}/README-public.md +0 -0
  14. {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.0rc2}/cmem_cmemc/__init__.py +0 -0
  15. {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.0rc2}/cmem_cmemc/_cmemc.zsh +0 -0
  16. {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.0rc2}/cmem_cmemc/command.py +0 -0
  17. {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.0rc2}/cmem_cmemc/command_group.py +0 -0
  18. {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.0rc2}/cmem_cmemc/commands/__init__.py +0 -0
  19. {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.0rc2}/cmem_cmemc/commands/acl.py +0 -0
  20. {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.0rc2}/cmem_cmemc/commands/admin.py +0 -0
  21. {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.0rc2}/cmem_cmemc/commands/client.py +0 -0
  22. {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.0rc2}/cmem_cmemc/commands/config.py +0 -0
  23. {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.0rc2}/cmem_cmemc/commands/dataset.py +0 -0
  24. {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.0rc2}/cmem_cmemc/commands/metrics.py +0 -0
  25. {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.0rc2}/cmem_cmemc/commands/project.py +0 -0
  26. {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.0rc2}/cmem_cmemc/commands/query.py +0 -0
  27. {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.0rc2}/cmem_cmemc/commands/resource.py +0 -0
  28. {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.0rc2}/cmem_cmemc/commands/scheduler.py +0 -0
  29. {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.0rc2}/cmem_cmemc/commands/store.py +0 -0
  30. {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.0rc2}/cmem_cmemc/commands/user.py +0 -0
  31. {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.0rc2}/cmem_cmemc/commands/validation.py +0 -0
  32. {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.0rc2}/cmem_cmemc/commands/variable.py +0 -0
  33. {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.0rc2}/cmem_cmemc/commands/vocabulary.py +0 -0
  34. {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.0rc2}/cmem_cmemc/commands/workflow.py +0 -0
  35. {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.0rc2}/cmem_cmemc/commands/workspace.py +0 -0
  36. {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.0rc2}/cmem_cmemc/constants.py +0 -0
  37. {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.0rc2}/cmem_cmemc/context.py +0 -0
  38. {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.0rc2}/cmem_cmemc/exceptions.py +0 -0
  39. {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.0rc2}/cmem_cmemc/manual_helper/__init__.py +0 -0
  40. {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.0rc2}/cmem_cmemc/manual_helper/graph.py +0 -0
  41. {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.0rc2}/cmem_cmemc/manual_helper/multi_page.py +0 -0
  42. {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.0rc2}/cmem_cmemc/manual_helper/single_page.py +0 -0
  43. {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.0rc2}/cmem_cmemc/migrations/__init__.py +0 -0
  44. {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.0rc2}/cmem_cmemc/migrations/abc.py +0 -0
  45. {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.0rc2}/cmem_cmemc/migrations/workspace_configurations.py +0 -0
  46. {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.0rc2}/cmem_cmemc/object_list.py +0 -0
  47. {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.0rc2}/cmem_cmemc/parameter_types/__init__.py +0 -0
  48. {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.0rc2}/cmem_cmemc/parameter_types/path.py +0 -0
  49. {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.0rc2}/cmem_cmemc/smart_path/__init__.py +0 -0
  50. {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.0rc2}/cmem_cmemc/smart_path/clients/__init__.py +0 -0
  51. {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.0rc2}/cmem_cmemc/smart_path/clients/http.py +0 -0
  52. {cmem_cmemc-24.3.0rc1 → cmem_cmemc-24.3.0rc2}/cmem_cmemc/title_helper.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cmem-cmemc
3
- Version: 24.3.0rc1
3
+ Version: 24.3.0rc2
4
4
  Summary: Command line client for eccenca Corporate Memory
5
5
  Home-page: https://eccenca.com/go/cmemc
6
6
  License: Apache-2.0
@@ -4,13 +4,16 @@ import gzip
4
4
  import hashlib
5
5
  import io
6
6
  import json
7
+ import mimetypes
7
8
  import os
9
+ from json import JSONDecodeError
8
10
  from xml.dom import minidom # nosec
9
11
  from xml.etree.ElementTree import ( # nosec
10
12
  Element,
11
13
  SubElement,
12
14
  tostring,
13
15
  )
16
+ from xml.sax import SAXParseException
14
17
 
15
18
  import click
16
19
  from click import Argument
@@ -20,6 +23,9 @@ from cmem.cmempy.dp.proxy import graph as graph_api
20
23
  from cmem.cmempy.dp.proxy.graph import get_graph_import_tree, get_graph_imports
21
24
  from cmem.cmempy.dp.proxy.sparql import get as sparql_api
22
25
  from jinja2 import Template
26
+ from rdflib import Graph
27
+ from rdflib.exceptions import ParserError
28
+ from rdflib.plugins.parsers.notation3 import BadSyntax
23
29
  from six.moves.urllib.parse import quote
24
30
  from treelib import Tree
25
31
 
@@ -103,7 +109,7 @@ def _get_graph_to_file( # noqa: PLR0913
103
109
 
104
110
 
105
111
  def _get_export_names(
106
- app: ApplicationContext, iris: list[str], template: str, file_extension: str = "ttl"
112
+ app: ApplicationContext, iris: list[str], template: str, file_extension: str = ".ttl"
107
113
  ) -> dict:
108
114
  """Get a dictionary of generated file names based on a template.
109
115
 
@@ -131,7 +137,7 @@ def _get_export_names(
131
137
  hash=hashlib.sha256(iri.encode("utf-8")).hexdigest(),
132
138
  iriname=convert_uri_to_filename(iri),
133
139
  )
134
- _name_created = f"{Template(template).render(template_data)}.{file_extension}"
140
+ _name_created = f"{Template(template).render(template_data)}{file_extension}"
135
141
  _names[iri] = _name_created
136
142
  if len(_names.values()) != len(set(_names.values())):
137
143
  raise ValueError(
@@ -542,9 +548,9 @@ def _validate_export_command_input_parameters(
542
548
  )
543
549
  @click.option(
544
550
  "--mime-type",
545
- default="application/n-triples",
551
+ default="text/turtle",
546
552
  show_default=True,
547
- type=click.Choice(["application/n-triples", "text/turtle"]),
553
+ type=click.Choice(["application/n-triples", "text/turtle", "application/rdf+xml"]),
548
554
  help="Define the requested mime type",
549
555
  )
550
556
  @click.option(
@@ -589,8 +595,11 @@ def export_command( # noqa: PLR0913
589
595
  app.echo_debug("output is directory")
590
596
  # pre-calculate all filenames with the template,
591
597
  # 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")
598
+ extension = mimetypes.guess_extension(mime_type)
599
+ _names = _get_export_names(
600
+ app, iris, template, f"{extension}.gz" if compress else f"{extension}"
601
+ )
602
+ _graph_file_names = _get_export_names(app, iris, template, f"{extension}.graph")
594
603
  # create directory
595
604
  if not Path(output_dir).exists():
596
605
  app.echo_warning("Output directory does not exist: " + "will create it.")
@@ -661,6 +670,57 @@ def validate_input_path(input_path: str) -> None:
661
670
  )
662
671
 
663
672
 
673
+ def _get_graph_supported_formats() -> dict[str, str]:
674
+ return {
675
+ "application/rdf+xml": "xml",
676
+ "application/ld+json": "json-ld",
677
+ "text/turtle": "turtle",
678
+ "application/n-triples": "nt",
679
+ }
680
+
681
+
682
+ def _guess_rdf_mime_type(content: str) -> str:
683
+ formats = _get_graph_supported_formats()
684
+ for mime_type, rdf_format in formats.items():
685
+ try:
686
+ g = Graph()
687
+ g.parse(data=content, format=rdf_format)
688
+ except (SAXParseException, JSONDecodeError, BadSyntax, ParserError):
689
+ continue
690
+ else:
691
+ return mime_type
692
+ raise ValueError("Unknown format")
693
+
694
+
695
+ def _parse_triple_file(triple_file: str) -> tuple[io.BytesIO, str]:
696
+ """Parse the content of the triple file."""
697
+ buffer = io.BytesIO()
698
+ transport_params = {}
699
+
700
+ if Path(str(triple_file)).schema in ["http", "https"]:
701
+ transport_params["headers"] = {
702
+ "Accept": "text/turtle; q=1.0, application/x-turtle; q=0.9, text/n3;"
703
+ " q=0.8, application/rdf+xml; q=0.5, text/plain; q=0.1"
704
+ }
705
+
706
+ with ClickSmartPath.open(triple_file, transport_params=transport_params) as file_obj:
707
+ buffer.write(file_obj.read())
708
+
709
+ buffer.seek(0)
710
+ is_gzip = buffer.read(2) == b"\x1f\x8b"
711
+ buffer.seek(0)
712
+
713
+ if is_gzip:
714
+ with gzip.GzipFile(fileobj=buffer, mode="rb") as gzip_file:
715
+ graph_content = gzip_file.read().decode("utf-8")
716
+ else:
717
+ graph_content = buffer.read().decode("utf-8")
718
+
719
+ content_type = _guess_rdf_mime_type(graph_content)
720
+ buffer.seek(0)
721
+ return buffer, content_type
722
+
723
+
664
724
  @click.command(cls=CmemcCommand, name="import")
665
725
  @click.option(
666
726
  "--replace",
@@ -722,8 +782,12 @@ def import_command(
722
782
  graphs = read_rdf_graph_files(input_path)
723
783
  else:
724
784
  # 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")]
785
+ graphs = []
786
+ for _ in _get_graph_supported_formats():
787
+ extension = mimetypes.guess_extension(_)
788
+ graphs += [(file, iri) for file in Path(input_path).glob(f"*{extension}")]
789
+ graphs += [(file, iri) for file in Path(input_path).glob(f"*{extension}.gz")]
790
+
727
791
  elif Path(input_path).is_file():
728
792
  if iri is None:
729
793
  raise ValueError(
@@ -748,17 +812,10 @@ def import_command(
748
812
  continue
749
813
  # prevents re-replacing of graphs in a single run
750
814
  _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)
815
+ _buffer, content_type = _parse_triple_file(triple_file)
816
+ response = graph_api.post_streamed(
817
+ graph_iri, _buffer, replace=_replace, content_type=content_type
818
+ )
762
819
  request_headers = response.request.headers
763
820
  request_headers.pop("Authorization")
764
821
  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="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
 
@@ -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
 
@@ -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
@@ -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,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "cmem-cmemc"
3
- version = "24.3.0rc1"
3
+ version = "24.3.0rc2"
4
4
  description = "Command line client for eccenca Corporate Memory"
5
5
  license = "Apache-2.0"
6
6
  classifiers = [
File without changes