tripper 0.3.3__tar.gz → 0.3.4__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 (91) hide show
  1. {tripper-0.3.3 → tripper-0.3.4}/.github/workflows/ci_tests.yml +2 -0
  2. {tripper-0.3.3 → tripper-0.3.4}/.pre-commit-config.yaml +4 -4
  3. {tripper-0.3.3 → tripper-0.3.4}/CHANGELOG.md +18 -8
  4. {tripper-0.3.3 → tripper-0.3.4}/PKG-INFO +2 -2
  5. {tripper-0.3.3 → tripper-0.3.4}/docs/developers.md +12 -1
  6. {tripper-0.3.3 → tripper-0.3.4}/pyproject.toml +2 -1
  7. {tripper-0.3.3 → tripper-0.3.4}/tests/backends/test_collection.py +2 -0
  8. {tripper-0.3.3 → tripper-0.3.4}/tests/backends/test_rdflib.py +2 -0
  9. {tripper-0.3.3 → tripper-0.3.4}/tests/test_literals.py +26 -2
  10. {tripper-0.3.3 → tripper-0.3.4}/tests/test_triplestore.py +93 -1
  11. {tripper-0.3.3 → tripper-0.3.4}/tripper/__init__.py +1 -1
  12. {tripper-0.3.3 → tripper-0.3.4}/tripper/backends/collection.py +2 -0
  13. {tripper-0.3.3 → tripper-0.3.4}/tripper/backends/ontopy.py +2 -0
  14. {tripper-0.3.3 → tripper-0.3.4}/tripper/backends/rdflib.py +2 -0
  15. {tripper-0.3.3 → tripper-0.3.4}/tripper/backends/sparqlwrapper.py +2 -0
  16. {tripper-0.3.3 → tripper-0.3.4}/tripper/convert/convert.py +35 -4
  17. tripper-0.3.4/tripper/errors.py +43 -0
  18. {tripper-0.3.3 → tripper-0.3.4}/tripper/interface.py +4 -0
  19. {tripper-0.3.3 → tripper-0.3.4}/tripper/literal.py +9 -4
  20. {tripper-0.3.3 → tripper-0.3.4}/tripper/triplestore.py +91 -28
  21. {tripper-0.3.3 → tripper-0.3.4}/tripper/tripper.py +2 -2
  22. tripper-0.3.3/tripper/errors.py +0 -35
  23. {tripper-0.3.3 → tripper-0.3.4}/.github/dependabot.yml +0 -0
  24. {tripper-0.3.3 → tripper-0.3.4}/.github/pull_request_template.md +0 -0
  25. {tripper-0.3.3 → tripper-0.3.4}/.github/workflows/cd_release.yml +0 -0
  26. {tripper-0.3.3 → tripper-0.3.4}/.github/workflows/ci_automerge_dependency_prs.yml +0 -0
  27. {tripper-0.3.3 → tripper-0.3.4}/.github/workflows/ci_cd_updated_main.yml +0 -0
  28. {tripper-0.3.3 → tripper-0.3.4}/.github/workflows/ci_check_dependencies.yml +0 -0
  29. {tripper-0.3.3 → tripper-0.3.4}/.github/workflows/ci_update_dependencies.yml +0 -0
  30. {tripper-0.3.3 → tripper-0.3.4}/.gitignore +0 -0
  31. {tripper-0.3.3 → tripper-0.3.4}/LICENSE +0 -0
  32. {tripper-0.3.3 → tripper-0.3.4}/README.md +0 -0
  33. {tripper-0.3.3 → tripper-0.3.4}/docs/CHANGELOG.md +0 -0
  34. {tripper-0.3.3 → tripper-0.3.4}/docs/LICENSE.md +0 -0
  35. {tripper-0.3.3 → tripper-0.3.4}/docs/api_reference/.pages +0 -0
  36. {tripper-0.3.3 → tripper-0.3.4}/docs/api_reference/backends/.pages +0 -0
  37. {tripper-0.3.3 → tripper-0.3.4}/docs/api_reference/backends/collection.md +0 -0
  38. {tripper-0.3.3 → tripper-0.3.4}/docs/api_reference/backends/ontopy.md +0 -0
  39. {tripper-0.3.3 → tripper-0.3.4}/docs/api_reference/backends/rdflib.md +0 -0
  40. {tripper-0.3.3 → tripper-0.3.4}/docs/api_reference/backends/sparqlwrapper.md +0 -0
  41. {tripper-0.3.3 → tripper-0.3.4}/docs/api_reference/convert/.pages +0 -0
  42. {tripper-0.3.3 → tripper-0.3.4}/docs/api_reference/convert/convert.md +0 -0
  43. {tripper-0.3.3 → tripper-0.3.4}/docs/api_reference/errors.md +0 -0
  44. {tripper-0.3.3 → tripper-0.3.4}/docs/api_reference/interface.md +0 -0
  45. {tripper-0.3.3 → tripper-0.3.4}/docs/api_reference/literal.md +0 -0
  46. {tripper-0.3.3 → tripper-0.3.4}/docs/api_reference/mappings/.pages +0 -0
  47. {tripper-0.3.3 → tripper-0.3.4}/docs/api_reference/mappings/mappings.md +0 -0
  48. {tripper-0.3.3 → tripper-0.3.4}/docs/api_reference/namespace.md +0 -0
  49. {tripper-0.3.3 → tripper-0.3.4}/docs/api_reference/testutils.md +0 -0
  50. {tripper-0.3.3 → tripper-0.3.4}/docs/api_reference/triplestore.md +0 -0
  51. {tripper-0.3.3 → tripper-0.3.4}/docs/api_reference/tripper.md +0 -0
  52. {tripper-0.3.3 → tripper-0.3.4}/docs/api_reference/utils.md +0 -0
  53. {tripper-0.3.3 → tripper-0.3.4}/docs/backend_discovery.md +0 -0
  54. {tripper-0.3.3 → tripper-0.3.4}/docs/css/reference.css +0 -0
  55. {tripper-0.3.3 → tripper-0.3.4}/docs/index.md +0 -0
  56. {tripper-0.3.3 → tripper-0.3.4}/docs/known-issues.md +0 -0
  57. {tripper-0.3.3 → tripper-0.3.4}/docs/mappings/figs/function_emmo.svg +0 -0
  58. {tripper-0.3.3 → tripper-0.3.4}/docs/planned-backends.md +0 -0
  59. {tripper-0.3.3 → tripper-0.3.4}/docs/tutorial.md +0 -0
  60. {tripper-0.3.3 → tripper-0.3.4}/examples/workflow-mappings/README.md +0 -0
  61. {tripper-0.3.3 → tripper-0.3.4}/examples/workflow-mappings/knowledge-base.svg +0 -0
  62. {tripper-0.3.3 → tripper-0.3.4}/examples/workflow-mappings/route.svg +0 -0
  63. {tripper-0.3.3 → tripper-0.3.4}/examples/workflow-mappings/workflow.svg +0 -0
  64. {tripper-0.3.3 → tripper-0.3.4}/examples/workflow-mappings/workflow_mappings.py +0 -0
  65. {tripper-0.3.3 → tripper-0.3.4}/examples/workflow-mappings/workflow_w_pipes.svg +0 -0
  66. {tripper-0.3.3 → tripper-0.3.4}/mkdocs.yml +0 -0
  67. {tripper-0.3.3 → tripper-0.3.4}/tests/backends/__init__.py +0 -0
  68. {tripper-0.3.3 → tripper-0.3.4}/tests/backends/dummy.py +0 -0
  69. {tripper-0.3.3 → tripper-0.3.4}/tests/conftest.py +0 -0
  70. {tripper-0.3.3 → tripper-0.3.4}/tests/convert/test_convert.py +0 -0
  71. {tripper-0.3.3 → tripper-0.3.4}/tests/mappings/test_cost.py +0 -0
  72. {tripper-0.3.3 → tripper-0.3.4}/tests/mappings/test_function.py +0 -0
  73. {tripper-0.3.3 → tripper-0.3.4}/tests/mappings/test_mappings.py +0 -0
  74. {tripper-0.3.3 → tripper-0.3.4}/tests/ontologies/family.ttl +0 -0
  75. {tripper-0.3.3 → tripper-0.3.4}/tests/ontologies/food-more.ttl +0 -0
  76. {tripper-0.3.3 → tripper-0.3.4}/tests/ontologies/food.ttl +0 -0
  77. {tripper-0.3.3 → tripper-0.3.4}/tests/output/.gitignore +0 -0
  78. {tripper-0.3.3 → tripper-0.3.4}/tests/test_add_function.py +0 -0
  79. {tripper-0.3.3 → tripper-0.3.4}/tests/test_custom_backend.py +0 -0
  80. {tripper-0.3.3 → tripper-0.3.4}/tests/test_eval_function.py +0 -0
  81. {tripper-0.3.3 → tripper-0.3.4}/tests/test_get_data.py +0 -0
  82. {tripper-0.3.3 → tripper-0.3.4}/tests/test_namespace.py +0 -0
  83. {tripper-0.3.3 → tripper-0.3.4}/tests/test_sparql.py +0 -0
  84. {tripper-0.3.3 → tripper-0.3.4}/tests/test_utils.py +0 -0
  85. {tripper-0.3.3 → tripper-0.3.4}/tripper/backends/__init__.py +0 -0
  86. {tripper-0.3.3 → tripper-0.3.4}/tripper/convert/__init__.py +0 -0
  87. {tripper-0.3.3 → tripper-0.3.4}/tripper/mappings/__init__.py +0 -0
  88. {tripper-0.3.3 → tripper-0.3.4}/tripper/mappings/mappings.py +0 -0
  89. {tripper-0.3.3 → tripper-0.3.4}/tripper/namespace.py +0 -0
  90. {tripper-0.3.3 → tripper-0.3.4}/tripper/testutils.py +0 -0
  91. {tripper-0.3.3 → tripper-0.3.4}/tripper/utils.py +0 -0
