Coverage for rdflib_ocdm / counter_handler / sqlite_counter_handler.py: 89%
46 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-21 12:35 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-21 12:35 +0000
1#!/usr/bin/python
3# SPDX-FileCopyrightText: 2023-2025 Arcangelo Massari <arcangelo.massari@unibo.it>
4#
5# SPDX-License-Identifier: ISC
7import sqlite3
8import urllib.parse
10from rdflib_ocdm.counter_handler.counter_handler import CounterHandler
13class SqliteCounterHandler(CounterHandler):
14 """A concrete implementation of the ``CounterHandler`` interface that persistently stores
15 the counter values within a SQLite database."""
17 def __init__(self, database: str) -> None:
18 """
19 Constructor of the ``SqliteCounterHandler`` class.
21 :param database: The name of the database
22 :type info_dir: str
23 """
24 sqlite3.threadsafety = 3
25 self.con = sqlite3.connect(database, check_same_thread=False)
26 self.cur = self.con.cursor()
27 self.cur.execute("""CREATE TABLE IF NOT EXISTS info(
28 entity TEXT PRIMARY KEY,
29 count INTEGER)""")
31 def set_counter(self, new_value: int, entity_name: str) -> None:
32 """
33 It allows to set the counter value of provenance entities.
35 :param new_value: The new counter value to be set
36 :type new_value: int
37 :param entity_name: The entity name
38 :type entity_name: str
39 :raises ValueError: if ``new_value`` is a negative integer.
40 :return: None
41 """
42 entity_name = urllib.parse.quote((str(entity_name)))
43 if new_value < 0:
44 raise ValueError("new_value must be a non negative integer!")
45 self.cur.execute(f"INSERT OR REPLACE INTO info (entity, count) VALUES ('{entity_name}', {new_value})")
46 self.con.commit()
48 def read_counter(self, entity_name: str) -> int:
49 """
50 It allows to read the counter value of provenance entities.
52 :param entity_name: The entity name
53 :type entity_name: str
54 :return: The requested counter value.
55 """
56 entity_name = urllib.parse.quote(str(entity_name))
57 result = self.cur.execute(f"SELECT count FROM info WHERE entity='{entity_name}'")
58 rows = result.fetchall()
59 if len(rows) == 1:
60 return rows[0][0]
61 elif len(rows) == 0:
62 return 0
63 else:
64 raise(Exception("There is more than one counter for this entity. The databse id broken"))
66 def increment_counter(self, entity_name: str) -> int:
67 """
68 It allows to increment the counter value of graph and provenance entities by one unit.
70 :param entity_name: The entity name
71 :type entity_name: str
72 :return: The newly-updated (already incremented) counter value.
73 """
74 cur_count = self.read_counter(entity_name)
75 count = cur_count + 1
76 self.set_counter(count, entity_name)
77 return count
79 def close(self) -> None:
80 """
81 Closes the database connection.
83 :return: None
84 """
85 try:
86 if hasattr(self, 'cur') and self.cur: 86 ↛ 90line 86 didn't jump to line 90 because the condition on line 86 was always true
87 self.cur.close()
88 except (sqlite3.ProgrammingError, Exception):
89 pass
90 try:
91 if hasattr(self, 'con') and self.con: 91 ↛ exitline 91 didn't return from function 'close' because the condition on line 91 was always true
92 self.con.close()
93 except (sqlite3.ProgrammingError, Exception):
94 pass
96 def __del__(self) -> None:
97 """
98 Destructor that ensures the database connection is closed.
100 :return: None
101 """
102 self.close()
104 def __enter__(self):
105 """
106 Context manager entry point.
108 :return: self
109 """
110 return self
112 def __exit__(self, exc_type, exc_val, exc_tb) -> None: # noqa: ARG002
113 """
114 Context manager exit point that ensures the database connection is closed.
116 :param exc_type: Exception type
117 :param exc_val: Exception value
118 :param exc_tb: Exception traceback
119 :return: None
120 """
121 self.close()