15
15
import os
16
16
import sqlite3
17
17
import sys
18
+ from contextlib import closing
18
19
19
20
20
21
class Logger (object ):
@@ -71,15 +72,22 @@ def code(value):
71
72
72
73
log_level = None # log level
73
74
dir_logs = None
74
- sqlite_cur = None
75
75
sqlite_file = None
76
- sqlite_conn = None
76
+ connection = None
77
77
path_txt = None
78
78
file_txt = None
79
79
name = None
80
80
max_lineno_width = 3
81
81
82
- def __new__ (cls , dir_logs = None , name = 'logs' ):
82
+ def __new__ (cls , dir_logs = None , name = None ):
83
+ return Logger ._get_instance (dir_logs , name )
84
+
85
+ def __call__ (self , * args , ** kwargs ):
86
+ return self .log_message (* args , ** kwargs , stack_displacement = 2 )
87
+
88
+ @staticmethod
89
+ def _get_instance (dir_logs = None , name = None ):
90
+ name = name or 'logs'
83
91
if Logger ._instance is None :
84
92
Logger ._instance = object .__new__ (Logger )
85
93
Logger ._instance .set_level (Logger ._instance .INFO )
@@ -90,16 +98,12 @@ def __new__(cls, dir_logs=None, name='logs'):
90
98
Logger ._instance .path_txt = os .path .join (dir_logs , '{}.txt' .format (name ))
91
99
Logger ._instance .file_txt = open (os .path .join (dir_logs , '{}.txt' .format (name )), 'a+' )
92
100
Logger ._instance .sqlite_file = os .path .join (dir_logs , '{}.sqlite' .format (name ))
93
- Logger ._instance .init_sqlite ()
94
101
else :
95
102
Logger ._instance .log_message ('No logs files will be created (dir_logs attribute is empty)' ,
96
103
log_level = Logger .WARNING )
97
104
98
105
return Logger ._instance
99
106
100
- def __call__ (self , * args , ** kwargs ):
101
- return self .log_message (* args , ** kwargs , stack_displacement = 2 )
102
-
103
107
def set_level (self , log_level ):
104
108
self .log_level = log_level
105
109
@@ -173,24 +177,24 @@ def print_subitem(prefix, subdictionary, stack_displacement=3):
173
177
self .log_message ('{}: {}' .format (group , description ), log_level = log_level , stack_displacement = stack_displacement )
174
178
print_subitem (' ' , dictionary , stack_displacement = stack_displacement + 1 )
175
179
176
- def _execute (self , statement , parameters = None , commit = True ):
180
+ def _execute (self , statement , parameters = None , commit = True , cursor = None ):
177
181
assert parameters is None or isinstance (parameters , tuple )
178
182
parameters = parameters or ()
179
- return_value = self . sqlite_cur .execute (statement , parameters )
183
+ return_value = cursor .execute (statement , parameters )
180
184
if commit :
181
- self .sqlite_conn .commit ()
185
+ self .get_conn () .commit ()
182
186
return return_value
183
187
184
- def _run_query (self , query , parameters = None ):
185
- return self ._execute (query , parameters , commit = False )
188
+ def _run_query (self , query , parameters = None , cursor = None ):
189
+ return self ._execute (query , parameters , commit = False , cursor = cursor )
186
190
187
191
def _get_internal_table_name (self , table_name ):
188
192
return f'_{ table_name } '
189
193
190
- def _check_table_exists (self , table_name ):
194
+ def _check_table_exists (self , table_name , cursor = None ):
191
195
table_name = self ._get_internal_table_name (table_name )
192
196
query = "SELECT name FROM sqlite_master WHERE type='table' AND name=?"
193
- return self ._run_query (query , (table_name ,))
197
+ return self ._run_query (query , (table_name ,), cursor = cursor )
194
198
195
199
def _create_table (self , table_name ):
196
200
table_name = self ._get_internal_table_name (table_name )
@@ -200,15 +204,17 @@ def _create_table(self, table_name):
200
204
"__timestamp" DATETIME DEFAULT CURRENT_TIMESTAMP
201
205
);
202
206
"""
203
- self ._execute (statement )
207
+ with closing (self .get_conn ().cursor ()) as cursor :
208
+ self ._execute (statement , cursor = cursor )
204
209
205
210
def _list_columns (self , table_name ):
206
211
table_name = self ._get_internal_table_name (table_name )
207
212
query = "SELECT name FROM PRAGMA_TABLE_INFO(?)"
208
- qry_cur = self ._run_query (query , (table_name ,))
209
- columns = (res [0 ] for res in qry_cur )
210
- # remove __id and __timestamp columns
211
- columns = [c for c in columns if not c .startswith ('__' )]
213
+ with closing (self .get_conn ().cursor ()) as cursor :
214
+ qry_cur = self ._run_query (query , (table_name ,), cursor = cursor )
215
+ columns = (res [0 ] for res in qry_cur )
216
+ # remove __id and __timestamp columns
217
+ columns = [c for c in columns if not c .startswith ('__' )]
212
218
return columns
213
219
214
220
@staticmethod
@@ -223,7 +229,8 @@ def _add_column(self, table_name, column_name, value_sample=None):
223
229
table_name = self ._get_internal_table_name (table_name )
224
230
value_type = self ._get_data_type (value_sample )
225
231
statement = f'ALTER TABLE { table_name } ADD COLUMN "{ column_name } " { value_type } '
226
- return self ._execute (statement )
232
+ with closing (self .get_conn ().cursor ()) as cursor :
233
+ return self ._execute (statement , cursor = cursor )
227
234
228
235
def _flatten_dict (self , dictionary , flatten_dict = None , prefix = '' ):
229
236
flatten_dict = flatten_dict if flatten_dict is not None else {}
@@ -247,51 +254,58 @@ def _insert_row(self, table_name, flat_dictionary):
247
254
value_placeholder = ', ' .join (['?' ] * len (columns ))
248
255
statement = f'INSERT INTO { table_name } ({ column_string } ) VALUES({ value_placeholder } )'
249
256
parameters = tuple (val for val in flat_dictionary .values ())
250
- return self ._execute (statement , parameters )
257
+ with closing (self .get_conn ().cursor ()) as cursor :
258
+ return self ._execute (statement , parameters , cursor = cursor )
251
259
252
260
def log_dict (self , group , dictionary , description = '' , stack_displacement = 2 , should_print = False , log_level = SUMMARY ):
253
261
if log_level < self .log_level :
254
262
return - 1
255
263
256
264
flat_dictionary = self ._flatten_dict (dictionary )
257
- if self ._check_table_exists (group ).fetchone ():
258
- columns = self ._list_columns (group )
259
- for key in flat_dictionary :
260
- if key not in columns :
261
- self .log_message (f'Key "{ key } " is unknown. New keys are not allowed' , log_level = self .ERROR )
262
- for column_name in columns :
263
- if column_name not in flat_dictionary :
264
- self .log_message (f'Key "{ column_name } " not in the dictionary to be logged' , log_level = self .ERROR )
265
- else :
266
- self ._create_table (group )
267
- for key , value in flat_dictionary .items ():
268
- self ._add_column (group , key , value )
265
+ with closing (self .get_conn ().cursor ()) as cursor :
266
+ if self ._check_table_exists (group , cursor = cursor ).fetchone ():
267
+ columns = self ._list_columns (group )
268
+ for key in flat_dictionary :
269
+ if key not in columns :
270
+ self .log_message (f'Key "{ key } " is unknown. New keys are not allowed' , log_level = self .ERROR )
271
+ for column_name in columns :
272
+ if column_name not in flat_dictionary :
273
+ self .log_message (f'Key "{ column_name } " not in the dictionary to be logged' , log_level = self .ERROR )
274
+ else :
275
+ self ._create_table (group )
276
+ for key , value in flat_dictionary .items ():
277
+ self ._add_column (group , key , value )
269
278
270
279
self ._insert_row (group , flat_dictionary )
271
280
272
281
if should_print :
273
282
self .log_dict_message (group , dictionary , description , stack_displacement + 1 , log_level )
274
283
275
- def select (self , group , columns = None ):
276
- table_name = self ._get_internal_table_name (group )
277
- table_columns = self ._list_columns (group )
284
+ @staticmethod
285
+ def select (group , columns = None ):
286
+ logger = Logger ._get_instance (dir_logs = None , name = None )
287
+ table_name = logger ._get_internal_table_name (group )
288
+ table_columns = logger ._list_columns (group )
278
289
if columns is None :
279
290
column_string = '*'
280
291
else :
281
292
for c in columns :
282
293
if c not in table_columns :
283
- self .log_message (f'Unknown column "{ c } "' , log_level = self .ERROR )
294
+ logger .log_message (f'Unknown column "{ c } "' , log_level = Logger .ERROR )
284
295
column_string = ', ' .join ([f'"{ c } "' for c in columns ])
285
296
statement = f'SELECT { column_string } FROM { table_name } '
286
- return self ._execute (statement )
287
-
288
- def init_sqlite (self ):
289
- pre_existing = os .path .isfile (self .sqlite_file )
290
- self .sqlite_conn = sqlite3 .connect (self .sqlite_file )
291
- self .sqlite_cur = self .sqlite_conn .cursor ()
292
- if not pre_existing :
293
- self ._create_table ('bootstrap' )
297
+ with closing (logger .get_conn ().cursor ()) as cursor :
298
+ return logger ._execute (statement , cursor = cursor , commit = False ).fetchall ()
299
+
300
+ def get_conn (self ):
301
+ if self .connection is None :
302
+ pre_existing = os .path .isfile (self .sqlite_file )
303
+ connection = sqlite3 .connect (self .sqlite_file , check_same_thread = False , isolation_level = 'IMMEDIATE' )
304
+ self .connection = connection
305
+ if not pre_existing :
306
+ self ._create_table ('bootstrap' )
307
+ return self .connection
294
308
295
309
def flush (self ):
296
310
if self .dir_logs :
297
- self .sqlite_conn .commit ()
311
+ self .get_conn () .commit ()
0 commit comments