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.
Files changed (58) hide show
  1. {soft7-0.2.2 → soft7-0.3.0}/.github/workflows/ci_tests.yml +1 -1
  2. soft7-0.3.0/.markdownlint.yaml +17 -0
  3. {soft7-0.2.2 → soft7-0.3.0}/CHANGELOG.md +40 -6
  4. {soft7-0.2.2 → soft7-0.3.0}/PKG-INFO +4 -3
  5. {soft7-0.2.2 → soft7-0.3.0}/pyproject.toml +3 -2
  6. {soft7-0.2.2 → soft7-0.3.0}/s7/__init__.py +1 -1
  7. {soft7-0.2.2 → soft7-0.3.0}/s7/factories/datasource_factory.py +5 -3
  8. {soft7-0.2.2 → soft7-0.3.0}/s7/factories/entity_factory.py +3 -2
  9. soft7-0.3.0/s7/graph/README.md +88 -0
  10. {soft7-0.2.2 → soft7-0.3.0/s7}/graph/utils.py +7 -4
  11. {soft7-0.2.2 → soft7-0.3.0}/s7/pydantic_models/soft7_entity.py +15 -12
  12. {soft7-0.2.2 → soft7-0.3.0}/tests/factories/test_datasource_factory.py +2 -2
  13. {soft7-0.2.2 → soft7-0.3.0}/tests/graph/test_utils.py +17 -10
  14. {soft7-0.2.2 → soft7-0.3.0}/tests/oteapi_plugin/test_soft7_function.py +25 -16
  15. {soft7-0.2.2 → soft7-0.3.0}/tests/pydantic_models/test_datasource.py +56 -56
  16. {soft7-0.2.2 → soft7-0.3.0}/tests/pydantic_models/test_soft7_entity.py +12 -12
  17. soft7-0.2.2/graph/README.md +0 -86
  18. {soft7-0.2.2 → soft7-0.3.0}/.github/dependabot.yml +0 -0
  19. {soft7-0.2.2 → soft7-0.3.0}/.github/workflows/cd_docs.yml +0 -0
  20. {soft7-0.2.2 → soft7-0.3.0}/.github/workflows/cd_publish.yml +0 -0
  21. {soft7-0.2.2 → soft7-0.3.0}/.github/workflows/ci_bot_pr_changes.yml +0 -0
  22. {soft7-0.2.2 → soft7-0.3.0}/.gitignore +0 -0
  23. {soft7-0.2.2 → soft7-0.3.0}/.pre-commit-config.yaml +0 -0
  24. {soft7-0.2.2 → soft7-0.3.0}/LICENSE +0 -0
  25. {soft7-0.2.2 → soft7-0.3.0}/README.md +0 -0
  26. {soft7-0.2.2 → soft7-0.3.0}/agraph.cfg +0 -0
  27. {soft7-0.2.2 → soft7-0.3.0}/docker-compose.yml +0 -0
  28. {soft7-0.2.2 → soft7-0.3.0}/docs/conf.py +0 -0
  29. {soft7-0.2.2 → soft7-0.3.0}/docs/examples/advanced.ipynb +0 -0
  30. {soft7-0.2.2 → soft7-0.3.0}/docs/examples/basic.ipynb +0 -0
  31. {soft7-0.2.2 → soft7-0.3.0}/docs/examples/datasource.ipynb +0 -0
  32. {soft7-0.2.2 → soft7-0.3.0}/docs/examples/index.md +0 -0
  33. {soft7-0.2.2 → soft7-0.3.0}/docs/index.md +0 -0
  34. {soft7-0.2.2 → soft7-0.3.0}/ontology/entity_soft7.ttl +0 -0
  35. {soft7-0.2.2 → soft7-0.3.0}/s7/exceptions.py +0 -0
  36. {soft7-0.2.2 → soft7-0.3.0}/s7/factories/__init__.py +0 -0
  37. {soft7-0.2.2 → soft7-0.3.0}/s7/factories/generated_classes.py +0 -0
  38. {soft7-0.2.2 → soft7-0.3.0/s7}/graph/__init__.py +0 -0
  39. {soft7-0.2.2 → soft7-0.3.0}/s7/oteapi_plugin/__init__.py +0 -0
  40. {soft7-0.2.2 → soft7-0.3.0}/s7/oteapi_plugin/models.py +0 -0
  41. {soft7-0.2.2 → soft7-0.3.0}/s7/oteapi_plugin/soft7_function.py +0 -0
  42. {soft7-0.2.2 → soft7-0.3.0}/s7/oteapi_plugin/yaml_parser.py +0 -0
  43. {soft7-0.2.2 → soft7-0.3.0}/s7/pydantic_models/__init__.py +0 -0
  44. {soft7-0.2.2 → soft7-0.3.0}/s7/pydantic_models/_utils.py +0 -0
  45. {soft7-0.2.2 → soft7-0.3.0}/s7/pydantic_models/datasource.py +0 -0
  46. {soft7-0.2.2 → soft7-0.3.0}/s7/pydantic_models/oteapi.py +0 -0
  47. {soft7-0.2.2 → soft7-0.3.0}/s7/pydantic_models/soft7_instance.py +0 -0
  48. {soft7-0.2.2 → soft7-0.3.0}/tests/conftest.py +0 -0
  49. {soft7-0.2.2 → soft7-0.3.0}/tests/factories/conftest.py +0 -0
  50. {soft7-0.2.2 → soft7-0.3.0}/tests/pydantic_models/conftest.py +0 -0
  51. {soft7-0.2.2 → soft7-0.3.0}/tests/static/onto-ns_OPTIMADEStructure.json +0 -0
  52. {soft7-0.2.2 → soft7-0.3.0}/tests/static/onto-ns_OPTIMADEStructureAssembly.json +0 -0
  53. {soft7-0.2.2 → soft7-0.3.0}/tests/static/onto-ns_OPTIMADEStructureAttributes.json +0 -0
  54. {soft7-0.2.2 → soft7-0.3.0}/tests/static/onto-ns_OPTIMADEStructureSpecies.json +0 -0
  55. {soft7-0.2.2 → soft7-0.3.0}/tests/static/optimade.materialsproject_mp-1228448.json +0 -0
  56. {soft7-0.2.2 → soft7-0.3.0}/tests/static/soft_datasource_content.yaml +0 -0
  57. {soft7-0.2.2 → soft7-0.3.0}/tests/static/soft_datasource_entity.yaml +0 -0
  58. {soft7-0.2.2 → soft7-0.3.0}/tests/static/soft_datasource_entity_test_data.yaml +0 -0