@@ -36,12 +36,14 @@ jobs:
36
36
  # https://pyup.io/vulnerabilities/CVE-2021-34141/44717/
37
37
  # 70612: Jinja2 vulnerability. Only used as subdependency for mkdocs++ in tripper.
38
38
  # https://data.safetycli.com/v/70612/97c/
39
+ # https://data.safetycli.com/v/72715/97c/ # update to mkdocs>=9.5.32
39
40
  safety_options: |
40
41
  --ignore=48547
41
42
  --ignore=44715
42
43
  --ignore=44716
43
44
  --ignore=44717
44
45
  --ignore=70612
46
+ --ignore=72715
45
47
 
46
48
  ## Build package
47
49
  run_build_package: true
@@ -19,7 +19,7 @@ ci:
19
19
  # hooks
20
20
  repos:
21
21
  - repo: https://github.com/pre-commit/pre-commit-hooks
22
- rev: v4.6.0
22
+ rev: v5.0.0
23
23
  hooks:
24
24
  - id: end-of-file-fixer
25
25
  exclude: ^.*\.(svg)$
@@ -41,20 +41,20 @@ repos:
41
41
  - --skip-gitignore
42
42
 
43
43
  - repo: https://github.com/psf/black
44
- rev: 24.8.0
44
+ rev: 24.10.0
45
45
  hooks:
46
46
  - id: black
47
47
  args: [--line-length=79]
48
48
 
49
49
  - repo: https://github.com/PyCQA/bandit
