Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .copier-answers.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Changes here will be overwritten by Copier
_commit: v7.3.0-22-g87d8a9b
_commit: v8.2.0
_src_path: gh:eccenca/cmem-plugin-template
author_mail: cmempy-developer@eccenca.com
author_name: eccenca GmbH
Expand Down
15 changes: 13 additions & 2 deletions .github/workflows/check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ jobs:
- name: Check out repository
uses: actions/checkout@v5

- name: Cache Trivy DB
id: cache-trivydb
uses: actions/cache@v4
with:
path: .trivycache
key: ${{ runner.os }}-trivydb

- name: Install Task
uses: arduino/setup-task@v2

Expand Down Expand Up @@ -61,9 +68,13 @@ jobs:
run: |
task check:deptry

- name: safety
- name: trivy
env:
TRIVY_NO_PROGRESS: "true"
TRIVY_CACHE_DIR: ".trivycache/"
TRIVY_DISABLE_VEX_NOTICE: "true"
run: |
task check:safety
task check:trivy

- name: Publish Test Report in Action
uses: mikepenz/action-junit-report@v4
Expand Down
13 changes: 10 additions & 3 deletions .gitlab-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,17 +60,24 @@ deptry:
script:
- task check:deptry

safety:
trivy:
stage: test
variables:
TRIVY_NO_PROGRESS: "true"
TRIVY_CACHE_DIR: ".trivycache/"
TRIVY_DISABLE_VEX_NOTICE: "true"
script:
- task check:safety
- task check:trivy
cache:
paths:
- .trivycache/

build:
stage: build
needs:
- mypy
- pytest
- safety
- trivy
- deptry
script:
- task build
Expand Down
9 changes: 9 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
---
default_language_version:
python: python3.13

repos:
- repo: local
hooks:
Expand Down Expand Up @@ -36,3 +39,9 @@ repos:
stages: [post-checkout, post-merge]
always_run: true

- id: trivy
name: check:trivy
description: run trivy to scan for vulnerabilities
entry: task check:trivy
language: python
pass_filenames: false
4 changes: 4 additions & 0 deletions .trivyignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# .trivyignore

