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

1#!/usr/bin/python 

2 

3# SPDX-FileCopyrightText: 2023-2025 Arcangelo Massari <arcangelo.massari@unibo.it> 

4# 

5# SPDX-License-Identifier: ISC 

6 

7import sqlite3 

8import urllib.parse 

9 

10from rdflib_ocdm.counter_handler.counter_handler import CounterHandler 

11 

12 

13class SqliteCounterHandler(CounterHandler): 

14 """A concrete implementation of the ``CounterHandler`` interface that persistently stores 

15 the counter values within a SQLite database.""" 

16 

17 def __init__(self, database: str) -> None: 

18 """ 

19 Constructor of the ``SqliteCounterHandler`` class. 

20 

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)""") 

30 

31 def set_counter(self, new_value: int, entity_name: str) -> None: 

32 """ 

33 It allows to set the counter value of provenance entities. 

34 

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() 

47 

48 def read_counter(self, entity_name: str) -> int: 

49 """ 

50 It allows to read the counter value of provenance entities. 

51 

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")) 

65 

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. 

69 

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 

78 

79 def close(self) -> None: 

80 """ 

81 Closes the database connection. 

82 

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 

95 

96 def __del__(self) -> None: 

97 """ 

98 Destructor that ensures the database connection is closed. 

99 

100 :return: None 

101 """ 

102 self.close() 

103 

104 def __enter__(self): 

105 """ 

106 Context manager entry point. 

107 

108 :return: self 

109 """ 

110 return self 

111 

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. 

115 

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()