rdflib-ocdm 0.1.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.
@@ -0,0 +1,27 @@
1
+ Metadata-Version: 2.1
2
+ Name: rdflib-ocdm
3
+ Version: 0.1.0
4
+ Summary:
5
+ License: ISC
6
+ Author: arcangelo7
7
+ Author-email: arcangelomas@gmail.com
8
+ Requires-Python: >=3.7.4,<4.0.0
9
+ Classifier: License :: OSI Approved
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.8
12
+ Classifier: Programming Language :: Python :: 3.9
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Requires-Dist: oc-ocdm (>=7.1.7,<8.0.0)
16
+ Requires-Dist: rdflib (>=6.2.0,<7.0.0)
17
+ Requires-Dist: sparqlwrapper (>=2.0.0,<3.0.0)
18
+ Description-Content-Type: text/markdown
19
+
20
+ [<img src="https://img.shields.io/badge/powered%20by-OpenCitations-%239931FC?labelColor=2D22DE" />](http://opencitations.net)
21
+ [![Run tests](https://github.com/opencitations/rdflib-ocdm/actions/workflows/run_tests.yml/badge.svg)](https://github.com/opencitations/rdflib-ocdm/actions/workflows/run_tests.yml)
22
+ ![Coverage](https://raw.githubusercontent.com/opencitations/rdflib-ocdm/main/test/coverage/coverage.svg)
23
+ ![PyPI](https://img.shields.io/pypi/pyversions/rdflib-ocdm)
24
+ ![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/opencitations/rdflib-ocdm)
25
+
26
+ # rdflib-ocdm
27
+
@@ -0,0 +1,7 @@
1
+ [<img src="https://img.shields.io/badge/powered%20by-OpenCitations-%239931FC?labelColor=2D22DE" />](http://opencitations.net)
2
+ [![Run tests](https://github.com/opencitations/rdflib-ocdm/actions/workflows/run_tests.yml/badge.svg)](https://github.com/opencitations/rdflib-ocdm/actions/workflows/run_tests.yml)
3
+ ![Coverage](https://raw.githubusercontent.com/opencitations/rdflib-ocdm/main/test/coverage/coverage.svg)
4
+ ![PyPI](https://img.shields.io/pypi/pyversions/rdflib-ocdm)
5
+ ![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/opencitations/rdflib-ocdm)
6
+
7
+ # rdflib-ocdm
@@ -0,0 +1,23 @@
1
+ [tool.poetry]
2
+ name = "rdflib-ocdm"
3
+ version = "0.1.0"
4
+ description = ""
5
+ authors = ["arcangelo7 <arcangelomas@gmail.com>"]
6
+ license = "ISC"
7
+ readme = "README.md"
8
+ packages = [{include = "rdflib_ocdm"}]
9
+
10
+ [tool.poetry.dependencies]
11
+ python = "^3.7.4"
12
+ rdflib = "^6.2.0"
13
+ oc-ocdm = "^7.1.7"
14
+ sparqlwrapper = "^2.0.0"
15
+
16
+
17
+ [tool.poetry.group.dev.dependencies]
18
+ coverage = "^7.2.3"
19
+ coverage-badge = "^1.1.0"
20
+
21
+ [build-system]
22
+ requires = ["poetry-core"]
23
+ build-backend = "poetry.core.masonry.api"
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/python
2
+ # -*- coding: utf-8 -*-
3
+ # Copyright 2023 Arcangelo Massari <arcangelo.massari@unibo.it>
4
+ #
5
+ # Permission to use, copy, modify, and/or distribute this software for any purpose
6
+ # with or without fee is hereby granted, provided that the above copyright notice
7
+ # and this permission notice appear in all copies.
8
+ #
9
+ # THE SOFTWARE IS PROVIDED 'AS IS' AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
10
+ # REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
11
+ # FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT,
12
+ # OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
13
+ # DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
14
+ # ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
15
+ # SOFTWARE.
@@ -0,0 +1,174 @@
1
+ #!/usr/bin/python
2
+ # -*- coding: utf-8 -*-
3
+ # Copyright (c) 2016, Silvio Peroni <essepuntato@gmail.com>
4
+ # Copyright (c) 2023, Arcangelo Massari <arcangelo.massari@unibo.it>
5
+ #
6
+ # Permission to use, copy, modify, and/or distribute this software for any purpose
7
+ # with or without fee is hereby granted, provided that the above copyright notice
8
+ # and this permission notice appear in all copies.
9
+ #
10
+ # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
11
+ # REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
12
+ # FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT,
13
+ # OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
14
+ # DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
15
+ # ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
16
+ # SOFTWARE.
17
+
18
+
19
+ from __future__ import annotations
20
+
21
+ from abc import ABC
22
+ from typing import TYPE_CHECKING
23
+
24
+ from rdflib import RDF, RDFS, Graph, Literal, URIRef
25
+
26
+ from rdflib_ocdm.support import create_literal, create_type
27
+
28
+ if TYPE_CHECKING:
29
+ from typing import ClassVar, Dict, Iterable, List, Optional
30
+
31
+ class AbstractEntity(ABC):
32
+ """
33
+ Abstract class which represents a generic entity.
34
+ It sits at the top of the entity class hierarchy.
35
+ """
36
+
37
+ short_name_to_type_iri: ClassVar[Dict[str, URIRef]] = {}
38
+
39
+ def __init__(self) -> None:
40
+ """
41
+ Constructor of the ``AbstractEntity`` class.
42
+ """
43
+ self.g: Graph = Graph()
44
+ self.res: URIRef = URIRef("")
45
+
46
+ def remove_every_triple(self) -> None:
47
+ """
48
+ Remover method that removes every triple from the current entity.
49
+
50
+ :return: None
51
+ """
52
+ self.g.remove((None, None, None))
53
+
54
+ # LABEL
55
+ def get_label(self) -> Optional[str]:
56
+ """
57
+ Getter method corresponding to the ``rdfs:label`` RDF predicate.
58
+
59
+ :return: The requested value if found, None otherwise
60
+ """
61
+ return self._get_literal(RDFS.label)
62
+
63
+ def create_label(self, string: str) -> None:
64
+ """
65
+ Setter method corresponding to the ``rdfs:label`` RDF predicate.
66
+
67
+ **WARNING: this is a functional property, hence any existing value will be overwritten!**
68
+
69
+ :param string: The value that will be set as the object of the property related to this method
70
+ :type string: str
71
+ :return: None
72
+ """
73
+ self.remove_label()
74
+ self._create_literal(RDFS.label, string)
75
+
76
+ def remove_label(self) -> None:
77
+ """
78
+ Remover method corresponding to the ``rdfs:label`` RDF predicate.
79
+
80
+ :return: None
81
+ """
82
+ self.g.remove((self.res, RDFS.label, None))
83
+
84
+ def _create_literal(self, p: URIRef, s: str, dt: URIRef = None, nor: bool = True) -> None:
85
+ """
86
+ Adds an RDF triple with a literal object inside the graph of the entity
87
+
88
+ :param p: The predicate
89
+ :type p: URIRef
90
+ :param s: The string to add as a literal value
91
+ :type s: str
92
+ :param dt: The object's datatype, if present
93
+ :type dt: URIRef, optional
94
+ :param nor: Whether to normalize the graph or not
95
+ :type nor: bool, optional
96
+ :return: None
97
+ """
98
+ create_literal(self.g, self.res, p, s, dt, nor)
99
+
100
+ # TYPE
101
+ def get_types(self) -> List[URIRef]:
102
+ """
103
+ Getter method corresponding to the ``rdf:type`` RDF predicate.
104
+
105
+ :return: A list containing the requested values if found, None otherwise
106
+ """
107
+ uri_list: List[URIRef] = self._get_multiple_uri_references(RDF.type)
108
+ return uri_list
109
+
110
+ def _create_type(self, res_type: URIRef, identifier: str = None) -> None:
111
+ """
112
+ Setter method corresponding to the ``rdf:type`` RDF predicate.
113
+
114
+ :param res_type: The value that will be set as the object of the property related to this method
115
+ :type res_type: URIRef
116
+ :return: None
117
+ """
118
+ create_type(self.g, self.res, res_type, identifier)
119
+
120
+ def remove_type(self) -> None:
121
+ """
122
+ Remover method corresponding to the ``rdf:type`` RDF predicate.
123
+
124
+ :return: None
125
+ """
126
+ self.g.remove((self.res, RDF.type, None))
127
+
128
+ # Overrides __str__ method
129
+ def __str__(self) -> str:
130
+ return str(self.res)
131
+
132
+ def add_triples(self, iterable_of_triples: Iterable) -> None:
133
+ """
134
+ A utility method that allows to add a batch of triples into the graph of the entity.
135
+
136
+ **WARNING: Only triples that have this entity as their subject will be imported!**
137
+
138
+ :param iterable_of_triples: A collection of triples to be added to the entity
139
+ :type iterable_of_triples: Iterable
140
+ :return: None
141
+ """
142
+ for s, p, o in iterable_of_triples:
143
+ if s == self.res: # This guarantees that only triples belonging to the resource will be added
144
+ self.g.add((s, p, o))
145
+
146
+ def _get_literal(self, predicate: URIRef) -> Optional[str]:
147
+ result: Optional[str] = None
148
+ for o in self.g.objects(self.res, predicate):
149
+ if type(o) == Literal:
150
+ result = str(o)
151
+ break
152
+ return result
153
+
154
+ def _get_multiple_literals(self, predicate: URIRef) -> List[str]:
155
+ result: List[str] = []
156
+ for o in self.g.objects(self.res, predicate):
157
+ if type(o) == Literal:
158
+ result.append(str(o))
159
+ return result
160
+
161
+ def _get_uri_reference(self, predicate: URIRef) -> Optional[URIRef]:
162
+ result: Optional[URIRef] = None
163
+ for o in self.g.objects(self.res, predicate):
164
+ if type(o) == URIRef:
165
+ result = o
166
+ break
167
+ return result
168
+
169
+ def _get_multiple_uri_references(self, predicate: URIRef) -> List[URIRef]:
170
+ result: List[URIRef] = []
171
+ for o in self.g.objects(self.res, predicate):
172
+ if type(o) == URIRef:
173
+ result.append(o)
174
+ return result
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/python
2
+ # -*- coding: utf-8 -*-
3
+ # Copyright 2023 Arcangelo Massari <arcangelo.massari@unibo.it>
4
+ #
5
+ # Permission to use, copy, modify, and/or distribute this software for any purpose
6
+ # with or without fee is hereby granted, provided that the above copyright notice
7
+ # and this permission notice appear in all copies.
8
+ #
9
+ # THE SOFTWARE IS PROVIDED 'AS IS' AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
10
+ # REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
11
+ # FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT,
12
+ # OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
13
+ # DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
14
+ # ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
15
+ # SOFTWARE.
@@ -0,0 +1,61 @@
1
+ #!/usr/bin/python
2
+ # -*- coding: utf-8 -*-
3
+ # Copyright (c) 2016, Silvio Peroni <essepuntato@gmail.com>
4
+ #
5
+ # Permission to use, copy, modify, and/or distribute this software for any purpose
6
+ # with or without fee is hereby granted, provided that the above copyright notice
7
+ # and this permission notice appear in all copies.
8
+ #
9
+ # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
10
+ # REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
11
+ # FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT,
12
+ # OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
13
+ # DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
14
+ # ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
15
+ # SOFTWARE.
16
+ from abc import ABC, abstractmethod
17
+
18
+
19
+ class CounterHandler(ABC):
20
+ """Abstract class representing the interface for every concrete counter handler."""
21
+
22
+ @abstractmethod
23
+ def set_counter(self, new_value: int, entity_name: str) -> None:
24
+ """
25
+ Method signature for concrete implementations that allow setting the counter value
26
+ of provenance entities.
27
+
28
+ :param new_value: The new counter value to be set
29
+ :type new_value: int
30
+ :param entity_name: The entity name
31
+ :type entity_name: str
32
+ :raises NotImplementedError: always
33
+ :return: None
34
+ """
35
+ raise NotImplementedError
36
+
37
+ @abstractmethod
38
+ def read_counter(self, entity_name: str) -> int:
39
+ """
40
+ Method signature for concrete implementations that allow reading the counter value
41
+ of graph and provenance entities.
42
+
43
+ :param entity_name: The entity name
44
+ :type entity_name: str
45
+ :raises NotImplementedError: always
46
+ :return: The requested counter value.
47
+ """
48
+ raise NotImplementedError
49
+
50
+ @abstractmethod
51
+ def increment_counter(self, entity_name: str) -> int:
52
+ """
53
+ Method signature for concrete implementations that allow incrementing by one unit
54
+ the counter value of graph and provenance entities.
55
+
56
+ :param entity_name: The entity name
57
+ :type entity_name: str
58
+ :raises NotImplementedError: always
59
+ :return: The newly-updated (already incremented) counter value.
60
+ """
61
+ raise NotImplementedError
@@ -0,0 +1,125 @@
1
+ #!/usr/bin/python
2
+ # -*- coding: utf-8 -*-
3
+ # Copyright (c) 2016, Silvio Peroni <essepuntato@gmail.com>
4
+ #
5
+ # Permission to use, copy, modify, and/or distribute this software for any purpose
6
+ # with or without fee is hereby granted, provided that the above copyright notice
7
+ # and this permission notice appear in all copies.
8
+ #
9
+ # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
10
+ # REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
11
+ # FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT,
12
+ # OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
13
+ # DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
14
+ # ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
15
+ # SOFTWARE.
16
+ from __future__ import annotations
17
+
18
+ import json
19
+ import os
20
+
21
+ from rdflib_ocdm.counter_handler.counter_handler import CounterHandler
22
+ from rdflib_ocdm.support import is_string_empty
23
+
24
+
25
+ class FilesystemCounterHandler(CounterHandler):
26
+ """A concrete implementation of the ``CounterHandler`` interface that persistently stores
27
+ the counter values within the filesystem."""
28
+
29
+ def __init__(self, info_dir: str) -> None:
30
+ """
31
+ Constructor of the ``FilesystemCounterHandler`` class.
32
+
33
+ :param info_dir: The path to the folder that does/will contain the counter values.
34
+ :type info_dir: str
35
+ :raises ValueError: if ``info_dir`` is None or an empty string.
36
+ """
37
+ if info_dir is None or is_string_empty(info_dir):
38
+ raise ValueError("info_dir parameter is required!")
39
+
40
+ if info_dir[-1] != os.sep:
41
+ info_dir += os.sep
42
+
43
+ self.info_dir: str = info_dir
44
+ self.prov_files = dict()
45
+ self.provenance_index_filename = 'provenance_index.json'
46
+
47
+ def set_counter(self, new_value: int, entity_name: str) -> None:
48
+ """
49
+ It allows to set the counter value of provenance entities.
50
+
51
+ :param new_value: The new counter value to be set
52
+ :type new_value: int
53
+ :param entity_name: The entity name
54
+ :type entity_name: str
55
+ :raises ValueError: if ``new_value`` is a negative integer.
56
+ :return: None
57
+ """
58
+ entity_name = str(entity_name)
59
+ if new_value < 0:
60
+ raise ValueError("new_value must be a non negative integer!")
61
+ file_path: str = self._get_prov_path()
62
+ self.__initialize_file_if_not_existing(file_path, entity_name)
63
+ with open(file_path, 'r', encoding='utf8') as file:
64
+ data = json.load(file)
65
+ with open(file_path, 'w', encoding='utf8') as outfile:
66
+ data[entity_name] = new_value
67
+ outfile.write(data)
68
+
69
+ def read_counter(self, entity_name: str) -> int:
70
+ """
71
+ It allows to read the counter value of provenance entities.
72
+
73
+ :param entity_name: The entity name
74
+ :type entity_name: str
75
+ :return: The requested counter value.
76
+ """
77
+ entity_name = str(entity_name)
78
+ file_path: str = self._get_prov_path()
79
+ return self._read_number(file_path, entity_name)
80
+
81
+ def increment_counter(self, entity_name: str) -> int:
82
+ """
83
+ It allows to increment the counter value of provenance entities by one unit.
84
+
85
+ :param entity_name: The entity name
86
+ :type entity_name: str
87
+ :return: The newly-updated (already incremented) counter value.
88
+ """
89
+ entity_name = str(entity_name)
90
+ file_path: str = self._get_prov_path()
91
+ return self._add_number(file_path, entity_name)
92
+
93
+ def _get_prov_path(self) -> str:
94
+ return os.path.join(self.info_dir, self.provenance_index_filename)
95
+
96
+ def __initialize_file_if_not_existing(self, file_path: str, entity_name: str):
97
+ entity_name = str(entity_name)
98
+ if not os.path.exists(os.path.dirname(file_path)):
99
+ os.makedirs(os.path.dirname(file_path))
100
+ if not os.path.isfile(file_path):
101
+ with open(file_path, 'w', encoding='utf8') as outfile:
102
+ json_object = json.dumps({entity_name: 0}, ensure_ascii=False, indent=None)
103
+ outfile.write(json_object)
104
+
105
+ def _read_number(self, file_path: str, entity_name: str) -> int:
106
+ self.__initialize_file_if_not_existing(file_path, entity_name)
107
+ with open(file_path, 'r', encoding='utf8') as file:
108
+ data = json.load(file)
109
+ if entity_name in data:
110
+ self.prov_files[entity_name] = data[entity_name]
111
+ else:
112
+ self.prov_files[entity_name] = 0
113
+ return self.prov_files[entity_name]
114
+
115
+ def _add_number(self, file_path: str, entity_name: str) -> int:
116
+ self.__initialize_file_if_not_existing(file_path, entity_name)
117
+ cur_number = self._read_number(file_path, entity_name)
118
+ cur_number += 1
119
+ with open(file_path, 'r', encoding='utf8') as file:
120
+ data = json.load(file)
121
+ with open(file_path, 'w', encoding='utf8') as outfile:
122
+ data[entity_name] = cur_number
123
+ json_object = json.dumps(data, ensure_ascii=False, indent=None)
124
+ outfile.write(json_object)
125
+ return cur_number
@@ -0,0 +1,81 @@
1
+ #!/usr/bin/python
2
+ # -*- coding: utf-8 -*-
3
+ # Copyright (c) 2016, Silvio Peroni <essepuntato@gmail.com>
4
+ #
5
+ # Permission to use, copy, modify, and/or distribute this software for any purpose
6
+ # with or without fee is hereby granted, provided that the above copyright notice
7
+ # and this permission notice appear in all copies.
8
+ #
9
+ # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
10
+ # REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
11
+ # FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT,
12
+ # OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
13
+ # DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
14
+ # ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
15
+ # SOFTWARE.
16
+
17
+ from __future__ import annotations
18
+
19
+ from typing import TYPE_CHECKING
20
+
21
+ if TYPE_CHECKING:
22
+ from typing import List, Dict
23
+
24
+ from rdflib_ocdm.counter_handler.counter_handler import CounterHandler
25
+
26
+
27
+ class InMemoryCounterHandler(CounterHandler):
28
+ """A concrete implementation of the ``CounterHandler`` interface that temporarily stores
29
+ the counter values in the volatile system memory."""
30
+
31
+ def __init__(self) -> None:
32
+ """
33
+ Constructor of the ``InMemoryCounterHandler`` class.
34
+ """
35
+ self.prov_counters = dict()
36
+
37
+ def set_counter(self, new_value: int, entity_name: str) -> None:
38
+ """
39
+ It allows to set the counter value of graph and provenance entities.
40
+
41
+ :param new_value: The new counter value to be set
42
+ :type new_value: int
43
+ :param entity_name: The entity name
44
+ :type entity_name: str
45
+ :raises ValueError: if ``new_value`` is a negative integer.
46
+ :return: None
47
+ """
48
+ entity_name = str(entity_name)
49
+ if new_value < 0:
50
+ raise ValueError("new_value must be a non negative integer!")
51
+ self.prov_counters[entity_name] = new_value
52
+
53
+ def read_counter(self, entity_name: str) -> int:
54
+ """
55
+ It allows to read the counter value of provenance entities.
56
+
57
+ :param entity_name: The entity name
58
+ :type entity_name: str
59
+ :return: The requested counter value.
60
+ """
61
+ entity_name = str(entity_name)
62
+ if entity_name in self.prov_counters:
63
+ return self.prov_counters[entity_name]
64
+ else:
65
+ self.prov_counters[entity_name] = 0
66
+ return 0
67
+
68
+ def increment_counter(self, entity_name: str) -> int:
69
+ """
70
+ It allows to increment the counter value of graph and provenance entities by one unit.
71
+
72
+ :param entity_name: The entity name
73
+ :type entity_name: str
74
+ :return: The newly-updated (already incremented) counter value.
75
+ """
76
+ entity_name = str(entity_name)
77
+ if entity_name in self.prov_counters:
78
+ self.prov_counters[entity_name] += 1
79
+ else:
80
+ self.prov_counters[entity_name] = 1
81
+ return self.prov_counters[entity_name]
@@ -0,0 +1,87 @@
1
+ #!/usr/bin/python
2
+ # -*- coding: utf-8 -*-
3
+ # Copyright (c) 2023, Arcangelo Massari <arcangelo.massari@unibo.it>
4
+ #
5
+ # Permission to use, copy, modify, and/or distribute this software for any purpose
6
+ # with or without fee is hereby granted, provided that the above copyright notice
7
+ # and this permission notice appear in all copies.
8
+ #
9
+ # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
10
+ # REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
11
+ # FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT,
12
+ # OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
13
+ # DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
14
+ # ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
15
+ # SOFTWARE.
16
+
17
+ import sqlite3
18
+
19
+ from rdflib_ocdm.counter_handler.counter_handler import CounterHandler
20
+
21
+
22
+ class SqliteCounterHandler(CounterHandler):
23
+ """A concrete implementation of the ``CounterHandler`` interface that persistently stores
24
+ the counter values within a SQLite database."""
25
+
26
+ def __init__(self, database: str) -> None:
27
+ """
28
+ Constructor of the ``SqliteCounterHandler`` class.
29
+
30
+ :param database: The name of the database
31
+ :type info_dir: str
32
+ """
33
+ sqlite3.threadsafety = 3
34
+ self.con = sqlite3.connect(database)
35
+ self.cur = self.con.cursor()
36
+ self.cur.execute("""CREATE TABLE IF NOT EXISTS info(
37
+ entity TEXT PRIMARY KEY,
38
+ count INTEGER)""")
39
+
40
+ def set_counter(self, new_value: int, entity_name: str) -> None:
41
+ """
42
+ It allows to set the counter value of provenance entities.
43
+
44
+ :param new_value: The new counter value to be set
45
+ :type new_value: int
46
+ :param entity_name: The entity name
47
+ :type entity_name: str
48
+ :raises ValueError: if ``new_value`` is a negative integer.
49
+ :return: None
50
+ """
51
+ entity_name = str(entity_name)
52
+ if new_value < 0:
53
+ raise ValueError("new_value must be a non negative integer!")
54
+ self.cur.execute(f"INSERT OR REPLACE INTO info (entity, count) VALUES ('{entity_name}', {new_value})")
55
+ self.con.commit()
56
+
57
+ def read_counter(self, entity_name: str) -> int:
58
+ """
59
+ It allows to read the counter value of provenance entities.
60
+
61
+ :param entity_name: The entity name
62
+ :type entity_name: str
63
+ :return: The requested counter value.
64
+ """
65
+ entity_name = str(entity_name)
66
+ result = self.cur.execute(f"SELECT count FROM info WHERE entity='{entity_name}'")
67
+ rows = result.fetchall()
68
+ if len(rows) == 1:
69
+ return rows[0][0]
70
+ elif len(rows) == 0:
71
+ return 0
72
+ else:
73
+ raise(Exception("There is more than one counter for this entity. The databse id broken"))
74
+
75
+ def increment_counter(self, entity_name: str) -> int:
76
+ """
77
+ It allows to increment the counter value of graph and provenance entities by one unit.
78
+
79
+ :param entity_name: The entity name
80
+ :type entity_name: str
81
+ :return: The newly-updated (already incremented) counter value.
82
+ """
83
+ entity_name = str(entity_name)
84
+ cur_count = self.read_counter(entity_name)
85
+ count = cur_count + 1
86
+ self.set_counter(count, entity_name)
87
+ return count