50
- rev: 1.7.9
50
+ rev: 1.7.10
51
51
  hooks:
52
52
  - id: bandit
53
53
  args: ["-r"]
54
54
  files: ^tripper/.*$
55
55
 
56
56
  - repo: https://github.com/pre-commit/mirrors-mypy
57
- rev: v1.11.1
57
+ rev: v1.11.2
58
58
  hooks:
59
59
  - id: mypy
60
60
  exclude: ^tests/.*$
@@ -1,23 +1,33 @@
1
1
  # Changelog
2
2
 
3
- ## [Unreleased](https://github.com/EMMC-ASBL/tripper/tree/HEAD)
3
+ ## [v0.3.4](https://github.com/EMMC-ASBL/tripper/tree/v0.3.4) (2024-10-15)
4
4
 
5
- [Full Changelog](https://github.com/EMMC-ASBL/tripper/compare/v0.3.3...HEAD)
5
+ [Full Changelog](https://github.com/EMMC-ASBL/tripper/compare/v0.3.3...v0.3.4)
6
6
 
7
- **Implemented enhancements:**
8
-
9
- - Use Trusted Publishers for PyPI [\#242](https://github.com/EMMC-ASBL/tripper/issues/242)
10
-
11
- **Closed issues:**
7
+ **Merged pull requests:**
12
8
 
13
- - Add instructions of how to release a new version [\#47](https://github.com/EMMC-ASBL/tripper/issues/47)
9
+ - Allowing backends to indicate whether they prefer the sparql interface [\#255](https://github.com/EMMC-ASBL/tripper/pull/255) ([jesper-friis](https://github.com/jesper-friis))
10
+ - \[pre-commit.ci\] pre-commit autoupdate [\#254](https://github.com/EMMC-ASBL/tripper/pull/254) ([pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci))
11
+ - Update n3 format for string with quotes. [\#253](https://github.com/EMMC-ASBL/tripper/pull/253) ([pintr](https://github.com/pintr))
12
+ - Added option to Triplestore.value\(\) to return a generator over all matching values [\#252](https://github.com/EMMC-ASBL/tripper/pull/252) ([jesper-friis](https://github.com/jesper-friis))
13
+ - Ignore safety vulnerabeility 72715 [\#251](https://github.com/EMMC-ASBL/tripper/pull/251) ([jesper-friis](https://github.com/jesper-friis))
14
+ - Fix empty prefix [\#250](https://github.com/EMMC-ASBL/tripper/pull/250) ([jesper-friis](https://github.com/jesper-friis))
15
+ - \[pre-commit.ci\] pre-commit autoupdate [\#249](https://github.com/EMMC-ASBL/tripper/pull/249) ([pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci))
16
+ - Backend info to triplestore instance [\#248](https://github.com/EMMC-ASBL/tripper/pull/248) ([jesper-friis](https://github.com/jesper-friis))
17
+ - Added argument `ignore_unrecognised` to tripper.convert\(\) [\#247](https://github.com/EMMC-ASBL/tripper/pull/247) ([jesper-friis](https://github.com/jesper-friis))
18
+ - \[pre-commit.ci\] pre-commit autoupdate [\#244](https://github.com/EMMC-ASBL/tripper/pull/244) ([pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci))
14
19
 
15
20
  ## [v0.3.3](https://github.com/EMMC-ASBL/tripper/tree/v0.3.3) (2024-08-26)
16
21
 
17
22
  [Full Changelog](https://github.com/EMMC-ASBL/tripper/compare/v0.3.2...v0.3.3)
18
23
 
24
+ **Implemented enhancements:**
25
+
26
+ - Use Trusted Publishers for PyPI [\#242](https://github.com/EMMC-ASBL/tripper/issues/242)
27
+
19
28
  **Closed issues:**
20
29
 
30
+ - Add instructions of how to release a new version [\#47](https://github.com/EMMC-ASBL/tripper/issues/47)
21
31
  - Update Python API references in README [\#22](https://github.com/EMMC-ASBL/tripper/issues/22)
22
32
 
23
33
  **Merged pull requests:**
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: tripper
3
- Version: 0.3.3
3
+ Version: 0.3.4
4
4
  Summary: A triplestore wrapper for Python.
5
5
  Keywords: triplestore,ontology,RDF
6
6
  Author-email: SINTEF <TEAM4.0@sintef.no>
@@ -47,7 +47,7 @@ Requires-Dist: mkdocs-awesome-pages-plugin==2.9.2 ; extra == "docs"
47
47
  Requires-Dist: mkdocs-material==9.5.17 ; extra == "docs"
48
48
  Requires-Dist: mkdocstrings==0.22.0 ; extra == "docs"
49
49
  Requires-Dist: mkdocstrings-python-legacy==0.2.3 ; extra == "docs"
50
- Requires-Dist: pre-commit==2.21.0 ; extra == "pre-commit"
50
+ Requires-Dist: pre-commit==4.0.1 ; extra == "pre-commit"
51
51
  Requires-Dist: pylint==2.15.5 ; extra == "pre-commit"
52
52
  Requires-Dist: pint>=0.16.1,<0.24 ; extra == "testing"
53
53
  Requires-Dist: pytest==7.4.4 ; extra == "testing"
@@ -1,6 +1,12 @@
1
1
  # For developers
2
2
 
3
- ## New release
3
+ ## Adding new backends
4
+
5
+ See [interface.py], which defines the interface of a backend and may serve as a template for creating new backends.
6
+
7
+
8
+
9
+ ## Creating new release
4
10
 
5
11
  To create a new release, it is good to have a release summary.
6
12
 
@@ -15,3 +21,8 @@ Then, go to [create a new GitHub releases](https://github.com/EMMC-ASBL/tripper/
15
21
  Add again the tag as the release title (optionally write something else that defines this release as a title).
16
22
 
17
23
  Finally, press the "Publish release" button and ensure the release workflow succeeds (check [the release workflow](https://github.com/EMMC-ASBL/tripper/actions/workflows/cd_release.yml)).
24
+
25
+
26
+
27
+
28
+ [interface.py]: https://github.com/EMMC-ASBL/tripper/blob/master/tripper/interface.py
@@ -35,7 +35,7 @@ dependencies = [
35
35
 
36
36
  [project.optional-dependencies]
37
37
  pre-commit = [
38
- "pre-commit==2.21.0",
38
+ "pre-commit==4.0.1",
39
39
  "pylint==2.15.5",
40
40
  ]
41
41
  testing-core = [
@@ -139,4 +139,5 @@ minversion = "7.0"
139
139
  addopts = "-rs --cov=tripper --cov-report=term --doctest-modules"
140
140
  filterwarnings = [
141
141
  "ignore:.*imp module.*:DeprecationWarning",
142
+ "ignore:::tripper.literal:243", # Ignore warning in doctest
142
143
  ]
@@ -51,3 +51,5 @@ def test_collection():
51
51
  label = ts3.value(STRUCTURE.name, DM.hasLabel)
52
52
  assert isinstance(label, Literal)
53
53
  assert label == Literal("Strontium titanate", lang="en")
54
+
55
+ assert ts.prefer_sparql is False
@@ -36,3 +36,5 @@ def test_rdflib_backend():
36
36
  # Test for bnode
37
37
  ts.add_triples([("_:bn1", RDFS.subClassOf, EX.s)])
38
38
  assert ts.value(predicate=RDFS.subClassOf, object=EX.s) == "_:bn1"
39
+
40
+ assert ts.prefer_sparql is False
@@ -7,6 +7,7 @@ def test_untyped() -> None:
7
7
  """Test creating a untyped literal."""
8
8
  import pytest
9
9
 
10
+ from tripper.errors import UnknownDatatypeWarning
10
11
  from tripper.literal import XSD, Literal
11
12
 
12
13
  literal = Literal("Hello world!")
@@ -23,7 +24,7 @@ def test_untyped() -> None:
23
24
  # Check two things here:
24
25
  # 1) that a plain literal compares false to a non-string literal
25
26
  # 2) that we get a warning about unknown XSD.ENTITY datatype
26
- with pytest.warns(UserWarning, match="^unknown datatype"):
27
+ with pytest.warns(UnknownDatatypeWarning, match="^unknown datatype"):
27
28
  assert literal != Literal("Hello world!", datatype=XSD.ENTITY)
28
29
 
29
30
 
@@ -184,6 +185,28 @@ def test_repr() -> None:
184
185
  )
185
186
 
186
187
 
188
+ def test_n3() -> None:
189
+ """Test n3()."""
190
+ from tripper import RDF, Literal
191
+
192
+ s = Literal(42, datatype=float).n3()
193
+ assert s == '"42"^^<http://www.w3.org/2001/XMLSchema#double>'
194
+ s = Literal("a string").n3()
195
+ assert s == '"a string"'
196
+ s = Literal('a string with "embedded" quotes').n3()
197
+ assert s == r'"a string with \"embedded\" quotes"'
198
+ s = Literal(r"a string with \"escaped\" quotes").n3()
199
+ assert s == r'"a string with \\\"escaped\\\" quotes"'
200
+ s = Literal('"json string"', datatype=RDF.JSON).n3()
201
+ assert s == (
202
+ r'"\"json string\""^^<http://www.w3.org/1999/02/22-rdf-syntax-ns#JSON>'
203
+ )
204
+ s = Literal('{"a": 1}', datatype=RDF.JSON).n3()
205
+ assert (
206
+ s == r'"{\"a\": 1}"^^<http://www.w3.org/1999/02/22-rdf-syntax-ns#JSON>'
207
+ )
208
+
209
+
187
210
  def test_split_iri() -> None:
188
211
  """Test parse n3-encoded literal value."""
189
212
  from tripper import DCTERMS, RDFS
@@ -205,6 +228,7 @@ def test_parse_literal() -> None:
205
228
  import pytest
206
229
 
207
230
  from tripper import RDF, XSD, Literal
231
+ from tripper.errors import UnknownDatatypeWarning
208
232
  from tripper.utils import parse_literal
209
233
 
210
234
  literal = parse_literal(Literal("abc").n3())
@@ -293,7 +317,7 @@ def test_parse_literal() -> None:
293
317
  assert literal.lang is None
294
318
  assert literal.datatype == RDF.JSON
295
319
 
296
- with pytest.warns(UserWarning, match="unknown datatype"):
320
+ with pytest.warns(UnknownDatatypeWarning, match="unknown datatype"):
297
321
  literal = parse_literal('"value"^^http://example.com/vocab#mytype')
298
322
  assert literal.value == "value"
299
323
  assert literal.lang is None
@@ -35,14 +35,30 @@ def test_triplestore( # pylint: disable=too-many-locals
35
35
  pytest.importorskip("rdflib")
36
36
  pytest.importorskip("dlite")
37
37
  pytest.importorskip("SPARQLWrapper")
38
+ from tripper.errors import NamespaceError
38
39
  from tripper.triplestore import DCTERMS, OWL, RDF, RDFS, XSD, Triplestore
39
40
 
40
41
  ts = Triplestore(backend)
41
42
  assert ts.expand_iri("xsd:integer") == XSD.integer
42
43
  assert ts.prefix_iri(RDF.type) == "rdf:type"
44
+
45
+ with pytest.raises(NamespaceError):
46
+ ts.expand_iri(":MyConcept")
47
+
48
+ assert ts.prefix_iri("http://example.com#MyConcept") == (
49
+ "http://example.com#MyConcept"
50
+ )
51
+ with pytest.raises(NamespaceError):
52
+ ts.prefix_iri("http://example.com#MyConcept", require_prefixed=True)
53
+
43
54
  EX = ts.bind(
44
55
  "ex", "http://example.com/onto#"
45
56
  ) # pylint: disable=invalid-name
57
+ BASE = ts.bind("", "http://example.com#") # pylint: disable=invalid-name
58
+
59
+ assert ts.expand_iri(":MyConcept") == BASE.MyConcept
60
+ assert ts.prefix_iri(BASE.MyConcept) == ":MyConcept"
61
+
46
62
  assert str(EX) == "http://example.com/onto#"
47
63
  ts.add_mapsTo(
48
64
  EX.MyConcept, "http://onto-ns.com/meta/0.1/MyEntity", "myprop"
@@ -93,6 +109,15 @@ def test_triplestore( # pylint: disable=too-many-locals
93
109
  # ) == example_function.__doc__
94
110
  assert ts.value(func_iri, DCTERMS.description, lang="de") is None
95
111
 
112
+ # Test the `prefer_sparql` property
113
+ facit = {
114
+ "collection": False,
115
+ "ontopy": False,
116
+ "rdflib": False,
117
+ "sparqlwrapper": True,
118
+ }
119
+ assert ts.prefer_sparql == facit[backend]
120
+
96
121
 
97
122
  # if True:
98
123
  def test_restriction() -> None: # pylint: disable=too-many-statements
@@ -392,6 +417,7 @@ def test_backend_ontopy(get_ontology_path: "Callable[[str], Path]") -> None:
392
417
  def test_backend_sparqlwrapper() -> None:
393
418
  """Specifically test the SPARQLWrapper backend Triplestore."""
394
419
  from tripper import SKOS, Triplestore
420
+ from tripper.errors import UnknownDatatypeWarning
395
421
 
396
422
  pytest.importorskip("SPARQLWrapper")
397
423
  ts = Triplestore(
@@ -400,7 +426,7 @@ def test_backend_sparqlwrapper() -> None:
400
426
  "csiro_international-chronostratigraphic-chart_geologic-"
401
427
  "time-scale-2020",
402
428
  )
403
- with pytest.warns(UserWarning, match="unknown datatype"):
429
+ with pytest.warns(UnknownDatatypeWarning, match="unknown datatype"):
404
430
  for s, p, o in ts.triples(predicate=SKOS.notation):
405
431
  assert s
406
432
  assert p
@@ -496,3 +522,69 @@ def test_bind_errors():
496
522
  ts2.bind("ex")
497
523
  with pytest.raises(TypeError):
498
524
  ts2.bind("ex", Ellipsis)
525
+
526
+
527
+ def test_value():
528
+ """Test Triplestore.value()."""
529
+ pytest.importorskip("rdflib")
530
+
531
+ from tripper import DCTERMS, RDF, RDFS, Literal, Triplestore
532
+ from tripper.errors import UniquenessError
533
+
534
+ ts = Triplestore(backend="rdflib")
535
+ EX = ts.bind("ex", "http://example.com#")
536
+ l1 = Literal("First comment...")
537
+ l2en = Literal("Second comment...", lang="en")
538
+ l2da = Literal("Anden kommentar...", lang="da")
539
+ l3en = Literal("Third comment...", lang="en")
540
+ ts.add_triples(
541
+ [
542
+ (EX.mydata, RDF.type, EX.Dataset),
543
+ (EX.mydata, DCTERMS.title, Literal("My little data")),
544
+ (EX.mydata, RDFS.comment, l1),
545
+ (EX.mydata, RDFS.comment, l2en),
546
+ (EX.mydata, RDFS.comment, l2da),
547
+ (EX.mydata, RDFS.comment, l3en),
548
+ ]
549
+ )
550
+
551
+ assert ts.value(subject=EX.mydata, predicate=RDF.type) == EX.Dataset
552
+ assert ts.value(predicate=RDF.type, object=EX.Dataset) == EX.mydata
553
+ assert ts.value(subject=EX.mydata, predicate=DCTERMS.title) == Literal(
554
+ "My little data"
555
+ )
556
+
557
+ with pytest.raises(UniquenessError):
558
+ ts.value(subject=EX.mydata, predicate=RDFS.comment, lang="en")
559
+
560
+ assert (
561
+ ts.value(subject=EX.mydata, predicate=RDFS.comment, lang="da") == l2da
562
+ )
563
+
564
+ assert ts.value(
565
+ subject=EX.mydata,
566
+ predicate=RDFS.comment,
567
+ lang="en",
568
+ any=True,
569
+ ) in (l2en, l3en)
570
+
571
+ assert set(
572
+ ts.value(
573
+ subject=EX.mydata,
574
+ predicate=RDFS.comment,
575
+ lang="en",
576
+ any=None,
577
+ )
578
+ ) == {l2en, l3en}
579
+
580
+ assert (
581
+ ts.value(subject=EX.mydata, predicate=RDFS.comment, lang="no") is None
582
+ )
583
+
584
+ t = ts.value(
585
+ subject=EX.mydata,
586
+ predicate=RDFS.comment,
587
+ lang="no",
588
+ default="a",
589
+ )
590
+ assert t == "a"
@@ -27,7 +27,7 @@ from .namespace import (
27
27
  from .triplestore import Triplestore, backend_packages
28
28
  from .tripper import Tripper
29
29
 
30
- __version__ = "0.3.3"
30
+ __version__ = "0.3.4"
31
31
 
32
32
  __all__ = (
33
33
  "Literal",
@@ -33,6 +33,8 @@ class CollectionStrategy:
33
33
  triplestore from.
34
34
  """
35
35
 
36
+ prefer_sparql = False
37
+
36
38
  def __init__(
37
39
  self,
38
40
  base_iri: "Optional[str]" = None,
@@ -43,6 +43,8 @@ class OntopyStrategy:
43
43
  Either the `base_iri` or `onto` argument must be provided.
44
44
  """
45
45
 
46
+ prefer_sparql = False
47
+
46
48
  def __init__(
47
49
  self,
48
50
  base_iri: "Optional[str]" = None,
@@ -69,6 +69,8 @@ class RdflibStrategy:
69
69
  creating a new empty Graph object.
70
70
  """
71
71
 
72
+ prefer_sparql = False
73
+
72
74
  def __init__(
73
75
  self,
74
76
  base_iri: "Optional[str]" = None, # pylint: disable=unused-argument
@@ -31,6 +31,8 @@ class SparqlwrapperStrategy:
31
31
 
32
32
  """
33
33
 
34
+ prefer_sparql = True
35
+
34
36
  def __init__(self, base_iri: str, **kwargs) -> None:
35
37
  kwargs.pop(
36
38
  "database", None
@@ -1,4 +1,28 @@
1
- """Tripper module for converting between RDF and other repetations."""
1
+ # pylint: disable=line-too-long
2
+ """Tripper module for converting between RDF and other repetations.
3
+
4
+ Example use:
5
+
6
+ >>> from tripper import DCTERMS, Literal, Triplestore
7
+ >>> from tripper.convert import load_container, save_container
8
+
9
+ >>> ts = Triplestore("rdflib")
10
+ >>> dataset = {"a": 1, "b": 2}
11
+ >>> save_container(ts, dataset, ":data_indv")
12
+ >>> load_container(ts, ":data_indv")
13
+ {'a': 1, 'b': 2}
14
+
15
+ # Add additional context to our data individual
16
+ >>> ts.add((":data_indv", DCTERMS.title, Literal("My wonderful data")))
17
+
18
+ >>> load_container(ts, ":data_indv") # doctest: +IGNORE_EXCEPTION_DETAIL
19
+ Traceback (most recent call last):
20
+ ValueError: Unrecognised predicate 'http://purl.org/dc/terms/title' in dict: :data_indv
21
+
22
+ >>> load_container(ts, ":data_indv", ignore_unrecognised=True)
23
+ {'a': 1, 'b': 2}
24
+
25
+ """
2
26
 
3
27
  # pylint: disable=invalid-name,redefined-builtin
4
28
  import warnings
@@ -88,11 +112,11 @@ def from_container(
88
112
  if isinstance(obj, Mapping):
89
113
  if not obj:
90
114
  return OTEIO.Dictionary
91
- obj_iri = f"_:{dict}_{uuid}"
115
+ obj_iri = f"_:dict_{uuid}"
92
116
  elif isinstance(obj, Sequence) and not isinstance(obj, str):
93
117
  if not obj:
94
118
  return RDF.List
95
- obj_iri = f"_:{list}_{uuid}"
119
+ obj_iri = f"_:list_{uuid}"
96
120
  elif obj is None:
97
121
  return OWL.Nothing
98
122
  else:
@@ -208,6 +232,7 @@ def load_container(
208
232
  ts: "Triplestore",
209
233
  iri: str,
210
234
  recognised_keys: "Optional[Union[Dict, str]]" = None,
235
+ ignore_unrecognised: bool = False,
211
236
  ) -> "Union[dict, list]":
212
237
  """Deserialise a Python container object from a triplestore.
213
238
 
@@ -218,6 +243,9 @@ def load_container(
218
243
  correspond to IRIs of recognised RDF properties.
219
244
  If set to the special string "basic", the
220
245
  `BASIC_RECOGNISED_KEYS` module will be used.
246
+ ignore_unrecognised: Ignore relations that has `iri` as subject and
247
+ a predicate that is not among the values of `recognised_keys`
248
+ or `rdf:type`. The default is to raise an exception.
221
249
 
222
250
  Returns:
223
251
  A Python container object corresponding to `iri`.
@@ -258,7 +286,10 @@ def load_container(
258
286
  container[str(key)] = get_obj(value)
259
287
  elif pred in recognised_iris:
260
288
  container[recognised_iris[pred]] = get_obj(obj)
261
- elif pred not in (RDF.type,):
289
+ elif not ignore_unrecognised and pred not in (
290
+ RDF.type,
291
+ RDFS.subClassOf,
292
+ ):
262
293
  raise ValueError(
263
294
  f"Unrecognised predicate '{pred}' in dict: {iri}"
264
295
  )
@@ -0,0 +1,43 @@
1
+ """Exceptions and warnings for the triplestore package."""
2
+
3
+
4
+ # === Exceptions ===
5
+ class TripperError(Exception):
6
+ """Base exception for tripper errors."""
7
+
8
+
9
+ class UniquenessError(TripperError):
10
+ """More than one matching triple."""
11
+
12
+
13
+ class NamespaceError(TripperError):
14
+ """Namespace error."""
15
+
16
+
17
+ class NoSuchIRIError(NamespaceError):
18
+ """Namespace has no such IRI."""
19
+
20
+
21
+ class CannotGetFunctionError(TripperError):
22
+ """Not able to get function documented in the triplestore."""
23
+
24
+
25
+ class ArgumentTypeError(TripperError, TypeError):
26
+ """Invalid argument type."""
27
+
28
+
29
+ class ArgumentValueError(TripperError, ValueError):
30
+ """Invalid argument value (of correct type)."""
31
+
32
+
33
+ # === Warnings ===
34
+ class TripperWarning(Warning):
35
+ """Base class for tripper warnings."""
36
+
37
+
38
+ class UnusedArgumentWarning(TripperWarning):
39
+ """Argument is unused."""
40
+
41
+
42
+ class UnknownDatatypeWarning(TripperWarning):
43
+ """Unknown datatype."""
@@ -24,6 +24,10 @@ class ITriplestore(Protocol):
24
24
 
25
25
  ```python
26
26
 
27
+ # Whether the backend perfers SPQRQL queries instead of using the
28
+ # triples() method.
29
+ prefer_sparql = True
30
+
27
31
  def parse(
28
32
  self,
29
33
  source: Union[str, Path, IO] = None,
@@ -12,6 +12,7 @@ import warnings
12
12
  from datetime import datetime
13
13
  from typing import TYPE_CHECKING
14
14
 
15
+ from tripper.errors import UnknownDatatypeWarning
15
16
  from tripper.namespace import RDF, RDFS, XSD
16
17
 
17
18
  if TYPE_CHECKING: # pragma: no cover
@@ -240,7 +241,8 @@ class Literal(str):
240
241
  string.datatype in types for types in cls.datatypes.values()
241
242
  ):
242
243
  warnings.warn(
243
- f"unknown datatype: {string.datatype} - assuming xsd:string"
244
+ f"unknown datatype: {string.datatype} - assuming xsd:string",
245
+ category=UnknownDatatypeWarning,
244
246
  )
245
247
 
246
248
  return string
@@ -305,8 +307,11 @@ class Literal(str):
305
307
 
306
308
  def n3(self) -> str: # pylint: disable=invalid-name
307
309
  """Returns a representation in n3 format."""
310
+
311
+ form = self.replace("\\", r"\\").replace('"', r"\"")
312
+
308
313
  if self.lang:
309
- return f'"{self}"@{self.lang}'
314
+ return f'"{form}"@{self.lang}'
310
315
  if self.datatype:
311
- return f'"{self}"^^<{self.datatype}>'
312
- return f'"{self}"'
316
+ return f'"{form}"^^<{self.datatype}>'
317
+ return f'"{form}"'
@@ -29,7 +29,7 @@ from tripper.errors import (
29
29
  ArgumentValueError,
30
30
  CannotGetFunctionError,
31
31
  NamespaceError,
32
- TriplestoreError,
32
+ TripperError,
33
33
  UniquenessError,
34
34
  )
35
35
  from tripper.literal import Literal
@@ -99,12 +99,14 @@ hasCost = DM.hasCost
99
99
 
100
100
 
101
101
  # Regular expression matching a prefixed IRI
102
- _MATCH_PREFIXED_IRI = re.compile(r"^([a-z][a-z0-9]*):([^/]{1}.*)$")
102
+ _MATCH_PREFIXED_IRI = re.compile(r"^([a-z][a-z0-9]*)?:([^/]{1}.*)$")
103
103
 
104
104
 
105
105
  class Triplestore:
106
106
  """Provides a common frontend to a range of triplestore backends."""
107
107
 
108
+ # pylint: disable=too-many-instance-attributes
109
+
108
110
  default_namespaces = {
109
111
  "xml": XML,
110
112
  "rdf": RDF,
@@ -165,6 +167,9 @@ class Triplestore:
165
167
  self.namespaces: "Dict[str, Namespace]" = {}
166
168
  self.closed = False
167
169
  self.backend_name = backend_name
170
+ self.database = database
171
+ self.package = package
172
+ self.kwargs = kwargs.copy()
168
173
  self.backend = cls(base_iri=base_iri, database=database, **kwargs)
169
174
 
170
175
  # Cache functions in the triplestore for fast access
@@ -534,6 +539,23 @@ class Triplestore:
534
539
  # interfaces to the triples(), add_triples() and remove() methods
535
540
  # implemented by all backends.
536
541
 
542
+ prefer_sparql = property(
543
+ fget=lambda self: getattr(self.backend, "prefer_sparql", None),
544
+ doc=(
545
+ "Whether the backend prefer SPARQL over the triples() interface. "
546
+ "Is None if not specified by the backend."
547
+ "\n\n"
548
+ "Even though Tripper requires that the Triplestore.triples() is "
549
+ "implemented, Triplestore.query() must be used for some "
550
+ "backends in specific cases (like fuseki when working on RDF "
551
+ "lists) because of how blank nodes are treated. "
552
+ "\n\n"
553
+ "The purpose of this property is to let tripper "
554
+ "automatically select the most appropriate interface depending "
555
+ "on the current backend settings."
556
+ ),
557
+ )
558
+
537
559
  @classmethod
538
560
  def _get_backend(cls, backend: str, package: "Optional[str]" = None):
539
561
  """Returns the class implementing the given backend."""
@@ -580,8 +602,11 @@ class Triplestore:
580
602
  predicate: Possible criteria to match.
581
603
  object: Possible criteria to match.
582
604
  default: Value to return if no matches are found.
583
- any: If true, return any matching value, otherwise raise
584
- UniquenessError.
605
+ any: Used to define how many values to return. Can be set to:
606
+ `False` (default): return the value or raise UniquenessError
607
+ if there is more than one matching value.
608
+ `True`: return any matching value if there is more than one.
609
+ `None`: return a generator over all matching values.
585
610
  lang: If provided, require that the value must be a localised
586
611
  literal with the given language code.
587
612
 
@@ -600,33 +625,30 @@ class Triplestore:
600
625
  (idx,) = [i for i, v in enumerate(spo) if v is None]
601
626
 
602
627
  triples = self.triples(subject, predicate, object)
628
+
603
629
  if lang:
604
- first = None
605
- if idx != 2:
606
- raise ValueError("`object` must be None if `lang` is given")
607
- for triple in triples:
608
- value = triple[idx]
609
- if isinstance(value, Literal) and value.lang == lang:
610
- if any:
611
- return value
612
- if first:
613
- raise UniquenessError("More than one match")
614
- first = value
615
- if first is None:
616
- return default
617
- else:
618
- try:
619
- triple = next(triples)
620
- except StopIteration:
621
- return default
630
+ triples = (
631
+ t
632
+ for t in triples
633
+ if isinstance(t[idx], Literal)
634
+ and t[idx].lang == lang # type: ignore
635
+ )
636
+
637
+ if any is None:
638
+ return (t[idx] for t in triples) # type: ignore
639
+
640
+ try:
641
+ value = next(triples)[idx]
642
+ except StopIteration:
643
+ return default
622
644
 
623
645
  try:
624
646
  next(triples)
625
647
  except StopIteration:
626
- return triple[idx]
648
+ return value
627
649
 
628
- if any:
629
- return triple[idx]
650
+ if any is True:
651
+ return value
630
652
  raise UniquenessError("More than one match")
631
653
 
632
654
  def subjects(
@@ -696,22 +718,63 @@ class Triplestore:
696
718
  # ------------------------------------------
697
719
  def expand_iri(self, iri: str):
698
720
  """Return the full IRI if `iri` is prefixed. Otherwise `iri` is
699
- returned."""
721
+ returned.
722
+
723
+ Examples:
724
+ >>> from tripper import Triplestore
725
+ >>> ts = Triplestore(backend="rdflib")
726
+
727
+ # Unknown prefix raises an exception
728
+ >>> ts.expand_iri("ex:Concept") # doctest: +ELLIPSIS
729
+ Traceback (most recent call last):
730
+ ...
731
+ tripper.errors.NamespaceError: unknown namespace: 'ex'
732
+
733
+ >>> EX = ts.bind("ex", "http://example.com#")
734
+ >>> ts.expand_iri("ex:Concept")
735
+ 'http://example.com#Concept'
736
+
737
+ # Returns `iri` if it has no prefix
738
+ >>> ts.expand_iri("http://example.com#Concept")
739
+ 'http://example.com#Concept'
740
+
741
+ """
700
742
  match = re.match(_MATCH_PREFIXED_IRI, iri)
701
743
  if match:
702
744
  prefix, name = match.groups()
745
+ if prefix is None:
746
+ prefix = ""
703
747
  if prefix not in self.namespaces:
704
- raise NamespaceError(f"unknown namespace: {prefix}")
748
+ raise NamespaceError(f"unknown namespace: '{prefix}'")
705
749
  return f"{self.namespaces[prefix]}{name}"
706
750
  return iri
707
751
 
708
752
  def prefix_iri(self, iri: str, require_prefixed: bool = False):
753
+ # pylint: disable=line-too-long
709
754
  """Return prefixed IRI.
710
755
 
711
756
  This is the reverse of expand_iri().
712
757
 
713
758
  If `require_prefixed` is true, a NamespaceError exception is raised
714
759
  if no prefix can be found.
760
+
761
+ Examples:
762
+ >>> from tripper import Triplestore
763
+ >>> ts = Triplestore(backend="rdflib")
764
+ >>> ts.prefix_iri("http://example.com#Concept")
765
+ 'http://example.com#Concept'
766
+
767
+ >>> ts.prefix_iri(
768
+ ... "http://example.com#Concept", require_prefixed=True
769
+ ... ) # doctest: +ELLIPSIS
770
+ Traceback (most recent call last):
771
+ ...
772
+ tripper.errors.NamespaceError: No prefix defined for IRI: http://example.com#Concept
773
+
774
+ >>> EX = ts.bind("ex", "http://example.com#")
775
+ >>> ts.prefix_iri("http://example.com#Concept")
776
+ 'ex:Concept'
777
+
715
778
  """
716
779
  if not re.match(_MATCH_PREFIXED_IRI, iri):
717
780
  for prefix, namespace in self.namespaces.items():
@@ -967,7 +1030,7 @@ class Triplestore:
967
1030
  self.bind("map", MAP)
968
1031
 
969
1032
  if not property_name and not isinstance(source, str):
970
- raise TriplestoreError(
1033
+ raise TripperError(
971
1034
  "`property_name` is required when `target` is not a string."
972
1035
  )
973
1036
 
@@ -7,7 +7,7 @@ from __future__ import annotations # Support Python 3.7 (PEP 585)
7
7
  import uuid
8
8
  from typing import TYPE_CHECKING
9
9
 
10
- from tripper.errors import TriplestoreError
10
+ from tripper.errors import TripperError
11
11
  from tripper.literal import Literal
12
12
  from tripper.namespace import MAP, RDF
13
13
  from tripper.triplestore import (
@@ -186,7 +186,7 @@ class Tripper(Triplestore):
186
186
  )
187
187
  return retval
188
188
 
189
- raise TriplestoreError(
189
+ raise TripperError(
190
190
  f"data source {iri} has neither a 'hasDataValue' or a "
191
191
  f"'hasAccessFunction' property"
192
192
  )
@@ -1,35 +0,0 @@
1
- """Exceptions and warnings for the triplestore package."""
2
-
3
-
4
- # === Exceptions ===
5
- class TriplestoreError(Exception):
6
- """Base exception for triplestore errors."""
7
-
8
-
9
- class UniquenessError(TriplestoreError):
10
- """More than one matching triple."""
11
-
12
-
13
- class NamespaceError(TriplestoreError):
14
- """Namespace error."""
15
-
16
-
17
- class NoSuchIRIError(NamespaceError):
18
- """Namespace has no such IRI."""
19
-
20
-
21
- class CannotGetFunctionError(TriplestoreError):
22
- """Not able to get function documented in the triplestore."""
23
-
24
-
25
- class ArgumentTypeError(TriplestoreError, TypeError):
26
- """Invalid argument type."""
27
-
28
-
29
- class ArgumentValueError(TriplestoreError, ValueError):
30
- """Invalid argument value (of correct type)."""
31
-
32
-
33
- # === Warnings ===
34
- class UnusedArgumentWarning(Warning):
35
- """Argument is unused."""
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes