prezmanifest 0.2.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.
@@ -0,0 +1,29 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2024-2025, KurrawongAI
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are met:
8
+
9
+ 1. Redistributions of source code must retain the above copyright notice, this
10
+ list of conditions and the following disclaimer.
11
+
12
+ 2. Redistributions in binary form must reproduce the above copyright notice,
13
+ this list of conditions and the following disclaimer in the documentation
14
+ and/or other materials provided with the distribution.
15
+
16
+ 3. Neither the name of the copyright holder nor the names of its
17
+ contributors may be used to endorse or promote products derived from
18
+ this software without specific prior written permission.
19
+
20
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,60 @@
1
+ Metadata-Version: 2.1
2
+ Name: prezmanifest
3
+ Version: 0.2.2
4
+ Summary: A Python package that provides a series of functions to work with Prez Manifests.
5
+ License: BSD-3-Clause
6
+ Author: Nicholas Car
7
+ Author-email: nick@kurrawong.ai
8
+ Requires-Python: >=3.12,<4.0
9
+ Classifier: License :: OSI Approved :: BSD License
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.12
12
+ Classifier: Programming Language :: Python :: 3.13
13
+ Requires-Dist: kurra (>=0.6.1,<0.7.0)
14
+ Requires-Dist: labelify (>=0.3.8,<0.4.0)
15
+ Requires-Dist: pyshacl (>=0.29.0,<0.30.0)
16
+ Description-Content-Type: text/plain
17
+
18
+ = Prez Manifest
19
+
20
+ A Prez Manifest is an RDF file that describes and links to a set of resources that can be loaded into an RDF database for the http://prez.dev[Prez graph database publication system] to provide access to.
21
+
22
+ The Prez Manifest specification is online at:
23
+
24
+ * https://prez.dev/manifest/
25
+
26
+ This repository contains the `prez-manifest` Python package that provides a series of functions to work with Prez Manifests. The functions provided are:
27
+
28
+ * `create_table`: creates an ASCIIDOC or Markdown table of Manifest content from a Manifest file
29
+ * `validate`: validates that a Manifest file conforms to the specification and that all linked-to assets are available
30
+ * `load`: loads a Manifest file, and all the content it specifies, into either an n-quads file or a Fuseki database
31
+
32
+
33
+ == Installation & Use
34
+
35
+ This Python package is intended to be used on the command line on Linux/UNIX-like systems and/or as a Python library, called directly from other Python code.
36
+
37
+ It can be installed using https://python-poetry.org[Poetry]. It may one day be published on https://pypi.org[PyPI].
38
+
39
+ Please see the `documentor.py`, `loader.py`, & `validator.py` files in the `prezmanifest` folder and the test files in `test` for documentation text and examples of use.
40
+
41
+
42
+ == Testing
43
+
44
+ Run `python -m pytest` in the top-level folder to test. You must have Docker Desktop running to allow all loader tests to be executed.
45
+
46
+
47
+ == License
48
+
49
+ This code is available for reuse according to the https://opensource.org/license/bsd-3-clause[BSD 3-Clause License].
50
+
51
+ &copy; 2024-2025 KurrawongAI
52
+
53
+
54
+ == Contact
55
+
56
+ For all matters, please contact:
57
+
58
+ *KurrawongAI* +
59
+ info@kurrawong.ai +
60
+ https://kurrawong.ai +
@@ -0,0 +1,43 @@
1
+ = Prez Manifest
2
+
3
+ A Prez Manifest is an RDF file that describes and links to a set of resources that can be loaded into an RDF database for the http://prez.dev[Prez graph database publication system] to provide access to.
4
+
5
+ The Prez Manifest specification is online at:
6
+
7
+ * https://prez.dev/manifest/
8
+
9
+ This repository contains the `prez-manifest` Python package that provides a series of functions to work with Prez Manifests. The functions provided are:
10
+
11
+ * `create_table`: creates an ASCIIDOC or Markdown table of Manifest content from a Manifest file
12
+ * `validate`: validates that a Manifest file conforms to the specification and that all linked-to assets are available
13
+ * `load`: loads a Manifest file, and all the content it specifies, into either an n-quads file or a Fuseki database
14
+
15
+
16
+ == Installation & Use
17
+
18
+ This Python package is intended to be used on the command line on Linux/UNIX-like systems and/or as a Python library, called directly from other Python code.
19
+
20
+ It can be installed using https://python-poetry.org[Poetry]. It may one day be published on https://pypi.org[PyPI].
21
+
22
+ Please see the `documentor.py`, `loader.py`, & `validator.py` files in the `prezmanifest` folder and the test files in `test` for documentation text and examples of use.
23
+
24
+
25
+ == Testing
26
+
27
+ Run `python -m pytest` in the top-level folder to test. You must have Docker Desktop running to allow all loader tests to be executed.
28
+
29
+
30
+ == License
31
+
32
+ This code is available for reuse according to the https://opensource.org/license/bsd-3-clause[BSD 3-Clause License].
33
+
34
+ &copy; 2024-2025 KurrawongAI
35
+
36
+
37
+ == Contact
38
+
39
+ For all matters, please contact:
40
+
41
+ *KurrawongAI* +
42
+ info@kurrawong.ai +
43
+ https://kurrawong.ai +
@@ -0,0 +1,9 @@
1
+ import importlib.metadata
2
+
3
+ __version__ = importlib.metadata.version(__package__)
4
+
5
+ from .definednamespaces import MRR, OLIS, PREZ
6
+ from .validator import validate
7
+ from .documentor import create_table
8
+ from .loader import load
9
+ from .labeller import label
@@ -0,0 +1,37 @@
1
+ from rdflib.namespace import DefinedNamespace, Namespace
2
+ from rdflib.term import URIRef
3
+
4
+
5
+ class MRR(DefinedNamespace):
6
+
7
+ _NS = Namespace("https://prez.dev/ManifestResourceRoles/")
8
+ _fail = True
9
+
10
+ CatalogueData: URIRef
11
+ CatalogueModel: URIRef
12
+ ResourceData: URIRef
13
+ ResourceModel: URIRef
14
+ CatalogueAndResourceModel: URIRef
15
+ CompleteCatalogueAndResourceLabels: URIRef
16
+ IncompleteCatalogueAndResourceLabels: URIRef
17
+
18
+
19
+ class OLIS(DefinedNamespace):
20
+
21
+ _NS = Namespace("https://olis.dev/")
22
+ _fail = True
23
+
24
+ NamedGraph: URIRef
25
+ RealGraph: URIRef
26
+ SystemGraph: URIRef
27
+ VirtualGraph: URIRef
28
+
29
+ isAliasFor: URIRef
30
+
31
+
32
+ class PREZ(DefinedNamespace):
33
+
34
+ _NS = Namespace("https://prez.dev/")
35
+ _fail = True
36
+
37
+ Manifest: URIRef
@@ -0,0 +1,209 @@
1
+ """
2
+ This script creates a "Prez Resources" table in either Markdown or ASCIIDOC from a Manifest file which it validates first
3
+
4
+ Run this script with the -h flag for more help, i.e. ~$ python documentor.py -h
5
+
6
+ Example:
7
+
8
+ Input:
9
+
10
+ PREFIX mrr: <https://prez.dev/ManifestResourceRoles/>
11
+ PREFIX prez: <https://prez.dev/>
12
+ PREFIX prof: <http://www.w3.org/ns/dx/prof/>
13
+ PREFIX schema: <https://schema.org/>
14
+
15
+ []
16
+ a prez:Manifest ;
17
+ prof:hasResource
18
+ [
19
+ prof:hasArtifact "catalogue.ttl" ;
20
+ prof:hasRole mrr:ContainerData ;
21
+ schema:description "The definition of, and medata for, the container which here is a dcat:Catalog object" ;
22
+ schema:name "Catalogue Definition"
23
+ ] ,
24
+ [
25
+ prof:hasArtifact "vocabs/*.ttl" ;
26
+ prof:hasRole mrr:ContentData ;
27
+ schema:description "skos:ConceptScheme objects in RDF (Turtle) files in the vocabs/ folder" ;
28
+ schema:name "Content"
29
+ ] ,
30
+ [
31
+ prof:hasArtifact "https://github.com/RDFLib/prez/blob/main/prez/reference_data/profiles/ogc_records_profile.ttl" ;
32
+ prof:hasRole mrr:ContainerAndContentModel ;
33
+ schema:description "The default Prez profile for Records API" ;
34
+ schema:name "Profile Definition"
35
+ ] ,
36
+ [
37
+ prof:hasArtifact "_background/labels.ttl" ;
38
+ prof:hasRole mrr:CompleteContainerAndContentLabels ;
39
+ schema:description "An RDF file containing all the labels for the container content" ;
40
+ ] ;
41
+ .
42
+
43
+ Output:
44
+
45
+ Resource | Role | Description
46
+ --- | --- | ---
47
+ Catalogue Definition, [`catalogue.ttl`](catalogue.ttl) | [Container Data](https://prez.dev/ManifestResourceRoles/ContainerData) | The definition of, and medata for, the container which here is a dcat:Catalog object
48
+ Content, [`vocabs/*.ttl`](vocabs/*.ttl) | [Content Data](https://prez.dev/ManifestResourceRoles/ContentData) | skos:ConceptScheme objects in RDF (Turtle) files in the vocabs/ folder
49
+ Profile Definition, [`ogc_records_profile.ttl`](https://github.com/RDFLib/prez/blob/main/prez/reference_data/profiles/ogc_records_profile.ttl) | [Container & Content Model](https://prez.dev/ManifestResourceRoles/ContainerAndContentModel) | The default Prez profile for Records API
50
+ Labels file, [`_background/labels.ttl`](_background/labels.ttl) | [Complete Content and Container Labels](https://prez.dev/ManifestResourceRoles/CompleteContainerAndContentLabels) | An RDF file containing all the labels for the container content
51
+ """
52
+
53
+ import argparse
54
+ import sys
55
+ from pathlib import Path
56
+ from urllib.parse import ParseResult, urlparse
57
+
58
+ from rdflib import DCAT, DCTERMS, PROF, SDO, SKOS, Graph
59
+ from kurra.utils import load_graph
60
+
61
+ try:
62
+ from prezmanifest import MRR, validate, __version__
63
+ from prezmanifest.utils import *
64
+ except ImportError:
65
+ import sys
66
+
67
+ sys.path.append(str(Path(__file__).parent.parent.resolve()))
68
+ from prezmanifest import MRR, validate, __version__
69
+ from prezmanifest.utils import *
70
+
71
+
72
+ def create_table(manifest: Path, t="markdown") -> str:
73
+ # load and validate manifest
74
+ validate(manifest)
75
+ manifest_graph = load_graph(manifest)
76
+
77
+ # add in MRR vocab
78
+ manifest_graph += load_graph(Path(__file__).parent / "mrr.ttl")
79
+
80
+ if t == "asciidoc":
81
+ header = "|===\n| Resource | Role | Description\n\n"
82
+ else:
83
+ header = "Resource | Role | Description\n--- | --- | ---\n"
84
+
85
+ body = ""
86
+ for s, o in manifest_graph.subject_objects(PROF.hasResource):
87
+ a = str(manifest_graph.value(o, PROF.hasArtifact))
88
+ if t == "asciidoc":
89
+ artifact = f'link:{a}[`{a.split("/")[-1] if a.startswith("http") else a}`]'
90
+ else:
91
+ artifact = f'[`{a.split("/")[-1] if a.startswith("http") else a}`]({a})'
92
+ role_iri = manifest_graph.value(o, PROF.hasRole)
93
+ role_label = manifest_graph.value(role_iri, SKOS.prefLabel)
94
+ if t == "asciidoc":
95
+ role = f"{role_iri}[{role_label}]"
96
+ else:
97
+ role = f"[{role_label}]({role_iri})"
98
+ name = manifest_graph.value(o, SDO.name)
99
+ description = manifest_graph.value(o, SDO.description)
100
+ n = f"{name}, {artifact}" if name is not None else f"{artifact}"
101
+ d = description if description is not None else ""
102
+ if t == "asciidoc":
103
+ body += f"| {n} | {role} | {d}\n"
104
+ else:
105
+ body += f"{n} | {role} | {d}\n"
106
+
107
+ if t == "asciidoc":
108
+ footer = "|===\n"
109
+ else:
110
+ footer = ""
111
+
112
+ return (header + body + footer).strip()
113
+
114
+
115
+ def create_catalogue(manifest: Path):
116
+ MANIFEST_ROOT_DIR = manifest.parent
117
+ # load and validate manifest
118
+ validate(manifest)
119
+ manifest_graph = load_graph(manifest)
120
+
121
+ for s, o in manifest_graph.subject_objects(PROF.hasResource):
122
+ for role in manifest_graph.objects(o, PROF.hasRole):
123
+ if role == MRR.CatalogueData:
124
+ for artifact in manifest_graph.objects(o, PROF.hasArtifact):
125
+ # the artifact can only be a triples file (not a quads file)
126
+ catalogue = load_graph(MANIFEST_ROOT_DIR / artifact)
127
+
128
+ # get the IRI of the catalogue
129
+ catalogue_iri = catalogue.value(predicate=RDF.type, object=DCAT.Catalog)
130
+
131
+ # non-catalogue resources
132
+ for s, o in manifest_graph.subject_objects(PROF.hasResource):
133
+ for role in manifest_graph.objects(o, PROF.hasRole):
134
+ if role in [
135
+ MRR.CompleteCatalogueAndResourceLabels,
136
+ MRR.IncompleteCatalogueAndResourceLabels,
137
+ MRR.ResourceData,
138
+ ]:
139
+ for artifact in manifest_graph.objects(o, PROF.hasArtifact):
140
+ for f in get_files_from_artifact(manifest, artifact):
141
+ for iri in sorted(get_identifier_from_file(f)):
142
+ if iri != URIRef("urn:x-rdflib:default"):
143
+ catalogue.add((catalogue_iri, DCTERMS.hasPart, iri))
144
+
145
+ return catalogue
146
+
147
+
148
+ def setup_cli_parser(args=None):
149
+ def url_file_or_folder(input: str) -> ParseResult | Path:
150
+ parsed = urlparse(input)
151
+ if all([parsed.scheme, parsed.netloc]):
152
+ return parsed
153
+ path = Path(input)
154
+ if path.is_file():
155
+ return path
156
+ if path.is_dir():
157
+ return path
158
+ raise argparse.ArgumentTypeError(
159
+ f"{input} is not a valid input. Must be a file, folder or sparql endpoint"
160
+ )
161
+
162
+ parser = argparse.ArgumentParser()
163
+
164
+ parser.add_argument(
165
+ "-v",
166
+ "--version",
167
+ action="version",
168
+ version="{version}".format(version=__version__),
169
+ )
170
+
171
+ parser.add_argument(
172
+ "function",
173
+ help="The documentation function you wish to perform",
174
+ choices=["table", "catalogue"],
175
+ )
176
+
177
+ parser.add_argument(
178
+ "-t",
179
+ "--type",
180
+ help="The type of markup you want to export: Markdown or ASCCIDOC. Only relevant for the 'table' function",
181
+ choices=["markdown", "asciidoc"],
182
+ default="markdown",
183
+ )
184
+
185
+ parser.add_argument(
186
+ "input",
187
+ help="A Prez Manifest file",
188
+ type=url_file_or_folder,
189
+ )
190
+
191
+ return parser.parse_args(args)
192
+
193
+
194
+ def cli(args=None):
195
+ if args is None:
196
+ args = sys.argv[1:]
197
+
198
+ args = setup_cli_parser(args)
199
+
200
+ if args.function == "table":
201
+ print(create_table(args.input, t=args.type))
202
+ else:
203
+ print(create_catalogue(args.input).serialize(format="longturtle"))
204
+
205
+
206
+ if __name__ == "__main__":
207
+ retval = cli(sys.argv[1:])
208
+ if retval is not None:
209
+ sys.exit(retval)
@@ -0,0 +1,14 @@
1
+ from pathlib import Path
2
+ from rdflib import Graph
3
+ from rdflib.namespace import RDF, SKOS
4
+
5
+ FILES_DIR = Path("something")
6
+
7
+ for f in FILES_DIR.glob("*.ttl"):
8
+ g = Graph()
9
+ g.parse(f)
10
+ iri = g.value(predicate=RDF.type, object=SKOS.ConceptScheme)
11
+
12
+ # print(f"{f.name}, {iri}")
13
+
14
+ print(f"<{iri}> ,")
@@ -0,0 +1,188 @@
1
+ """
2
+ Assesses a given Manifest, finds any IRIs in any of the given resources missing labels and tries to patch them from
3
+ a given source of labels, such as KurrawongAI's Semantic Background (https://github.com/kurrawong/semantic-background)
4
+ repository.
5
+ """
6
+ import argparse
7
+ import sys
8
+ from pathlib import Path
9
+ from typing import Literal as TLiteral
10
+ from urllib.parse import ParseResult, urlparse
11
+
12
+ from kurra.utils import load_graph
13
+ from labelify import find_missing_labels, extract_labels
14
+ from rdflib import Graph, BNode, Literal
15
+ from rdflib.namespace import PROF, RDF
16
+
17
+ from prezmanifest.utils import get_files_from_artifact
18
+
19
+ try:
20
+ from prezmanifest import MRR, OLIS, validate, load, __version__
21
+ except ImportError:
22
+ import sys
23
+
24
+ sys.path.append(str(Path(__file__).parent.parent.resolve()))
25
+ from prezmanifest import MRR, OLIS, validate, __version__
26
+
27
+
28
+ def label(
29
+ manifest: Path,
30
+ output: TLiteral["iris", "rdf", "manifest"] = "manifest",
31
+ additional_context: Path | str | Graph = None,
32
+ ) -> set | Graph | None:
33
+ """"Main function for labeller module"""
34
+ # create the target from the Manifest
35
+ manifest_content_graph = load(manifest, return_data_type="Graph")
36
+
37
+ output_types = ["iris", "rdf", "manifest"]
38
+ if output not in output_types:
39
+ raise ValueError(f"Parameter output is {output} but must be one of {', '.join(output_types)}")
40
+
41
+ # determine if any labelling context is given in Manifest
42
+ context_graph = Graph()
43
+ for s, o in manifest_content_graph.subject_objects(PROF.hasResource):
44
+ for role in manifest_content_graph.objects(o, PROF.hasRole):
45
+ if role in [
46
+ MRR.IncompleteCatalogueAndResourceLabels,
47
+ MRR.CompleteCatalogueAndResourceLabels,
48
+ ]:
49
+ for artifact in manifest_content_graph.objects(o, PROF.hasArtifact):
50
+ artifact: Literal
51
+ for f in get_files_from_artifact(manifest, artifact):
52
+ context_graph += load_graph(f)
53
+
54
+ if output == "iris":
55
+ return find_missing_labels(manifest_content_graph + context_graph, additional_context)
56
+
57
+ elif output == "rdf":
58
+ iris = find_missing_labels(manifest_content_graph, context_graph)
59
+
60
+ if additional_context is not None:
61
+ return extract_labels(iris, additional_context)
62
+ else:
63
+ return None
64
+
65
+ else: # output == manifest
66
+ # If this is selected, generate the "rdf" output and create a resource for it in the Manifest
67
+ # If there are no more missing labels then we have an mrr:CompleteCatalogueAndResourceLabels
68
+ # else add mrr:IncompleteCatalogueAndResourceLabels
69
+
70
+ # Generate labels for any IRIs missing them, using context given in the Manifest and any
71
+ # Additional Context supplied
72
+ manifest_only_graph = load_graph(manifest)
73
+ rdf_addition = label(manifest, "rdf", additional_context)
74
+
75
+ if len(rdf_addition) > 0:
76
+ new_artifact = manifest.parent / "labels-additional.ttl"
77
+ rdf_addition.serialize(destination=new_artifact, format="longturtle")
78
+ new_resource = BNode()
79
+
80
+ # Find the role of any context in the Manifest
81
+ manifest_iri = None
82
+ context_roles = []
83
+ for s, o in manifest_only_graph.subject_objects(PROF.hasResource):
84
+ manifest_iri = s
85
+ for role in manifest_only_graph.objects(o, PROF.hasRole):
86
+ if role in [
87
+ MRR.IncompleteCatalogueAndResourceLabels,
88
+ MRR.CompleteCatalogueAndResourceLabels,
89
+ ]:
90
+ context_roles.append(role)
91
+
92
+ if MRR.CompleteCatalogueAndResourceLabels in context_roles and len(context_roles) == 1:
93
+ # If a CompleteCatalogueAndResourceLabels is present in Manifest and yet more labels were discovered,
94
+ # change CompleteCatalogueAndResourceLabels to IncompleteCatalogueAndResourceLabels and add another
95
+ for s, o in manifest_content_graph.subject_objects(PROF.hasRole):
96
+ if o == MRR.CompleteCatalogueAndResourceLabels:
97
+ manifest_only_graph.remove((s, PROF.hasRole, o))
98
+ manifest_only_graph.add((manifest_iri, PROF.hasResource, new_resource))
99
+ manifest_only_graph.add((new_resource, PROF.hasRole, MRR.IncompleteCatalogueAndResourceLabels))
100
+ manifest_only_graph.add((new_resource, PROF.hasArtifact, Literal(new_artifact.name)))
101
+ else:
102
+ # If an IncompleteCatalogueAndResourceLabels was present, add another IncompleteCatalogueAndResourceLabels
103
+ # which together make a CompleteCatalogueAndResourceLabels
104
+
105
+ # If none was present, add an IncompleteCatalogueAndResourceLabels or a CompleteCatalogueAndResourceLabels
106
+ # TODO: test for completeness of labelling and add in CompleteCatalogueAndResourceLabels if complete
107
+ manifest_only_graph.add((manifest_iri, PROF.hasResource, new_resource))
108
+ manifest_only_graph.add((new_resource, PROF.hasRole, MRR.IncompleteCatalogueAndResourceLabels))
109
+ manifest_only_graph.add((new_resource, PROF.hasArtifact, Literal(new_artifact.name)))
110
+
111
+ manifest_only_graph.serialize(destination=manifest, format="longturtle")
112
+
113
+ else:
114
+ raise Warning("No new labels have been generated for content in this Manifest. "
115
+ "This could be because none were missing or because no new labels can be found in any "
116
+ "supplied additional context.")
117
+
118
+
119
+ def setup_cli_parser(args=None):
120
+ def url_file_or_folder(input: str) -> ParseResult | Path:
121
+ parsed = urlparse(input)
122
+ if all([parsed.scheme, parsed.netloc]):
123
+ return parsed
124
+ path = Path(input)
125
+ if path.is_file():
126
+ return path
127
+ if path.is_dir():
128
+ return path
129
+ raise argparse.ArgumentTypeError(
130
+ f"{input} is not a valid input. Must be a file, folder or sparql endpoint"
131
+ )
132
+
133
+ parser = argparse.ArgumentParser()
134
+ group = parser.add_mutually_exclusive_group(required=True)
135
+
136
+ parser.add_argument(
137
+ "-v",
138
+ "--version",
139
+ action="version",
140
+ version="{version}".format(version=__version__),
141
+ )
142
+
143
+ group.add_argument(
144
+ "-o",
145
+ "--output",
146
+ help="The form of output you want",
147
+ choices=["iris", "rdf", "manifest"],
148
+ default="manifest",
149
+ )
150
+
151
+ parser.add_argument(
152
+ "-a",
153
+ "--additional-context",
154
+ help="File, Folder or Sparql Endpoint to read additional context RDF from",
155
+ type=url_file_or_folder,
156
+ )
157
+
158
+ parser.add_argument(
159
+ "manifest",
160
+ help="A Manifest file to process",
161
+ type=Path,
162
+ )
163
+
164
+ return parser.parse_args(args)
165
+
166
+
167
+ def cli(args=None):
168
+ if args is None:
169
+ args = sys.argv[1:]
170
+
171
+ args = setup_cli_parser(args)
172
+
173
+ x = label(args.manifest, args.output, args.additional_context)
174
+
175
+ if args.output == "iris":
176
+ print("\n".join([str(iri) for iri in x]))
177
+ elif args.output == "rdf":
178
+ if x is not None:
179
+ print(x.serialize(format="longturtle"))
180
+
181
+ else: # manifest
182
+ pass
183
+
184
+
185
+ if __name__ == "__main__":
186
+ retval = cli(sys.argv[1:])
187
+ if retval is not None:
188
+ sys.exit(retval)
@@ -0,0 +1,244 @@
1
+ """
2
+ Either creates an n-quads files containing the content of a Manifest file or uploads the content to Fuseki.
3
+
4
+ It creates:
5
+
6
+ 1. A Named Graph for each resource using the item's IRI as the graph IRI
7
+ 2. A Named Graph for the catalogue, either using the catalogue's IRI as the graph IRI + "-catalogue" if given, or by making one up - a Blank Node
8
+ 3. All the triples in resources with roles mrr:CompleteCatalogueAndResourceLabels & mrr:IncompleteCatalogueAndResourceLabels within a Named Graph with IRI <https://background>
9
+ 4. An Olis Virtual Graph, <https://olis.dev/VirtualGraph> object using the catalogue IRI, if give, which is as an alias for all the Named Graphs from 1., 2. & 3.
10
+ 5. Multiple entries in the System Graph - Named Graph with IRI <https://olis.dev/SystemGraph> - for each Named and the Virtual Graph from 1., 2. & 3.
11
+
12
+ Run this script with the -h flag for more help, i.e. ~$ python loader.py -h
13
+ """
14
+
15
+ import argparse
16
+ import sys
17
+ from pathlib import Path
18
+
19
+ from kurra.format import make_dataset, export_quads
20
+ from kurra.fuseki import upload
21
+ from kurra.utils import load_graph
22
+ from rdflib import DCAT, DCTERMS, OWL, PROF, RDF, SDO, SKOS
23
+ from rdflib import Graph, URIRef, Dataset
24
+ from typing import Literal as TLiteral
25
+ import logging
26
+
27
+ try:
28
+ from prezmanifest import MRR, OLIS, validate, __version__
29
+ from prezmanifest.utils import get_files_from_artifact
30
+ except ImportError:
31
+ import sys
32
+
33
+ sys.path.append(str(Path(__file__).parent.parent.resolve()))
34
+ from prezmanifest import MRR, OLIS, validate, __version__
35
+ from prezmanifest.utils import get_files_from_artifact
36
+
37
+
38
+ def load(
39
+ manifest: Path,
40
+ sparql_endpoint: str = None,
41
+ destination_file: Path = None,
42
+ return_data_type: TLiteral["Graph", "Dataset", None] = None
43
+ ) -> None | Graph | Dataset:
44
+ """Loads a catalogue of data from a prezmanifest file, whose content are valid according to the Prez Manifest Model
45
+ (https://kurrawong.github.io/prez.dev/manifest/) either into a specified quads file in the Trig format, or into a
46
+ given SPARQL Endpoint."""
47
+
48
+ if return_data_type == "Dataset":
49
+ dataset_holder = Dataset()
50
+
51
+ if return_data_type == "Graph":
52
+ graph_holder = Graph()
53
+
54
+ return_data_value_error_message = "return_data_type was set to an invalid value. Must be one of Dataset or Graph or None"
55
+
56
+ def _export(data: Graph | Dataset, iri, sparql_endpoint, destination_file, return_data_type, append=False):
57
+ if type(data) is Dataset:
58
+ if iri is not None:
59
+ raise ValueError("If the data is a Dataset, the parameter iri must be None")
60
+
61
+ if destination_file is not None:
62
+ export_quads(data, destination_file)
63
+ elif sparql_endpoint is not None:
64
+ for g in data.graphs():
65
+ if g.identifier != URIRef("urn:x-rdflib:default"):
66
+ _export(g, g.identifier, sparql_endpoint, None, None)
67
+ else:
68
+ if return_data_type == "Dataset":
69
+ return data
70
+ elif return_data_type == "Graph":
71
+ gx = Graph()
72
+ for g in data.graphs():
73
+ if g.identifier != URIRef("urn:x-rdflib:default"):
74
+ for s, p, o in g.triples((None, None, None)):
75
+ gx.add((s, p, o))
76
+ return gx
77
+
78
+ elif type(data) is Graph:
79
+ if iri is None:
80
+ raise ValueError("If the data is a GRaph, the parameter iri must not be None")
81
+
82
+ msg = f"exporting {iri} "
83
+ if destination_file is not None:
84
+ msg += f"to file {destination_file} "
85
+ export_quads(make_dataset(data, iri), destination_file)
86
+ elif sparql_endpoint is not None:
87
+ msg += f"to SPARQL Endpoint {sparql_endpoint}"
88
+ upload(sparql_endpoint, data, iri, append)
89
+ else: # returning data
90
+ if return_data_type == "Dataset":
91
+ msg += "to Dataset"
92
+ for s, p, o in data:
93
+ dataset_holder.add((s, p, o, iri))
94
+ elif return_data_type == "Graph":
95
+ msg += "to Graph"
96
+ for s, p, o in data:
97
+ graph_holder.add((s, p, o))
98
+ else:
99
+ raise ValueError(return_data_value_error_message)
100
+
101
+ logging.info(msg)
102
+
103
+ if sum(x is not None for x in [sparql_endpoint, destination_file, return_data_type]) != 1:
104
+ raise ValueError(
105
+ "You must specify exactly 1 of sparql_endpoint, destination_file or return_data_type",
106
+ )
107
+
108
+ MANIFEST_ROOT_DIR = manifest.parent
109
+ # load and validate manifest
110
+ validate(manifest)
111
+ manifest_graph = load_graph(manifest)
112
+
113
+ vg = Graph()
114
+ vg_iri = None
115
+
116
+ for s, o in manifest_graph.subject_objects(PROF.hasResource):
117
+ for role in manifest_graph.objects(o, PROF.hasRole):
118
+ # The catalogue - must be processed first
119
+ if role == MRR.CatalogueData:
120
+ for artifact in manifest_graph.objects(o, PROF.hasArtifact):
121
+ # load the Catalogue, determine the Virtual Graph & Catalogue IRIs
122
+ # and fail if we can't see a Catalogue object
123
+ c = load_graph(MANIFEST_ROOT_DIR / str(artifact))
124
+ vg_iri = c.value(predicate=RDF.type, object=DCAT.Catalog)
125
+ if vg_iri is None:
126
+ raise ValueError(
127
+ f"ERROR: Could not create a Virtual Graph as no Catalog found in the Catalogue data"
128
+ )
129
+ catalogue_iri = URIRef(str(vg_iri) + "-catalogue")
130
+
131
+ # add to the System Graph
132
+ vg.add((vg_iri, RDF.type, OLIS.VirtualGraph))
133
+ vg.add((vg_iri, OLIS.isAliasFor, catalogue_iri))
134
+ vg_name = c.value(
135
+ subject=vg_iri,
136
+ predicate=SDO.name | DCTERMS.title | SKOS.prefLabel,
137
+ ) or str(vg_iri)
138
+ vg.add((vg_iri, SDO.name, vg_name))
139
+
140
+ # export the Catalogue data
141
+ _export(c, catalogue_iri, sparql_endpoint, destination_file, return_data_type)
142
+
143
+ # non-catalogue resources
144
+ for s, o in manifest_graph.subject_objects(PROF.hasResource):
145
+ for role in manifest_graph.objects(o, PROF.hasRole):
146
+ # The data files & background - must be processed after Catalogue
147
+ if role in [
148
+ MRR.CompleteCatalogueAndResourceLabels,
149
+ MRR.IncompleteCatalogueAndResourceLabels,
150
+ MRR.ResourceData,
151
+ ]:
152
+ for artifact in manifest_graph.objects(o, PROF.hasArtifact):
153
+ for f in get_files_from_artifact(manifest, artifact):
154
+ if str(f.name).endswith(".ttl"):
155
+ fg = Graph().parse(f)
156
+ # fg.bind("rdf", RDF)
157
+
158
+ if role == MRR.ResourceData:
159
+ resource_iri = fg.value(predicate=RDF.type, object=SKOS.ConceptScheme) or fg.value(predicate=RDF.type, object=OWL.Ontology)
160
+
161
+ if role in [
162
+ MRR.CompleteCatalogueAndResourceLabels,
163
+ MRR.IncompleteCatalogueAndResourceLabels,
164
+ ]:
165
+ resource_iri = URIRef("http://background")
166
+
167
+ if resource_iri is None:
168
+ raise ValueError(f"Could not determine Resource IRI for file {f}")
169
+
170
+ vg.add((vg_iri, OLIS.isAliasFor, resource_iri))
171
+
172
+ # export one Resource
173
+ _export(fg, resource_iri, sparql_endpoint, destination_file, return_data_type)
174
+ elif str(f.name).endswith(".trig"):
175
+ d = Dataset()
176
+ d.parse(f)
177
+ for g in d.graphs():
178
+ if g.identifier != URIRef("urn:x-rdflib:default"):
179
+ vg.add((vg_iri, OLIS.isAliasFor, g.identifier))
180
+ _export(d, None, sparql_endpoint, destination_file, return_data_type)
181
+
182
+
183
+ # export the System Graph
184
+ _export(vg, OLIS.SystemGraph, sparql_endpoint, destination_file, return_data_type, append=True)
185
+
186
+ if return_data_type == "Dataset":
187
+ return dataset_holder
188
+ elif return_data_type == "Graph":
189
+ return graph_holder
190
+ elif return_data_type is None:
191
+ pass # return nothing
192
+ else:
193
+ raise ValueError(return_data_value_error_message)
194
+
195
+
196
+ def setup_cli_parser(args=None):
197
+ parser = argparse.ArgumentParser()
198
+ group = parser.add_mutually_exclusive_group(required=True)
199
+
200
+ parser.add_argument(
201
+ "-v",
202
+ "--version",
203
+ action="version",
204
+ version="{version}".format(version=__version__),
205
+ )
206
+
207
+ group.add_argument(
208
+ "-e",
209
+ "--endpoint",
210
+ help="The SPARQL endpoint you want to load the data into. Cannot be specified when destination is.",
211
+ )
212
+
213
+ group.add_argument(
214
+ "-d",
215
+ "--destination",
216
+ help="The n-quads file you want to export the data into. Cannot be specified when endpoint is.",
217
+ )
218
+
219
+ parser.add_argument(
220
+ "manifest",
221
+ help="A Manifest file to process",
222
+ type=Path,
223
+ )
224
+
225
+ return parser.parse_args(args)
226
+
227
+
228
+ def cli(args=None):
229
+ if args is None:
230
+ args = sys.argv[1:]
231
+
232
+ args = setup_cli_parser(args)
233
+
234
+ load(
235
+ Path(args.manifest),
236
+ args.endpoint if args.endpoint is not None else None,
237
+ Path(args.destination) if args.destination is not None else None,
238
+ )
239
+
240
+
241
+ if __name__ == "__main__":
242
+ retval = cli(sys.argv[1:])
243
+ if retval is not None:
244
+ sys.exit(retval)
@@ -0,0 +1,87 @@
1
+ PREFIX cs: <https://prez.dev/ManifestResourceRoles>
2
+ PREFIX : <https://prez.dev/ManifestResourceRoles/>
3
+ PREFIX owl: <http://www.w3.org/2002/07/owl#>
4
+ PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
5
+ PREFIX schema: <https://schema.org/>
6
+ PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
7
+ PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
8
+
9
+
10
+ :CompleteCatalogueAndResourceLabels
11
+ a skos:Concept ;
12
+ skos:definition "All the labels - possibly including names, descriptions & seeAlso links - for the Catalogue and Resource objects" ;
13
+ skos:inScheme cs: ;
14
+ skos:prefLabel "Complete Catalogue and Resource Labels" ;
15
+ .
16
+
17
+ :CatalogueAndResourceModel
18
+ a skos:Concept ;
19
+ skos:definition "The default model for the container and the content. Must be a set of SHACL Shapes" ;
20
+ skos:inScheme cs: ;
21
+ skos:prefLabel "Catalogue & Resource Model" ;
22
+ .
23
+
24
+ :CatalogueData
25
+ a skos:Concept ;
26
+ skos:definition "Data for the container, usually a Catalogue, including the identity of it and each item fo content" ;
27
+ skos:inScheme cs: ;
28
+ skos:prefLabel "Catalogue Data" ;
29
+ .
30
+
31
+ :CatalogueModel
32
+ a skos:Concept ;
33
+ skos:definition "The default model for the container. Must be a set of SHACL Shapes" ;
34
+ skos:inScheme cs: ;
35
+ skos:prefLabel "Catalogue Model" ;
36
+ .
37
+
38
+ :ResourceData
39
+ a skos:Concept ;
40
+ skos:definition "Data for the content of the container" ;
41
+ skos:inScheme cs: ;
42
+ skos:prefLabel "Resource Data" ;
43
+ .
44
+
45
+ :ResourceModel
46
+ a skos:Concept ;
47
+ skos:definition "The default model for the content. Must be a set of SHACL Shapes" ;
48
+ skos:inScheme cs: ;
49
+ skos:prefLabel "Resource Model" ;
50
+ .
51
+
52
+ :IncompleteCatalogueAndResourceLabels
53
+ a skos:Concept ;
54
+ skos:definition "Some of the labels - possibly including names, descriptions & seeAlso links - for the Catalogue and Resource objects" ;
55
+ skos:inScheme cs: ;
56
+ skos:prefLabel "Incomplete Catalogue and Resource Labels" ;
57
+ .
58
+
59
+ <https://kurrawong.ai>
60
+ a schema:Organization ;
61
+ schema:name "KurrawongAI" ;
62
+ schema:url "https://kurrawong.ai"^^xsd:anyURI ;
63
+ .
64
+
65
+ :1.0.0
66
+ rdfs:label "1.0.0" ;
67
+ .
68
+
69
+ cs:
70
+ a skos:ConceptScheme ;
71
+ owl:versionIRI :1.0.0 ;
72
+ owl:versionInfo "1.0.0: First public version" ;
73
+ skos:definition "This vocabulary is a test vocabulary showing the use of images within Concept definitions"@en ;
74
+ skos:hasTopConcept
75
+ :CompleteCatalogueAndResourceLabels ,
76
+ :CatalogueAndResourceModel ,
77
+ :CatalogueData ,
78
+ :CatalogueModel ,
79
+ :ResourceData ,
80
+ :IncompleteCatalogueAndResourceLabels ;
81
+ skos:historyNote "Create in 2024 for Prez data loading" ;
82
+ skos:prefLabel "Manifest Resource Roles Vocabulary" ;
83
+ schema:creator <https://kurrawong.ai> ;
84
+ schema:dateCreated "2024-11-29"^^xsd:date ;
85
+ schema:dateModified "2024-12-17"^^xsd:date ;
86
+ schema:publisher <https://kurrawong.ai> ;
87
+ .
@@ -0,0 +1,39 @@
1
+ from pathlib import Path
2
+ from typing import List
3
+ from collections.abc import Generator
4
+ from rdflib import Literal, URIRef, Graph, Dataset
5
+ from rdflib.namespace import OWL, RDF, RDFS, SKOS
6
+
7
+
8
+ def get_files_from_artifact(manifest: Path, artifact: Literal) -> List[Path] | Generator[Path]:
9
+ """Returns an iterable (list or generator) of Path objects for files within an artifact literal.
10
+
11
+ This function will correctly interpret artifacts such as 'file.ttl', '*.ttl', '**/*.trig' etc."""
12
+ if not "*" in str(artifact):
13
+ return [manifest.parent / Path(str(artifact))]
14
+ else:
15
+ artifact_str = str(artifact)
16
+ glob_marker_location = artifact_str.find("*")
17
+ glob_parts = [artifact_str[:glob_marker_location], artifact_str[glob_marker_location:]]
18
+
19
+ return Path(manifest.parent / Path(glob_parts[0])).glob(glob_parts[1])
20
+
21
+
22
+ def get_identifier_from_file(file: Path) -> List[URIRef]:
23
+ """Returns a list if RDFLib graph identifier (URIRefs) from a triples or quads file
24
+ for all owl:Ontology and skos:ConceptScheme objects"""
25
+ if file.name.endswith(".ttl"):
26
+ g = Graph().parse(file)
27
+ return [
28
+ g.value(predicate=RDF.type, object=SKOS.ConceptScheme) or
29
+ g.value(predicate=RDF.type, object=OWL.Ontology)
30
+ ]
31
+ elif file.name.endswith(".trig"):
32
+ gs = []
33
+ d = Dataset()
34
+ d.parse(file, format="trig")
35
+ for g in d.graphs():
36
+ gs.append(g.identifier)
37
+ return gs
38
+ else:
39
+ return []
@@ -0,0 +1,104 @@
1
+ """Validate a Prez Manifest file.
2
+
3
+ This script performs both SHACL validation to ensure the Manifest is valid according to the Prez Manifest
4
+ specification (see https://prez.dev/manifest/) and checks that all the resources indicated by the Manifest
5
+ - whether local files/folders or remote resources on the Internet - are reachable.
6
+
7
+ ~$ python validate.py {MANIFEST_FILE_PATH}"""
8
+
9
+ import argparse
10
+ import sys
11
+ from pathlib import Path
12
+
13
+ import httpx
14
+ from kurra.utils import load_graph
15
+ from pyshacl import validate as shacl_validate
16
+ from rdflib import Graph
17
+ from rdflib.namespace import PROF
18
+
19
+ try:
20
+ from prezmanifest import __version__
21
+ except ImportError:
22
+ import sys
23
+
24
+ sys.path.append(str(Path(__file__).parent.parent.resolve()))
25
+ from prezmanifest import __version__
26
+
27
+
28
+ def validate(manifest: Path) -> bool:
29
+ ME = Path(__file__)
30
+ MANIFEST_ROOT_DIR = manifest.parent
31
+
32
+ # SHACL validation
33
+ manifest_graph = load_graph(manifest)
34
+ mrr_vocab_graph = load_graph(ME.parent / "mrr.ttl")
35
+ data_graph = manifest_graph + mrr_vocab_graph
36
+ shacl_graph = load_graph(ME.parent / "validator.ttl")
37
+ valid, v_graph, v_text = shacl_validate(data_graph, shacl_graph=shacl_graph)
38
+
39
+ if not valid:
40
+ raise ValueError(f"SHACL invalid:\n\n{v_text}")
41
+
42
+ # Content link validation
43
+ for s, o in manifest_graph.subject_objects(PROF.hasResource):
44
+ for artifact in manifest_graph.objects(o, PROF.hasArtifact):
45
+ artifact_str = str(artifact)
46
+ if "http" in artifact_str:
47
+ r = httpx.get(artifact_str)
48
+ if 200 <= r.status_code < 400:
49
+ pass
50
+ else:
51
+ raise ValueError(
52
+ f"Remote content link non-resolving: {artifact_str}"
53
+ )
54
+ elif "*" in artifact_str:
55
+ glob_parts = artifact_str.split("*")
56
+ dir = Path(manifest.parent / Path(glob_parts[0]))
57
+ if not Path(dir).is_dir():
58
+ raise ValueError(
59
+ f"The content link {artifact_str} is not a directory"
60
+ )
61
+ else:
62
+ # It must be a local
63
+ if not (MANIFEST_ROOT_DIR / artifact_str).is_file():
64
+ print(
65
+ f"Content link {MANIFEST_ROOT_DIR / artifact_str} is invalid - not a file"
66
+ )
67
+
68
+ return manifest_graph
69
+
70
+
71
+ def setup_cli_parser(args=None):
72
+
73
+ parser = argparse.ArgumentParser()
74
+
75
+ parser.add_argument(
76
+ "-v",
77
+ "--version",
78
+ action="version",
79
+ version="{version}".format(version=__version__),
80
+ )
81
+
82
+ parser.add_argument(
83
+ "manifest",
84
+ help="A Manifest file to process",
85
+ type=Path,
86
+ )
87
+
88
+ return parser.parse_args(args)
89
+
90
+
91
+ def cli(args=None):
92
+ if args is None:
93
+ args = sys.argv[1:]
94
+
95
+ args = setup_cli_parser(args)
96
+
97
+ validate(args.manifest)
98
+
99
+
100
+ if __name__ == "__main__":
101
+ retval = cli(sys.argv[1:])
102
+ if retval is not None:
103
+ sys.exit(retval)
104
+
@@ -0,0 +1,126 @@
1
+ PREFIX owl: <http://www.w3.org/2002/07/owl#>
2
+ PREFIX prez: <https://prez.dev/>
3
+ PREFIX prof: <http://www.w3.org/ns/dx/prof/>
4
+ PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
5
+ PREFIX schema: <https://schema.org/>
6
+ PREFIX sh: <http://www.w3.org/ns/shacl#>
7
+ PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
8
+ PREFIX val: <https://prez.dev/manifest-validator/>
9
+ PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
10
+
11
+ <https://prez.dev/manifest-validator>
12
+ a owl:Ontology ;
13
+ owl:versionIRI val:1.0.0 ;
14
+ owl:versionInfo "1.0.0: First public version" ;
15
+ schema:creator <https://orcid.org/0000-0002-8742-7730> ;
16
+ schema:dateCreated "2024-11-29"^^xsd:date ;
17
+ schema:dateModified "2024-11-29"^^xsd:date ;
18
+ schema:definition "The SHACL Shapes Validator for the Prez Ontology Manifest Model"@en ;
19
+ schema:name "Prez Manifest Validator" ;
20
+ schema:publisher <https://kurrawong.ai> ;
21
+ .
22
+
23
+ val:ShapeN01
24
+ a sh:NodeShape ;
25
+ sh:property val:ShapeP01 ;
26
+ sh:targetClass prez:Manifest ;
27
+ .
28
+
29
+ val:ShapeN02
30
+ a sh:NodeShape ;
31
+ sh:property
32
+ val:ShapeP02 ,
33
+ val:ShapeP03 ,
34
+ val:ShapeP04 ,
35
+ val:ShapeP05 ;
36
+ sh:targetObjectsOf prof:hasResource ;
37
+ .
38
+
39
+ val:ShapeP01
40
+ a sh:PropertyShape ;
41
+ sh:nodeKind sh:BlankNodeOrIRI ;
42
+ sh:message "ShapeP01: You must supply at least one value for prof:hasResource for a prez:Manifest and it must be either a Blank Node or an IRI" ;
43
+ sh:minCount 1 ;
44
+ sh:path prof:hasResource ;
45
+ .
46
+
47
+ val:ShapeP02
48
+ a sh:PropertyShape ;
49
+ sh:maxCount 1 ;
50
+ sh:message "ShapeP02: You must supply exactly one value for prof:hasArtifact for a prez:Manifest's Resource Descriptors and it must be a string that can be interpreted as a file path, either relative to the manifest file, or an absolute system path or a URL" ;
51
+ sh:minCount 1 ;
52
+ sh:nodeKind sh:Literal ;
53
+ sh:path prof:hasArtifact ;
54
+ .
55
+
56
+ val:ShapeP03
57
+ a sh:PropertyShape ;
58
+ sh:maxCount 1 ;
59
+ sh:message "ShapeP03: You must supply exactly one value for prof:hasRole for a prez:Manifest's Resource Descriptors with the value selected from the Prez Manifest Resource Roles Vocabulary" ;
60
+ sh:minCount 1 ;
61
+ sh:nodeKind sh:IRI ;
62
+ sh:path prof:hasRole ;
63
+ .
64
+
65
+ val:ShapeP04
66
+ a sh:PropertyShape ;
67
+ sh:maxCount 1 ;
68
+ sh:message "ShapeP04: If you supply a schema:description for a prez:Manifest's Resource Descriptors, it must be either a plain string or a langString literal" ;
69
+ sh:or (
70
+
71
+ [
72
+ sh:datatype xsd:string ;
73
+ ]
74
+
75
+ [
76
+ sh:datatype rdf:langString ;
77
+ ]
78
+ ) ;
79
+ sh:path schema:description ;
80
+ .
81
+
82
+ val:ShapeP05
83
+ a sh:PropertyShape ;
84
+ sh:maxCount 1 ;
85
+ sh:message "ShapeP05: If you supply a schema:name for a prez:Manifest's Resource Descriptors, it must be either a plain string or a langString literal" ;
86
+ sh:or (
87
+
88
+ [
89
+ sh:datatype xsd:string ;
90
+ ]
91
+
92
+ [
93
+ sh:datatype rdf:langString ;
94
+ ]
95
+ ) ;
96
+ sh:path schema:name ;
97
+ .
98
+
99
+ val:ShapeN03
100
+ a sh:NodeShape ;
101
+ sh:targetObjectsOf prof:hasRole ;
102
+ sh:property [
103
+ a sh:PropertyShape ;
104
+ sh:minCount 1 ;
105
+ sh:maxCount 1 ;
106
+ sh:path skos:inScheme ;
107
+ ] ;
108
+ .
109
+
110
+ val:1.0.0
111
+ schema:name "1.0.0" ;
112
+ .
113
+
114
+ <https://orcid.org/0000-0002-8742-7730>
115
+ a schema:Person ;
116
+ schema:email "nick@kurrawong.ai"^^xsd:anyURI ;
117
+ schema:identifier "https://orcid.org/0000-0002-8742-7730"^^xsd:anyURI ;
118
+ schema:memberOf <https://kurrawong.ai> ;
119
+ schema:name "Nicholas J. Car" ;
120
+ .
121
+
122
+ <https://kurrawong.ai>
123
+ a schema:Organization ;
124
+ schema:name "KurrawongAI" ;
125
+ schema:url "https://kurrawong.ai"^^xsd:anyURI ;
126
+ .
@@ -0,0 +1,24 @@
1
+ [tool.poetry]
2
+ name = "prezmanifest"
3
+ version = "0.2.2"
4
+ description = "A Python package that provides a series of functions to work with Prez Manifests."
5
+ authors = ["Nicholas Car <nick@kurrawong.ai>"]
6
+ license = "BSD-3-Clause"
7
+ readme = "README.adoc"
8
+
9
+ [tool.poetry.dependencies]
10
+ python = ">=3.12,<4.0"
11
+ pyshacl = "^0.29.0"
12
+ kurra = "^0.6.1"
13
+ labelify = "^0.3.8"
14
+
15
+ [tool.poetry.dev-dependencies]
16
+
17
+ [tool.poetry.group.dev.dependencies]
18
+ pytest = "^8.3.4"
19
+ black = "^24.10.0"
20
+ testcontainers = "^4.9.0"
21
+
22
+ [build-system]
23
+ requires = ["poetry-core>=1.0.0"]
24
+ build-backend = "poetry.core.masonry.api"