# ignore 51358 safety - dev dependency only
CVE-2022-39280
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](https://semver.org/)

## [Unreleased]

### Added

- Parameter to ignore specific type IRIs (blacklisting)

## [4.2.0] 2025-10-20

### Changed
Expand Down
14 changes: 9 additions & 5 deletions Taskfile.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ tasks:
- task: check:ruff
- task: check:mypy
- task: check:deptry
- task: check:safety
- task: check:trivy

check:pytest:
desc: Run unit and integration tests
Expand Down Expand Up @@ -154,12 +154,16 @@ tasks:
vars:
JUNIT_FILE: ./{{.DIST_DIR}}/junit-mypy.xml

check:safety:
desc: Complain about vulnerabilities in dependencies
check:trivy:
desc: Scan for vulnerabilities using Trivy
<<: *preparation
cmds:
# ignore 51358 safety - dev dependency only
- poetry run safety check -i 51358
- >
poetry run trivy fs
--include-dev-deps
--scanners vuln
--exit-code 1
.

check:deptry:
desc: Complain about unused or missing dependencies
Expand Down
6 changes: 0 additions & 6 deletions cmem_plugin_shapes/doc/__init__.py

This file was deleted.

47 changes: 0 additions & 47 deletions cmem_plugin_shapes/doc/shapes_doc.md

This file was deleted.

78 changes: 75 additions & 3 deletions cmem_plugin_shapes/plugin_shapes.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,6 @@
from rdflib import DCTERMS, RDF, RDFS, SH, XSD, Graph, Literal, Namespace, URIRef
from rdflib.namespace import split_uri

from cmem_plugin_shapes.doc import SHAPES_DOC

from . import __path__

SHUI = Namespace("https://vocab.eccenca.com/shui/")
Expand Down Expand Up @@ -73,7 +71,65 @@ def str2bool(value: str) -> bool:
label=PLUGIN_LABEL,
icon=Icon(file_name="shapes.svg", package=__package__),
description="Generate SHACL node and property shapes from a data graph",
documentation=SHAPES_DOC,
documentation="""This workflow task generates SHACL (Shapes Constraint Language)
node and property shapes by analyzing instance data from a knowledge graph. The generated
shapes describe the structure and properties of the classes used in the data graph.

## Usage

The plugin analyzes an input data graph and creates:

- **Node shapes**: One for each class (`rdf:type`) used in the data graph
- **Property shapes**: For all properties associated with each class, including:
- Regular object properties (subject → object relationships)
- Inverse object properties (object ← subject relationships, marked with ← prefix)
- Datatype properties (literal values)

## Output

The generated shapes are written to a shape catalog graph with:

- Unique URIs based on UUIDs (UUID5 derived from class/property IRIs)
- Human-readable labels and names (using namespace prefixes when available)
- Metadata including source data graph reference and timestamps
- Optional plugin provenance information (see advanced options)

## Example

Given a data graph with:

``` turtle
ex:Person123 a ex:Person ;
ex:name "John" ;
ex:knows ex:Person456 .
```

The plugin generates:

- A node shape for `ex:Person` with `sh:targetClass ex:Person`

``` turtle
graph:90ee6e27-59b1-5ac8-9d7a-116c60c6791a a sh:NodeShape ;
rdfs:label "Person (ex:)"@en ;
sh:name "Person (ex:)"@en ;
sh:property
graph:0fcf371d-f99a-5eeb-ab50-6e6b5fbb0e06 ,
graph:dd5c6728-75a2-5215-8a5d-f9cd4077aaea ;
sh:targetClass ex:Person .
```

- Property shapes for `ex:name` (datatype property) and `ex:knows` (object property)

``` turtle
graph:0fcf371d-f99a-5eeb-ab50-6e6b5fbb0e06 a sh:PropertyShape ;
rdfs:label "knows (ex:)"@en ;
sh:name "knows (ex:)"@en ;
sh:nodeKind sh:IRI ;
sh:path ex:knows ;
shui:showAlways true .
```

""",
parameters=[
PluginParameter(
param_type=GraphParameterType(allow_only_autocompleted_values=False),
Expand Down Expand Up @@ -135,6 +191,13 @@ def str2bool(value: str) -> bool:
description="Provide the list of properties (as IRIs) to ignore.",
advanced=True,
),
PluginParameter(
param_type=MultilineStringParameterType(),
name="ignore_types",
label="Types to ignore",
description="Provide the list of types (as IRIs) to ignore.",
advanced=True,
),
PluginParameter(
param_type=BoolParameterType(),
name="plugin_provenance",
Expand All @@ -156,6 +219,7 @@ def __init__( # noqa: PLR0913
import_shapes: bool = False,
prefix_cc: bool = False,
ignore_properties: str = "http://www.w3.org/1999/02/22-rdf-syntax-ns#type",
ignore_types: str = "",
plugin_provenance: bool = False,
) -> None:
if not validators.url(data_graph_iri):
Expand Down Expand Up @@ -191,6 +255,12 @@ def __init__( # noqa: PLR0913
raise ValueError(f"Invalid property IRI ({_}) in parameter 'Properties to ignore'")
self.ignore_properties.append(_)

self.ignore_types = []
for _ in filter(None, ignore_types.split("\n")):
if not validators.url(_):
raise ValueError(f"Invalid type IRI ({_}) in parameter 'Types to ignore'")
self.ignore_types.append(_)

self.plugin_provenance = plugin_provenance

self.shapes_count = 0
Expand Down Expand Up @@ -296,6 +366,7 @@ def get_class_dict(self) -> dict:
?subject a ?class .
?subject ?property ?object .
{self.iri_list_to_filter(self.ignore_properties)}
{self.iri_list_to_filter(self.ignore_types, name="class")}
BIND(isLiteral(?object) AS ?data)
BIND("false" AS ?inverse)
}}
Expand All @@ -304,6 +375,7 @@ def get_class_dict(self) -> dict:
?object a ?class .
?subject ?property ?object .
{self.iri_list_to_filter(self.ignore_properties)}
{self.iri_list_to_filter(self.ignore_types, name="class")}
BIND("false" AS ?data)
BIND("true" AS ?inverse)
}}
Expand Down
Loading