soft7 0.2.2__tar.gz → 0.3.0__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.
- {soft7-0.2.2 → soft7-0.3.0}/.github/workflows/ci_tests.yml +1 -1
- soft7-0.3.0/.markdownlint.yaml +17 -0
- {soft7-0.2.2 → soft7-0.3.0}/CHANGELOG.md +40 -6
- {soft7-0.2.2 → soft7-0.3.0}/PKG-INFO +4 -3
- {soft7-0.2.2 → soft7-0.3.0}/pyproject.toml +3 -2
- {soft7-0.2.2 → soft7-0.3.0}/s7/__init__.py +1 -1
- {soft7-0.2.2 → soft7-0.3.0}/s7/factories/datasource_factory.py +5 -3
- {soft7-0.2.2 → soft7-0.3.0}/s7/factories/entity_factory.py +3 -2
- soft7-0.3.0/s7/graph/README.md +88 -0
- {soft7-0.2.2 → soft7-0.3.0/s7}/graph/utils.py +7 -4
- {soft7-0.2.2 → soft7-0.3.0}/s7/pydantic_models/soft7_entity.py +15 -12
- {soft7-0.2.2 → soft7-0.3.0}/tests/factories/test_datasource_factory.py +2 -2
- {soft7-0.2.2 → soft7-0.3.0}/tests/graph/test_utils.py +17 -10
- {soft7-0.2.2 → soft7-0.3.0}/tests/oteapi_plugin/test_soft7_function.py +25 -16
- {soft7-0.2.2 → soft7-0.3.0}/tests/pydantic_models/test_datasource.py +56 -56
- {soft7-0.2.2 → soft7-0.3.0}/tests/pydantic_models/test_soft7_entity.py +12 -12
- soft7-0.2.2/graph/README.md +0 -86
- {soft7-0.2.2 → soft7-0.3.0}/.github/dependabot.yml +0 -0
- {soft7-0.2.2 → soft7-0.3.0}/.github/workflows/cd_docs.yml +0 -0
- {soft7-0.2.2 → soft7-0.3.0}/.github/workflows/cd_publish.yml +0 -0
- {soft7-0.2.2 → soft7-0.3.0}/.github/workflows/ci_bot_pr_changes.yml +0 -0
- {soft7-0.2.2 → soft7-0.3.0}/.gitignore +0 -0
- {soft7-0.2.2 → soft7-0.3.0}/.pre-commit-config.yaml +0 -0
- {soft7-0.2.2 → soft7-0.3.0}/LICENSE +0 -0
- {soft7-0.2.2 → soft7-0.3.0}/README.md +0 -0
- {soft7-0.2.2 → soft7-0.3.0}/agraph.cfg +0 -0
- {soft7-0.2.2 → soft7-0.3.0}/docker-compose.yml +0 -0
- {soft7-0.2.2 → soft7-0.3.0}/docs/conf.py +0 -0
- {soft7-0.2.2 → soft7-0.3.0}/docs/examples/advanced.ipynb +0 -0
- {soft7-0.2.2 → soft7-0.3.0}/docs/examples/basic.ipynb +0 -0
- {soft7-0.2.2 → soft7-0.3.0}/docs/examples/datasource.ipynb +0 -0
- {soft7-0.2.2 → soft7-0.3.0}/docs/examples/index.md +0 -0
- {soft7-0.2.2 → soft7-0.3.0}/docs/index.md +0 -0
- {soft7-0.2.2 → soft7-0.3.0}/ontology/entity_soft7.ttl +0 -0
- {soft7-0.2.2 → soft7-0.3.0}/s7/exceptions.py +0 -0
- {soft7-0.2.2 → soft7-0.3.0}/s7/factories/__init__.py +0 -0
- {soft7-0.2.2 → soft7-0.3.0}/s7/factories/generated_classes.py +0 -0
- {soft7-0.2.2 → soft7-0.3.0/s7}/graph/__init__.py +0 -0
- {soft7-0.2.2 → soft7-0.3.0}/s7/oteapi_plugin/__init__.py +0 -0
- {soft7-0.2.2 → soft7-0.3.0}/s7/oteapi_plugin/models.py +0 -0
- {soft7-0.2.2 → soft7-0.3.0}/s7/oteapi_plugin/soft7_function.py +0 -0
- {soft7-0.2.2 → soft7-0.3.0}/s7/oteapi_plugin/yaml_parser.py +0 -0
- {soft7-0.2.2 → soft7-0.3.0}/s7/pydantic_models/__init__.py +0 -0
- {soft7-0.2.2 → soft7-0.3.0}/s7/pydantic_models/_utils.py +0 -0
- {soft7-0.2.2 → soft7-0.3.0}/s7/pydantic_models/datasource.py +0 -0
- {soft7-0.2.2 → soft7-0.3.0}/s7/pydantic_models/oteapi.py +0 -0
- {soft7-0.2.2 → soft7-0.3.0}/s7/pydantic_models/soft7_instance.py +0 -0
- {soft7-0.2.2 → soft7-0.3.0}/tests/conftest.py +0 -0
- {soft7-0.2.2 → soft7-0.3.0}/tests/factories/conftest.py +0 -0
- {soft7-0.2.2 → soft7-0.3.0}/tests/pydantic_models/conftest.py +0 -0
- {soft7-0.2.2 → soft7-0.3.0}/tests/static/onto-ns_OPTIMADEStructure.json +0 -0
- {soft7-0.2.2 → soft7-0.3.0}/tests/static/onto-ns_OPTIMADEStructureAssembly.json +0 -0
- {soft7-0.2.2 → soft7-0.3.0}/tests/static/onto-ns_OPTIMADEStructureAttributes.json +0 -0
- {soft7-0.2.2 → soft7-0.3.0}/tests/static/onto-ns_OPTIMADEStructureSpecies.json +0 -0
- {soft7-0.2.2 → soft7-0.3.0}/tests/static/optimade.materialsproject_mp-1228448.json +0 -0
- {soft7-0.2.2 → soft7-0.3.0}/tests/static/soft_datasource_content.yaml +0 -0
- {soft7-0.2.2 → soft7-0.3.0}/tests/static/soft_datasource_entity.yaml +0 -0
- {soft7-0.2.2 → soft7-0.3.0}/tests/static/soft_datasource_entity_test_data.yaml +0 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Default state for all rules
|
|
2
|
+
default: true
|
|
3
|
+
|
|
4
|
+
# MD013/line-length - Line length
|
|
5
|
+
MD013:
|
|
6
|
+
# Number of characters
|
|
7
|
+
# Set very long to support 1-sentence-per-line style.
|
|
8
|
+
line_length: 999
|
|
9
|
+
# Number of characters for headings (match that of black's default)
|
|
10
|
+
heading_line_length: 88
|
|
11
|
+
# Number of characters for code blocks (match that of black's default)
|
|
12
|
+
code_block_line_length: 88
|
|
13
|
+
|
|
14
|
+
# MD024/no-duplicate-heading - Multiple headings with the same content
|
|
15
|
+
MD024:
|
|
16
|
+
# Only check sibling headings
|
|
17
|
+
siblings_only: true
|
|
@@ -2,15 +2,49 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased](https://github.com/SINTEF/soft7/tree/HEAD)
|
|
4
4
|
|
|
5
|
-
[Full Changelog](https://github.com/SINTEF/soft7/compare/v0.
|
|
5
|
+
[Full Changelog](https://github.com/SINTEF/soft7/compare/v0.3.0...HEAD)
|
|
6
6
|
|
|
7
|
-
#
|
|
7
|
+
# New `graph` module
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
Since we use the pydantic version specified by `oteapi-core`, we make sure to at least use v0.7.0.dev6, since this ensure v2.10.0 and v2.10.1 is not installed.
|
|
11
|
-
When v2.10.2 comes out, it can be tested on `oteapi-core` before a new release is done.
|
|
9
|
+
Perform RDF graph operations, specifically meant for usage with SOFT7 and semantic interoperability utilizing the `s7.graph` module.
|
|
12
10
|
|
|
13
|
-
|
|
11
|
+
The main underlying technology is [RDFLib](https://rdflib.readthedocs.io/) and [SPARQLWrapper](https://sparqlwrapper.readthedocs.io/).
|
|
12
|
+
|
|
13
|
+
## Support Python 3.13
|
|
14
|
+
|
|
15
|
+
CI tests and official package metadata support for Python 3.13 has been added.
|
|
16
|
+
|
|
17
|
+
## Support Pydantic v2.10
|
|
18
|
+
|
|
19
|
+
There were several changes to the networking models in pydantic v2.10 - this update remedies most of these changes.
|
|
20
|
+
|
|
21
|
+
**Implemented enhancements:**
|
|
22
|
+
|
|
23
|
+
- Test support for Python 3.13 [\#71](https://github.com/SINTEF/soft7/issues/71)
|
|
24
|
+
|
|
25
|
+
## [v0.3.0](https://github.com/SINTEF/soft7/tree/v0.3.0) (2024-12-02)
|
|
26
|
+
|
|
27
|
+
[Full Changelog](https://github.com/SINTEF/soft7/compare/v0.2.2...v0.3.0)
|
|
28
|
+
|
|
29
|
+
# New `graph` module
|
|
30
|
+
|
|
31
|
+
Perform RDF graph operations, specifically meant for usage with SOFT7 and semantic interoperability utilizing the `s7.graph` module.
|
|
32
|
+
|
|
33
|
+
The main underlying technology is [RDFLib](https://rdflib.readthedocs.io/) and [SPARQLWrapper](https://sparqlwrapper.readthedocs.io/).
|
|
34
|
+
|
|
35
|
+
## Support Python 3.13
|
|
36
|
+
|
|
37
|
+
CI tests and official package metadata support for Python 3.13 has been added.
|
|
38
|
+
|
|
39
|
+
## Support Pydantic v2.10
|
|
40
|
+
|
|
41
|
+
There were several changes to the networking models in pydantic v2.10 - this update remedies most of these changes.
|
|
42
|
+
|
|
43
|
+
**Merged pull requests:**
|
|
44
|
+
|
|
45
|
+
- Bump the dependencies group with 2 updates [\#87](https://github.com/SINTEF/soft7/pull/87) ([dependabot[bot]](https://github.com/apps/dependabot))
|
|
46
|
+
- Move in graphs to s7 package [\#86](https://github.com/SINTEF/soft7/pull/86) ([CasperWA](https://github.com/CasperWA))
|
|
47
|
+
- Test with Python 3.13 [\#85](https://github.com/SINTEF/soft7/pull/85) ([CasperWA](https://github.com/CasperWA))
|
|
14
48
|
|
|
15
49
|
## [v0.2.2](https://github.com/SINTEF/soft7/tree/v0.2.2) (2024-11-25)
|
|
16
50
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: soft7
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: SOFT7 semantic interoperability framework.
|
|
5
5
|
Keywords: interoperability,semantic
|
|
6
6
|
Author-email: Thomas Fjæstad Hagelien <thomas.f.hagelien@sintef.no>, Casper Welzel Andersen <casper.w.andersen@sintef.no>, Treesa Rose Joseph <treesa.joseph@sintef.no>
|
|
@@ -13,9 +13,10 @@ Classifier: Programming Language :: Python :: 3.9
|
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.10
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.11
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
17
|
Classifier: Natural Language :: English
|
|
17
18
|
Classifier: Operating System :: OS Independent
|
|
18
|
-
Requires-Dist: httpx
|
|
19
|
+
Requires-Dist: httpx>=0.27.2,<1
|
|
19
20
|
Requires-Dist: oteapi-core>=0.7.0.dev6
|
|
20
21
|
Requires-Dist: otelib~=0.5.0.dev1
|
|
21
22
|
Requires-Dist: pyyaml~=6.0
|
|
@@ -30,7 +31,7 @@ Requires-Dist: rdflib~=7.1 ; extra == "graph"
|
|
|
30
31
|
Requires-Dist: sparqlwrapper~=2.0 ; extra == "graph"
|
|
31
32
|
Requires-Dist: pytest~=8.3 ; extra == "testing"
|
|
32
33
|
Requires-Dist: pytest-cov~=6.0 ; extra == "testing"
|
|
33
|
-
Requires-Dist: pytest-httpx~=0.
|
|
34
|
+
Requires-Dist: pytest-httpx~=0.35.0 ; extra == "testing"
|
|
34
35
|
Requires-Dist: requests-mock~=1.12 ; extra == "testing"
|
|
35
36
|
Requires-Dist: soft7[graph] ; extra == "testing"
|
|
36
37
|
Project-URL: Changelog, https://github.com/SINTEF/soft7/blob/main/CHANGELOG.md
|
|
@@ -21,6 +21,7 @@ classifiers = [
|
|
|
21
21
|
"Programming Language :: Python :: 3.10",
|
|
22
22
|
"Programming Language :: Python :: 3.11",
|
|
23
23
|
"Programming Language :: Python :: 3.12",
|
|
24
|
+
"Programming Language :: Python :: 3.13",
|
|
24
25
|
"Natural Language :: English",
|
|
25
26
|
"Operating System :: OS Independent",
|
|
26
27
|
]
|
|
@@ -29,7 +30,7 @@ requires-python = "~=3.9"
|
|
|
29
30
|
dynamic = ["version"]
|
|
30
31
|
|
|
31
32
|
dependencies = [
|
|
32
|
-
"httpx
|
|
33
|
+
"httpx>=0.27.2,<1",
|
|
33
34
|
"oteapi-core>=0.7.0.dev6",
|
|
34
35
|
"otelib~=0.5.0.dev1",
|
|
35
36
|
"pyyaml~=6.0",
|
|
@@ -53,7 +54,7 @@ graph = [
|
|
|
53
54
|
testing = [
|
|
54
55
|
"pytest~=8.3",
|
|
55
56
|
"pytest-cov~=6.0",
|
|
56
|
-
"pytest-httpx~=0.
|
|
57
|
+
"pytest-httpx~=0.35.0",
|
|
57
58
|
"requests-mock~=1.12",
|
|
58
59
|
"soft7[graph]",
|
|
59
60
|
]
|
|
@@ -7,7 +7,7 @@ import logging
|
|
|
7
7
|
from .pydantic_models.soft7_entity import SOFT7Entity, SOFT7EntityPropertyType
|
|
8
8
|
from .pydantic_models.soft7_entity import parse_input_entity as get_entity
|
|
9
9
|
|
|
10
|
-
__version__ = "0.
|
|
10
|
+
__version__ = "0.3.0"
|
|
11
11
|
|
|
12
12
|
__all__ = ("SOFT7Entity", "SOFT7EntityPropertyType", "__version__", "get_entity")
|
|
13
13
|
|
|
@@ -19,7 +19,7 @@ from pathlib import Path
|
|
|
19
19
|
from typing import TYPE_CHECKING, Optional
|
|
20
20
|
|
|
21
21
|
from otelib import OTEClient
|
|
22
|
-
from pydantic import AnyUrl, Field, create_model
|
|
22
|
+
from pydantic import AnyUrl, Field, FileUrl, create_model
|
|
23
23
|
|
|
24
24
|
from s7.pydantic_models.datasource import (
|
|
25
25
|
DataSourceDimensions,
|
|
@@ -28,6 +28,7 @@ from s7.pydantic_models.datasource import (
|
|
|
28
28
|
)
|
|
29
29
|
from s7.pydantic_models.soft7_entity import (
|
|
30
30
|
SOFT7Entity,
|
|
31
|
+
SOFT7IdentityURIType,
|
|
31
32
|
parse_identity,
|
|
32
33
|
parse_input_entity,
|
|
33
34
|
)
|
|
@@ -196,12 +197,13 @@ def _get_data(
|
|
|
196
197
|
|
|
197
198
|
|
|
198
199
|
def create_datasource(
|
|
199
|
-
entity: SOFT7Entity | dict[str, Any] | Path |
|
|
200
|
+
entity: SOFT7Entity | dict[str, Any] | Path | SOFT7IdentityURIType | str,
|
|
200
201
|
configs: (
|
|
201
202
|
GetDataConfigDict
|
|
202
|
-
| dict[str, GenericConfig | dict[str, Any] | Path | AnyUrl | str]
|
|
203
|
+
| dict[str, GenericConfig | dict[str, Any] | Path | AnyUrl | FileUrl | str]
|
|
203
204
|
| Path
|
|
204
205
|
| AnyUrl
|
|
206
|
+
| FileUrl
|
|
205
207
|
| str
|
|
206
208
|
),
|
|
207
209
|
oteapi_url: str | None = None,
|
|
@@ -6,10 +6,11 @@ import logging
|
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
from typing import TYPE_CHECKING, Optional
|
|
8
8
|
|
|
9
|
-
from pydantic import
|
|
9
|
+
from pydantic import ConfigDict, Field, create_model
|
|
10
10
|
|
|
11
11
|
from s7.pydantic_models.soft7_entity import (
|
|
12
12
|
SOFT7Entity,
|
|
13
|
+
SOFT7IdentityURIType,
|
|
13
14
|
parse_identity,
|
|
14
15
|
parse_input_entity,
|
|
15
16
|
)
|
|
@@ -33,7 +34,7 @@ LOGGER = logging.getLogger(__name__)
|
|
|
33
34
|
|
|
34
35
|
|
|
35
36
|
def create_entity(
|
|
36
|
-
entity: Union[SOFT7Entity, dict[str, Any], Path,
|
|
37
|
+
entity: Union[SOFT7Entity, dict[str, Any], Path, SOFT7IdentityURIType, str],
|
|
37
38
|
) -> type[SOFT7EntityInstance]:
|
|
38
39
|
"""Create and return a SOFT7 entity as a pydantic model.
|
|
39
40
|
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# RDF Graph Operations Library
|
|
2
|
+
|
|
3
|
+
This library provides tools for interacting with RDF graphs via a SPARQL endpoint.
|
|
4
|
+
It includes functions to find the `Lowest Common Ancestor` (LCA) of a set of classes and to populate an RDF graph with triples related to a given parent node.
|
|
5
|
+
|
|
6
|
+
## Installation
|
|
7
|
+
|
|
8
|
+
Ensure you have Python installed on your system.
|
|
9
|
+
|
|
10
|
+
Install the SOFT7 package with the `graph` extra:
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
pip install soft7[graph}
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Functions
|
|
17
|
+
|
|
18
|
+
### 1. find_parent_node
|
|
19
|
+
|
|
20
|
+
This function queries a SPARQL endpoint to find the Lowest Common Ancestor (LCA) of a list of class URIs within a specified graph URI.
|
|
21
|
+
|
|
22
|
+
#### Parameters
|
|
23
|
+
|
|
24
|
+
* **sparql (`SPARQLWrapper`)**: An instance of SPARQLWrapper configured for the target SPARQL service.
|
|
25
|
+
* **class_names (`list[str]`)**: The class URIs to find a common parent for.
|
|
26
|
+
* **graph_uri (`str`)**: The URI of the graph in which to perform the query.
|
|
27
|
+
|
|
28
|
+
#### Returns
|
|
29
|
+
|
|
30
|
+
* **str | None**: The URI of the common parent node if one exists, otherwise `None`.
|
|
31
|
+
|
|
32
|
+
#### Raises
|
|
33
|
+
|
|
34
|
+
* **SPARQLWrapperException**: If there is an error in executing or processing the SPARQL query.
|
|
35
|
+
* **TemplateError**: If there is an error in rendering the SPARQL query using Jinja2 templates.
|
|
36
|
+
|
|
37
|
+
#### Usage Example
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
...
|
|
41
|
+
sparqlWrapper = SPARQLWrapper(sparql_endpoint)
|
|
42
|
+
sparqlWrapper.setHTTPAuth(BASIC)
|
|
43
|
+
sparqlWrapper.setCredentials(username, password)
|
|
44
|
+
|
|
45
|
+
parent_node = find_parent_node(
|
|
46
|
+
sparql=sparqlWrapper,
|
|
47
|
+
class_names=["http://example.com/class1", "http://example.com/class2"],
|
|
48
|
+
graph_uri="http://example.com/graph"
|
|
49
|
+
)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### 2. fetch_and_populate_graph
|
|
53
|
+
|
|
54
|
+
Fetches and populates an RDF graph with triples related to a specified parent node.
|
|
55
|
+
|
|
56
|
+
#### Parameters
|
|
57
|
+
|
|
58
|
+
* **sparql (`SPARQLWrapper`)**: An instance of SPARQLWrapper configured for the target SPARQL service.
|
|
59
|
+
* **graph_uri (`str`)**: The URI of the graph from which triples will be fetched.
|
|
60
|
+
* **parent_node (`str`)**: The URI of the parent node to base the triple fetching on.
|
|
61
|
+
* **graph (`rdflib.Graph`, `optional`)**: An instance of an RDFlib graph to populate with fetched triples.
|
|
62
|
+
If `None`, a new empty graph is created. Defaults to `None`.
|
|
63
|
+
|
|
64
|
+
#### Returns
|
|
65
|
+
|
|
66
|
+
* **rdflib.Graph**: The graph populated with the fetched triples.
|
|
67
|
+
|
|
68
|
+
#### Raises
|
|
69
|
+
|
|
70
|
+
* **SPARQLWrapperException**: If there is an error in executing or processing the SPARQL query.
|
|
71
|
+
* **RDFLibException**: If there is an error in adding fetched triples to the RDF graph.
|
|
72
|
+
|
|
73
|
+
#### Usage Example
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
...
|
|
77
|
+
|
|
78
|
+
rdf_graph = fetch_and_populate_graph(
|
|
79
|
+
sparql=sparqlWrapper,
|
|
80
|
+
graph_uri="http://example.com/graph",
|
|
81
|
+
parent_node="http://example.com/parentClass"
|
|
82
|
+
)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Error Handling
|
|
86
|
+
|
|
87
|
+
Both functions handle errors internally and will return `None` if an error occurs during execution.
|
|
88
|
+
Errors during SPARQL querying or RDF graph manipulation are logged to the console.
|
|
@@ -10,6 +10,7 @@ used to handle SPARQL queries and RDFlib for graph operations.
|
|
|
10
10
|
|
|
11
11
|
from __future__ import annotations
|
|
12
12
|
|
|
13
|
+
import logging
|
|
13
14
|
from typing import Optional
|
|
14
15
|
|
|
15
16
|
import rdflib
|
|
@@ -18,6 +19,8 @@ from rdflib.exceptions import Error as RDFLibException
|
|
|
18
19
|
from SPARQLWrapper import JSON, SPARQLWrapper
|
|
19
20
|
from SPARQLWrapper.SPARQLExceptions import SPARQLWrapperException
|
|
20
21
|
|
|
22
|
+
LOGGER = logging.getLogger(__name__)
|
|
23
|
+
|
|
21
24
|
|
|
22
25
|
def find_parent_node(
|
|
23
26
|
sparql: SPARQLWrapper,
|
|
@@ -66,7 +69,7 @@ def find_parent_node(
|
|
|
66
69
|
|
|
67
70
|
template = Template(template_str)
|
|
68
71
|
query = template.module.sparql_query(class_names, graph_uri)
|
|
69
|
-
|
|
72
|
+
LOGGER.debug("Query: %s", query)
|
|
70
73
|
sparql.setReturnFormat(JSON)
|
|
71
74
|
sparql.setQuery(query)
|
|
72
75
|
|
|
@@ -90,7 +93,7 @@ def find_parent_node(
|
|
|
90
93
|
f"Jinja2 template error: {template_error}"
|
|
91
94
|
) from template_error
|
|
92
95
|
|
|
93
|
-
|
|
96
|
+
LOGGER.info("Could not find a common parent node.")
|
|
94
97
|
return None
|
|
95
98
|
|
|
96
99
|
|
|
@@ -157,7 +160,7 @@ def fetch_and_populate_graph(
|
|
|
157
160
|
}}
|
|
158
161
|
}}
|
|
159
162
|
"""
|
|
160
|
-
|
|
163
|
+
LOGGER.debug("Query: %s", query)
|
|
161
164
|
sparql.setQuery(query)
|
|
162
165
|
|
|
163
166
|
results = sparql.query().convert()
|
|
@@ -170,7 +173,7 @@ def fetch_and_populate_graph(
|
|
|
170
173
|
)
|
|
171
174
|
)
|
|
172
175
|
|
|
173
|
-
|
|
176
|
+
LOGGER.info("Graph populated with fetched triples.")
|
|
174
177
|
|
|
175
178
|
except SPARQLWrapperException as wrapper_error:
|
|
176
179
|
raise RuntimeError(
|
|
@@ -11,6 +11,7 @@ from typing import (
|
|
|
11
11
|
Any,
|
|
12
12
|
Optional,
|
|
13
13
|
Union,
|
|
14
|
+
get_args,
|
|
14
15
|
)
|
|
15
16
|
|
|
16
17
|
if sys.version_info >= (3, 10):
|
|
@@ -20,16 +21,17 @@ else:
|
|
|
20
21
|
|
|
21
22
|
from pydantic import (
|
|
22
23
|
AliasChoices,
|
|
23
|
-
|
|
24
|
+
AnyHttpUrl,
|
|
24
25
|
BaseModel,
|
|
25
26
|
Field,
|
|
27
|
+
FileUrl,
|
|
28
|
+
TypeAdapter,
|
|
26
29
|
ValidationError,
|
|
27
30
|
)
|
|
28
31
|
from pydantic.functional_validators import (
|
|
29
32
|
field_validator,
|
|
30
33
|
model_validator,
|
|
31
34
|
)
|
|
32
|
-
from pydantic.networks import UrlConstraints
|
|
33
35
|
|
|
34
36
|
from s7.exceptions import EntityNotFound
|
|
35
37
|
from s7.pydantic_models._utils import (
|
|
@@ -48,10 +50,7 @@ if TYPE_CHECKING: # pragma: no cover
|
|
|
48
50
|
ListPropertyType = Union[UnshapedPropertyType, ShapedListPropertyType]
|
|
49
51
|
|
|
50
52
|
|
|
51
|
-
SOFT7IdentityURIType =
|
|
52
|
-
AnyUrl,
|
|
53
|
-
UrlConstraints(allowed_schemes=["http", "https", "file"], host_required=True),
|
|
54
|
-
]
|
|
53
|
+
SOFT7IdentityURIType = Union[AnyHttpUrl, FileUrl]
|
|
55
54
|
|
|
56
55
|
|
|
57
56
|
LOGGER = logging.getLogger(__name__)
|
|
@@ -76,9 +75,9 @@ def SOFT7IdentityURI(url: str) -> SOFT7IdentityURIType:
|
|
|
76
75
|
"""
|
|
77
76
|
if isinstance(url, str):
|
|
78
77
|
url = url.split("#", maxsplit=1)[0].split("?", maxsplit=1)[0]
|
|
79
|
-
return SOFT7IdentityURIType(url)
|
|
78
|
+
return TypeAdapter(SOFT7IdentityURIType).validate_python(url)
|
|
80
79
|
|
|
81
|
-
raise TypeError(f"Expected str or
|
|
80
|
+
raise TypeError(f"Expected str, AnyHttpUrl, or FileUrl, got {type(url)}")
|
|
82
81
|
|
|
83
82
|
|
|
84
83
|
SOFT7EntityPropertyType = Union[
|
|
@@ -109,7 +108,9 @@ map_soft_to_py_types: dict[str, type[UnshapedPropertyType]] = {
|
|
|
109
108
|
"""Use this with a fallback default of returning the lookup value."""
|
|
110
109
|
|
|
111
110
|
|
|
112
|
-
def parse_identity(
|
|
111
|
+
def parse_identity(
|
|
112
|
+
identity: SOFT7IdentityURIType,
|
|
113
|
+
) -> tuple[SOFT7IdentityURIType, Optional[str], str]:
|
|
113
114
|
"""Parse the identity into a tuple of (namespace, version, name).
|
|
114
115
|
|
|
115
116
|
The identity is a URI of the form: `<namespace>/<version>/<name>`.
|
|
@@ -168,7 +169,7 @@ def parse_identity(identity: AnyUrl) -> tuple[AnyUrl, Optional[str], str]:
|
|
|
168
169
|
# Remove version and name from path, including the 2 associated preceding slashes.
|
|
169
170
|
namespace += identity.path.rstrip("/")[: -len(version) - len(name) - 2]
|
|
170
171
|
|
|
171
|
-
return
|
|
172
|
+
return SOFT7IdentityURI(namespace), version or None, name
|
|
172
173
|
|
|
173
174
|
|
|
174
175
|
class SOFT7EntityProperty(BaseModel):
|
|
@@ -281,7 +282,7 @@ class SOFT7EntityProperty(BaseModel):
|
|
|
281
282
|
# it.
|
|
282
283
|
return data
|
|
283
284
|
|
|
284
|
-
if isinstance(ref,
|
|
285
|
+
if isinstance(ref, get_args(SOFT7IdentityURIType)):
|
|
285
286
|
new_type = ref
|
|
286
287
|
elif isinstance(ref, str):
|
|
287
288
|
try:
|
|
@@ -632,7 +633,9 @@ class SOFT7Entity(BaseModel):
|
|
|
632
633
|
|
|
633
634
|
|
|
634
635
|
def parse_input_entity(
|
|
635
|
-
entity: Union[
|
|
636
|
+
entity: Union[
|
|
637
|
+
SOFT7Entity, dict[str, Any], Path, SOFT7IdentityURIType, str, bytes, bytearray
|
|
638
|
+
],
|
|
636
639
|
) -> SOFT7Entity:
|
|
637
640
|
"""Parse input to a function that expects a SOFT7 entity."""
|
|
638
641
|
if isinstance(entity, SOFT7Entity):
|
|
@@ -169,7 +169,7 @@ def test_inspect_created_datasource(
|
|
|
169
169
|
httpx_mock: HTTPXMock,
|
|
170
170
|
) -> None:
|
|
171
171
|
"""Test the generated data source contains the expected attributes and metadata."""
|
|
172
|
-
from pydantic import
|
|
172
|
+
from pydantic import AnyHttpUrl, BaseModel
|
|
173
173
|
|
|
174
174
|
from s7.factories.datasource_factory import create_datasource
|
|
175
175
|
|
|
@@ -270,7 +270,7 @@ def test_inspect_created_datasource(
|
|
|
270
270
|
== soft_entity_init["identity"]
|
|
271
271
|
== "http://onto-ns.com/s7/0.1.0/MolecularSpecies"
|
|
272
272
|
)
|
|
273
|
-
assert datasource.soft7___namespace ==
|
|
273
|
+
assert datasource.soft7___namespace == AnyHttpUrl("http://onto-ns.com/s7")
|
|
274
274
|
assert datasource.soft7___version == "0.1.0"
|
|
275
275
|
assert datasource.soft7___name == "MolecularSpecies"
|
|
276
276
|
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
"""
|
|
2
|
-
"""
|
|
1
|
+
"""Unit tests for the graph module"""
|
|
3
2
|
|
|
4
3
|
from __future__ import annotations
|
|
5
4
|
|
|
@@ -8,15 +7,15 @@ from unittest.mock import patch
|
|
|
8
7
|
|
|
9
8
|
import pytest
|
|
10
9
|
|
|
11
|
-
from graph.utils import fetch_and_populate_graph, find_parent_node
|
|
12
|
-
|
|
13
10
|
|
|
14
11
|
class TestFindParentNode(unittest.TestCase):
|
|
15
12
|
"""Testclass"""
|
|
16
13
|
|
|
17
|
-
@patch("graph.utils.SPARQLWrapper")
|
|
14
|
+
@patch("s7.graph.utils.SPARQLWrapper")
|
|
18
15
|
def test_find_parent_node_success(self, mock_wrapper):
|
|
19
16
|
"""Set up the mock for successful return"""
|
|
17
|
+
from s7.graph.utils import find_parent_node
|
|
18
|
+
|
|
20
19
|
sparql = mock_wrapper.return_value
|
|
21
20
|
sparql.query().convert.return_value = {
|
|
22
21
|
"results": {
|
|
@@ -31,9 +30,11 @@ class TestFindParentNode(unittest.TestCase):
|
|
|
31
30
|
|
|
32
31
|
assert result == "http://example.com/Parent"
|
|
33
32
|
|
|
34
|
-
@patch("graph.utils.SPARQLWrapper")
|
|
33
|
+
@patch("s7.graph.utils.SPARQLWrapper")
|
|
35
34
|
def test_find_parent_node_no_parent_found(self, mock_wrapper):
|
|
36
35
|
"""Set up the mock for no parent found"""
|
|
36
|
+
from s7.graph.utils import find_parent_node
|
|
37
|
+
|
|
37
38
|
sparql = mock_wrapper.return_value
|
|
38
39
|
sparql.query().convert.return_value = {"results": {"bindings": []}}
|
|
39
40
|
|
|
@@ -42,9 +43,11 @@ class TestFindParentNode(unittest.TestCase):
|
|
|
42
43
|
)
|
|
43
44
|
assert result is None
|
|
44
45
|
|
|
45
|
-
@patch("graph.utils.SPARQLWrapper")
|
|
46
|
+
@patch("s7.graph.utils.SPARQLWrapper")
|
|
46
47
|
def test_find_parent_node_exception(self, mock_wrapper):
|
|
47
48
|
"""Simulate a RuntimError exception"""
|
|
49
|
+
from s7.graph.utils import find_parent_node
|
|
50
|
+
|
|
48
51
|
sparql = mock_wrapper.return_value
|
|
49
52
|
sparql.query.side_effect = RuntimeError("SPARQL error")
|
|
50
53
|
|
|
@@ -57,10 +60,12 @@ class TestFindParentNode(unittest.TestCase):
|
|
|
57
60
|
class TestFetchAndPopulateGraph(unittest.TestCase):
|
|
58
61
|
"""Testclass"""
|
|
59
62
|
|
|
60
|
-
@patch("graph.utils.SPARQLWrapper")
|
|
61
|
-
@patch("graph.utils.rdflib.Graph")
|
|
63
|
+
@patch("s7.graph.utils.SPARQLWrapper")
|
|
64
|
+
@patch("s7.graph.utils.rdflib.Graph")
|
|
62
65
|
def test_fetch_and_populate_graph_success(self, mock_graph, mock_wrapper):
|
|
63
66
|
"""Set up mocks"""
|
|
67
|
+
from s7.graph.utils import fetch_and_populate_graph
|
|
68
|
+
|
|
64
69
|
sparql = mock_wrapper.return_value
|
|
65
70
|
graph = mock_graph.return_value
|
|
66
71
|
sparql.query().convert.return_value = {
|
|
@@ -82,9 +87,11 @@ class TestFetchAndPopulateGraph(unittest.TestCase):
|
|
|
82
87
|
assert graph.add.called
|
|
83
88
|
assert result == graph
|
|
84
89
|
|
|
85
|
-
@patch("graph.utils.SPARQLWrapper")
|
|
90
|
+
@patch("s7.graph.utils.SPARQLWrapper")
|
|
86
91
|
def test_fetch_and_populate_graph_failure(self, mock_wrapper):
|
|
87
92
|
"""Simulate a SPARQLWrapper exception"""
|
|
93
|
+
from s7.graph.utils import fetch_and_populate_graph
|
|
94
|
+
|
|
88
95
|
sparql = mock_wrapper.return_value
|
|
89
96
|
sparql.query.side_effect = RuntimeError("SPARQL error")
|
|
90
97
|
|
|
@@ -10,7 +10,7 @@ if TYPE_CHECKING:
|
|
|
10
10
|
from pathlib import Path
|
|
11
11
|
from typing import Any, Union
|
|
12
12
|
|
|
13
|
-
from pydantic import
|
|
13
|
+
from pydantic import AnyHttpUrl
|
|
14
14
|
from pytest_httpx import HTTPXMock
|
|
15
15
|
|
|
16
16
|
from s7.pydantic_models.soft7_entity import SOFT7Entity
|
|
@@ -25,7 +25,7 @@ def test__flatten_mapping(httpx_mock: HTTPXMock, static_folder: Path) -> None:
|
|
|
25
25
|
|
|
26
26
|
from oteapi.models import AttrDict, MappingConfig
|
|
27
27
|
from oteapi.strategies.mapping.mapping import MappingStrategy
|
|
28
|
-
from pydantic import
|
|
28
|
+
from pydantic import AnyHttpUrl
|
|
29
29
|
|
|
30
30
|
from s7.oteapi_plugin.soft7_function import RDFTriple, SOFT7Generator
|
|
31
31
|
from s7.pydantic_models.oteapi import default_soft7_ote_function_config
|
|
@@ -139,40 +139,46 @@ def test__flatten_mapping(httpx_mock: HTTPXMock, static_folder: Path) -> None:
|
|
|
139
139
|
for _ in [
|
|
140
140
|
(
|
|
141
141
|
{
|
|
142
|
-
"namespace":
|
|
142
|
+
"namespace": AnyHttpUrl(
|
|
143
143
|
mapping_config.prefixes["optimade"].rstrip("#")
|
|
144
144
|
),
|
|
145
145
|
"concept": "data.id",
|
|
146
146
|
},
|
|
147
147
|
{"namespace": "", "concept": ""},
|
|
148
148
|
{
|
|
149
|
-
"namespace":
|
|
149
|
+
"namespace": AnyHttpUrl(
|
|
150
|
+
mapping_config.prefixes["soft7"].rstrip("#")
|
|
151
|
+
),
|
|
150
152
|
"concept": "properties.id",
|
|
151
153
|
},
|
|
152
154
|
),
|
|
153
155
|
(
|
|
154
156
|
{
|
|
155
|
-
"namespace":
|
|
157
|
+
"namespace": AnyHttpUrl(
|
|
156
158
|
mapping_config.prefixes["optimade"].rstrip("#")
|
|
157
159
|
),
|
|
158
160
|
"concept": "data.type",
|
|
159
161
|
},
|
|
160
162
|
{"namespace": "", "concept": ""},
|
|
161
163
|
{
|
|
162
|
-
"namespace":
|
|
164
|
+
"namespace": AnyHttpUrl(
|
|
165
|
+
mapping_config.prefixes["soft7"].rstrip("#")
|
|
166
|
+
),
|
|
163
167
|
"concept": "properties.type",
|
|
164
168
|
},
|
|
165
169
|
),
|
|
166
170
|
(
|
|
167
171
|
{
|
|
168
|
-
"namespace":
|
|
172
|
+
"namespace": AnyHttpUrl(
|
|
169
173
|
mapping_config.prefixes["optimade"].rstrip("#")
|
|
170
174
|
),
|
|
171
175
|
"concept": "data.attributes",
|
|
172
176
|
},
|
|
173
177
|
{"namespace": "", "concept": ""},
|
|
174
178
|
{
|
|
175
|
-
"namespace":
|
|
179
|
+
"namespace": AnyHttpUrl(
|
|
180
|
+
mapping_config.prefixes["soft7"].rstrip("#")
|
|
181
|
+
),
|
|
176
182
|
"concept": "properties.attributes",
|
|
177
183
|
},
|
|
178
184
|
),
|
|
@@ -190,7 +196,7 @@ def _generate_entity_test_cases() -> tuple[
|
|
|
190
196
|
list[str],
|
|
191
197
|
list[
|
|
192
198
|
str,
|
|
193
|
-
|
|
199
|
+
AnyHttpUrl,
|
|
194
200
|
SOFT7Entity,
|
|
195
201
|
str,
|
|
196
202
|
dict[str, Any],
|
|
@@ -199,14 +205,17 @@ def _generate_entity_test_cases() -> tuple[
|
|
|
199
205
|
],
|
|
200
206
|
]:
|
|
201
207
|
"""Generate test cases for the `test_dataclass_validation()` test."""
|
|
208
|
+
from pathlib import Path
|
|
209
|
+
|
|
202
210
|
import yaml
|
|
203
|
-
from pydantic import
|
|
211
|
+
from pydantic import AnyHttpUrl
|
|
204
212
|
|
|
205
213
|
from s7.factories.entity_factory import create_entity
|
|
206
214
|
from s7.pydantic_models.soft7_entity import SOFT7Entity
|
|
207
|
-
from tests.conftest import static_folder
|
|
208
215
|
|
|
209
|
-
|
|
216
|
+
static_folder = Path(__file__).parent.parent / "static"
|
|
217
|
+
|
|
218
|
+
test_data_entity_path = static_folder / "soft_datasource_entity.yaml"
|
|
210
219
|
test_data_entity = yaml.safe_load(test_data_entity_path.read_text(encoding="utf-8"))
|
|
211
220
|
test_entity = SOFT7Entity(**test_data_entity)
|
|
212
221
|
|
|
@@ -214,7 +223,7 @@ def _generate_entity_test_cases() -> tuple[
|
|
|
214
223
|
# identity as a string
|
|
215
224
|
test_data_entity["identity"],
|
|
216
225
|
# identity as a URL
|
|
217
|
-
|
|
226
|
+
AnyHttpUrl(test_data_entity["identity"]),
|
|
218
227
|
# Entity as a SOFT7Entity instance
|
|
219
228
|
test_entity,
|
|
220
229
|
# Entity as a JSON-serialized string
|
|
@@ -236,19 +245,19 @@ def _generate_entity_test_cases() -> tuple[
|
|
|
236
245
|
def test_dataclass_validation(
|
|
237
246
|
functionType: str,
|
|
238
247
|
entity: Union[
|
|
239
|
-
str, type[SOFT7EntityInstance], dict[str, Any], Path,
|
|
248
|
+
str, type[SOFT7EntityInstance], dict[str, Any], Path, AnyHttpUrl, SOFT7Entity
|
|
240
249
|
],
|
|
241
250
|
httpx_mock: HTTPXMock,
|
|
242
251
|
soft_entity_init: dict[str, Union[str, dict]],
|
|
243
252
|
) -> None:
|
|
244
253
|
"""Check the dataclass instantiates correctly (and validates) with different input
|
|
245
254
|
types."""
|
|
246
|
-
from pydantic import
|
|
255
|
+
from pydantic import AnyHttpUrl, ValidationError
|
|
247
256
|
|
|
248
257
|
from s7.oteapi_plugin.soft7_function import SOFT7Generator
|
|
249
258
|
from s7.pydantic_models.soft7_entity import SOFT7IdentityURI
|
|
250
259
|
|
|
251
|
-
if isinstance(entity, (
|
|
260
|
+
if isinstance(entity, (AnyHttpUrl, str)):
|
|
252
261
|
try:
|
|
253
262
|
SOFT7IdentityURI(str(entity))
|
|
254
263
|
except ValidationError:
|