@@ -46,7 +46,7 @@ jobs:
46
46
  fail-fast: false
47
47
  matrix:
48
48
  # Supported Python versions (according to pyproject.toml)
49
- python-version: ['3.9', '3.10', '3.11', '3.12']
49
+ python-version: ['3.9', '3.10', '3.11', '3.12', '3.13']
50
50
 
51
51
  steps:
52
52
  - name: Checkout repository
@@ -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.2.2...HEAD)
5
+ [Full Changelog](https://github.com/SINTEF/soft7/compare/v0.3.0...HEAD)
6
6
 
7
- # Fix issue with pydantic v2.10
7
+ # New `graph` module
8
8
 
9
- There are some issues with pydantic's v2.10 concerning `Url` models.
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
- All other dependencies and dev tools have also been updated.
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.2.2
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~=0.27.2
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.0 ; extra == "testing"
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~=0.27.2",
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.34.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.2.2"
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 | AnyUrl | str,
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 AnyUrl, ConfigDict, Field, create_model
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, AnyUrl, str],
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
- print(query)
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
- print("Could not find a common parent node.")
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
- print(query)
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
- print("Graph populated with fetched triples.")
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
- AnyUrl,
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 = Annotated[
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 AnyUrl, got {type(url)}")
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(identity: AnyUrl) -> tuple[AnyUrl, Optional[str], str]:
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 AnyUrl(namespace), version or None, name
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, AnyUrl):
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[SOFT7Entity, dict[str, Any], Path, AnyUrl, str, bytes, bytearray],
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 AnyUrl, BaseModel
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 == AnyUrl("http://onto-ns.com/s7")
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
- """ Unit tests for the graph module
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 AnyUrl
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 AnyUrl
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": AnyUrl(
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": AnyUrl(mapping_config.prefixes["soft7"].rstrip("#")),
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": AnyUrl(
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": AnyUrl(mapping_config.prefixes["soft7"].rstrip("#")),
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": AnyUrl(
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": AnyUrl(mapping_config.prefixes["soft7"].rstrip("#")),
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
- AnyUrl,
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 AnyUrl
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
- test_data_entity_path = static_folder() / "soft_datasource_entity.yaml"
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
- AnyUrl(test_data_entity["identity"]),
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, AnyUrl, SOFT7Entity
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 AnyUrl, ValidationError
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, (AnyUrl, str)):
260
+ if isinstance(entity, (AnyHttpUrl, str)):
252
261
  try:
253
262
  SOFT7IdentityURI(str(entity))
254
263
  except ValidationError: