pyquoks 1.3.2.1__py3-none-any.whl → 2.0.0__py3-none-any.whl

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.
pyquoks/data.py CHANGED
@@ -1,469 +1,482 @@
1
1
  from __future__ import annotations
2
- import configparser, datetime, logging, sqlite3, json, sys, io, os
3
- import requests, PIL.Image, PIL.ImageDraw
4
- from . import utils
2
+ import configparser, datetime, logging, sqlite3, typing, json, sys, io, os
3
+ import requests, PIL.Image
4
+ import pyquoks.utils
5
5
 
6
6
 
7
7
  # region Providers
8
8
 
9
- class IDataProvider:
9
+ class DataProvider(pyquoks.utils._HasRequiredAttributes):
10
10
  """
11
11
  Class for providing data from JSON-like files
12
- """
13
-
14
- _PATH: str = utils.get_path("data/")
15
- """
16
- Path to the directory with JSON-like files
17
- """
18
12
 
19
- _FILENAME: str = "{0}.json"
20
- """
21
- Filename of JSON-like files
22
- """
13
+ **Required attributes**::
23
14
 
24
- _DATA_VALUES: dict[str, type]
25
- """
26
- Dictionary with filenames and containers
15
+ _OBJECTS = {"users": UsersContainer}
27
16
 
28
- Example:
29
- _DATA_VALUES = {"users": UsersContainer}
30
- """
17
+ # Predefined:
31
18
 
32
- def __init__(self) -> None:
33
- for filename, container in self._DATA_VALUES.items():
34
- try:
35
- with open(self._PATH + self._FILENAME.format(filename), "rb") as file:
36
- setattr(self, filename, container(json.loads(file.read())))
37
- except:
38
- setattr(self, filename, None)
19
+ _PATH = pyquoks.utils.get_path("data/")
39
20
 
21
+ _FILENAME = "{0}.json"
40
22
 
41
- class IConfigProvider:
42
- """
43
- Class for providing data from configuration file
23
+ Attributes:
24
+ _OBJECTS: Dictionary with filenames and containers
25
+ _PATH: Path to the directory with JSON-like files
26
+ _FILENAME: Filename of JSON-like files
44
27
  """
45
28
 
46
- class IConfig:
47
- """
48
- Class that represents a section in configuration file
49
- """
50
-
51
- _SECTION: str = None
52
- """
53
- Name of the section in configuration file
29
+ _REQUIRED_ATTRIBUTES = {
30
+ "_OBJECTS",
31
+ "_PATH",
32
+ "_FILENAME",
33
+ }
54
34
 
55
- Example:
56
- _SECTION = "Settings"
57
- """
35
+ _OBJECTS: dict[str, type]
58
36
 
59
- _CONFIG_VALUES: dict[str, type]
60
- """
61
- Dictionary with settings and their types
62
- """
37
+ _PATH: str = pyquoks.utils.get_path("data/")
63
38
 
64
- def __init__(self, parent: IConfigProvider = None) -> None:
65
- if isinstance(parent, IConfigProvider):
66
- self._CONFIG_VALUES = parent._CONFIG_VALUES.get(self._SECTION)
67
-
68
- self._incorrect_content_exception = configparser.ParsingError(
69
- "configuration file is filled incorrectly!"
70
- )
71
- self._config = configparser.ConfigParser()
72
- self._config.read(parent._PATH)
73
-
74
- if not self._config.has_section(self._SECTION):
75
- self._config.add_section(self._SECTION)
76
-
77
- for setting, data_type in self._CONFIG_VALUES.items():
78
- try:
79
- setattr(self, setting, self._config.get(self._SECTION, setting))
80
- except:
81
- self._config.set(self._SECTION, setting, data_type.__name__)
82
- with open(parent._PATH, "w", encoding="utf-8") as file:
83
- self._config.write(file)
84
-
85
- for setting, data_type in self._CONFIG_VALUES.items():
86
- try:
87
- match data_type.__name__:
88
- case "int":
89
- setattr(self, setting, int(getattr(self, setting)))
90
- case "float":
91
- setattr(self, setting, float(getattr(self, setting)))
92
- case "bool":
93
- if getattr(self, setting) not in (str(True), str(False)):
94
- setattr(self, setting, None)
95
- raise self._incorrect_content_exception
96
- else:
97
- setattr(self, setting, getattr(self, setting) == str(True))
98
- case "dict" | "list":
99
- setattr(self, setting, json.loads(getattr(self, setting)))
100
- except:
101
- setattr(self, setting, None)
102
- raise self._incorrect_content_exception
103
-
104
- if not self.values:
105
- raise self._incorrect_content_exception
39
+ _FILENAME: str = "{0}.json"
106
40
 
107
- @property
108
- def values(self) -> dict | None:
109
- """
110
- :return: Values stored in section
111
- """
41
+ def __init__(self) -> None:
42
+ self._check_attributes()
112
43
 
44
+ for filename, object_class in self._OBJECTS.items():
113
45
  try:
114
- return {setting: getattr(self, setting) for setting in self._CONFIG_VALUES.keys()}
46
+ with open(self._PATH + self._FILENAME.format(filename), "rb") as file:
47
+ setattr(self, filename, object_class(json.loads(file.read())))
115
48
  except:
116
- return None
49
+ setattr(self, filename, None)
117
50
 
118
- _PATH: str = utils.get_path("config.ini")
119
- """
120
- Path to the configuration file
121
- """
122
51
 
123
- _CONFIG_VALUES: dict[str, dict[str, type]]
124
- """
125
- Dictionary with sections, their settings and their types
126
-
127
- Example:
128
- _CONFIG_VALUES = {"Settings": {"version": str}}
52
+ class AssetsProvider(pyquoks.utils._HasRequiredAttributes):
129
53
  """
54
+ Class for providing various assets data
130
55
 
131
- _CONFIG_OBJECTS: dict[str, type]
132
- """
133
- Dictionary with names of attributes and child objects
56
+ **Required attributes**::
134
57
 
135
- Example:
136
- _CONFIG_OBJECTS = {"settings": SettingsConfig}
137
- """
58
+ _OBJECTS = {"images": ImagesDirectory, "example": ExampleNetwork}
138
59
 
139
- def __init__(self) -> None:
140
- for name, data_class in self._CONFIG_OBJECTS.items():
141
- setattr(self, name, data_class(self))
60
+ # Predefined:
142
61
 
62
+ _PATH = pyquoks.utils.get_path("assets/")
143
63
 
144
- class IAssetsProvider:
145
- """
146
- Class for providing various assets data
64
+ Attributes:
65
+ _OBJECTS: Dictionary with names of attributes and child objects
66
+ _PATH: Path to the directory with assets folders
147
67
  """
148
68
 
149
- class IDirectory:
69
+ class Directory(pyquoks.utils._HasRequiredAttributes):
150
70
  """
151
71
  Class that represents a directory with various assets
152
- """
153
72
 
154
- _PATH: str = None
155
- """
156
- Path to the directory with assets files
73
+ **Required attributes**::
157
74
 
158
- Example:
159
- _PATH = "images/"
160
- """
75
+ _ATTRIBUTES = {"picture1", "picture2"}
161
76
 
162
- _FILENAME: str = None
163
- """
164
- Filename of assets files
77
+ _PATH = "images/"
165
78
 
166
- Example:
167
79
  _FILENAME = "{0}.png"
168
- """
169
80
 
170
- _NAMES: set[str]
81
+ Attributes:
82
+ _ATTRIBUTES: Names of files in the directory
83
+ _PATH: Path to the directory with assets files
84
+ _FILENAME: Filename of assets files
85
+ _parent: Parent object
171
86
  """
172
- Names of files in the directory
173
87
 
174
- Example:
175
- _NAMES = {"picture1", "picture2"}
176
- """
88
+ _REQUIRED_ATTRIBUTES = {
89
+ "_ATTRIBUTES",
90
+ "_PATH",
91
+ "_FILENAME",
92
+ }
93
+
94
+ _ATTRIBUTES: set[str]
95
+
96
+ _PATH: str
177
97
 
178
- def __init__(self, parent: IAssetsProvider) -> None:
179
- self._PATH = parent._PATH + self._PATH
98
+ _FILENAME: str
180
99
 
181
- if isinstance(parent, IAssetsProvider):
182
- for filename in self._NAMES:
183
- setattr(self, filename, parent.file_image(self._PATH + self._FILENAME.format(filename)))
100
+ _parent: AssetsProvider | None
101
+
102
+ def __init__(self, parent: AssetsProvider = None) -> None:
103
+ self._check_attributes()
104
+
105
+ if parent:
106
+ self._parent = parent
107
+ elif not hasattr(self, "_parent") or not self._parent:
108
+ raise AttributeError("This class cannot be initialized without a parent object!")
109
+
110
+ self._PATH = self._parent._PATH + self._PATH
111
+
112
+ for filename in self._ATTRIBUTES:
113
+ try:
114
+ setattr(self, filename, self._parent.file_image(
115
+ path=self._PATH + self._FILENAME.format(filename),
116
+ ))
117
+ except:
118
+ setattr(self, filename, None)
184
119
 
185
- class INetwork:
120
+ class Network(pyquoks.utils._HasRequiredAttributes):
186
121
  """
187
122
  Class that represents a set of images obtained from a network
188
- """
189
123
 
190
- _URLS: dict[str, str]
191
- """
192
- Dictionary with names of attributes and URLs
124
+ **Required attributes**::
193
125
 
194
- Example:
195
126
  _URLS = {"example": "https://example.com/image.png"}
127
+
128
+ Attributes:
129
+ _URLS: Dictionary with names of attributes and URLs
130
+ _parent: Parent object
196
131
  """
197
132
 
198
- def __init__(self, parent: IAssetsProvider) -> None:
199
- if isinstance(parent, IAssetsProvider):
200
- for name, url in self._URLS.items():
201
- setattr(self, name, parent.network_image(url))
133
+ _REQUIRED_ATTRIBUTES = {
134
+ "_URLS",
135
+ }
202
136
 
203
- _PATH: str = utils.get_path("assets/")
204
- """
205
- Path to the directory with assets folders
206
- """
137
+ _URLS: dict[str, str]
207
138
 
208
- _ASSETS_OBJECTS: dict[str, type]
209
- """
210
- Dictionary with names of attributes and child objects
139
+ _parent: AssetsProvider | None
211
140
 
212
- Example:
213
- _ASSETS_OBJECTS = {"images": ImagesAssets, "example": ExampleNetwork}
214
- """
141
+ def __init__(self, parent: AssetsProvider = None) -> None:
142
+ self._check_attributes()
143
+
144
+ if parent:
145
+ self._parent = parent
146
+ elif not hasattr(self, "_parent") or not self._parent:
147
+ raise AttributeError("This class cannot be initialized without a parent object!")
148
+
149
+ for attribute, url in self._URLS.items():
150
+ try:
151
+ setattr(self, attribute, self._parent.network_image(
152
+ url=url,
153
+ ))
154
+ except:
155
+ setattr(self, attribute, None)
156
+
157
+ _REQUIRED_ATTRIBUTES = {
158
+ "_OBJECTS",
159
+ "_PATH",
160
+ }
161
+
162
+ _OBJECTS: dict[str, type]
163
+
164
+ _PATH: str = pyquoks.utils.get_path("assets/")
215
165
 
216
166
  def __init__(self) -> None:
217
- for name, data_class in self._ASSETS_OBJECTS.items():
218
- setattr(self, name, data_class(self))
167
+ self._check_attributes()
168
+
169
+ for attribute, object_class in self._OBJECTS.items():
170
+ setattr(self, attribute, object_class(self))
219
171
 
220
172
  @staticmethod
221
173
  def file_image(path: str) -> PIL.Image.Image:
222
174
  """
175
+ :param path: Absolute path of the image file
223
176
  :return: Image object from a file
224
177
  """
225
178
 
226
179
  with open(path, "rb") as file:
227
- return PIL.Image.open(io.BytesIO(file.read()))
180
+ return PIL.Image.open(
181
+ fp=io.BytesIO(file.read()),
182
+ )
228
183
 
229
184
  @staticmethod
230
185
  def network_image(url: str) -> PIL.Image.Image:
231
186
  """
187
+ :param url: URL of the image file
232
188
  :return: Image object from a URL
233
189
  """
234
190
 
235
- return PIL.Image.open(io.BytesIO(requests.get(url).content))
236
-
237
- @staticmethod
238
- def round_corners(image: PIL.Image.Image, radius: int) -> PIL.Image.Image:
239
- """
240
- :return: Image with rounded edges of the specified radius
241
- """
242
-
243
- if image.mode != "RGB":
244
- image = image.convert("RGB")
245
- width, height = image.size
191
+ return PIL.Image.open(
192
+ fp=io.BytesIO(
193
+ initial_bytes=requests.get(url).content,
194
+ ),
195
+ )
246
196
 
247
- shape = PIL.Image.new("L", (radius * 2, radius * 2), 0)
248
- PIL.ImageDraw.Draw(shape).ellipse((0, 0, radius * 2, radius * 2), fill=255)
249
197
 
250
- alpha = PIL.Image.new("L", image.size, "white")
251
- alpha.paste(shape.crop((0, 0, radius, radius)), (0, 0))
252
- alpha.paste(shape.crop((0, radius, radius, radius * 2)), (0, height - radius))
253
- alpha.paste(shape.crop((radius, 0, radius * 2, radius)), (width - radius, 0))
254
- alpha.paste(shape.crop((radius, radius, radius * 2, radius * 2)), (width - radius, height - radius))
255
- image.putalpha(alpha)
198
+ class StringsProvider(pyquoks.utils._HasRequiredAttributes):
199
+ """
200
+ Class for providing various strings data
256
201
 
257
- return image
202
+ **Required attributes**::
258
203
 
204
+ _OBJECTS = {"menu": MenuStrings}
259
205
 
260
- class IStringsProvider:
261
- """
262
- Class for providing various strings data
206
+ Attributes:
207
+ _OBJECTS: Dictionary with names of attributes and child objects
263
208
  """
264
209
 
265
- class IStrings:
210
+ class Strings:
266
211
  """
267
212
  Class that represents a container for strings
268
213
  """
269
214
 
270
- pass
215
+ # noinspection PyUnusedLocal
216
+ def __init__(self, parent: StringsProvider) -> None: ... # TODO
271
217
 
272
- _STRINGS_OBJECTS: dict[str, type]
273
- """
274
- Dictionary with names of attributes and child objects
218
+ _REQUIRED_ATTRIBUTES = {
219
+ "_OBJECTS",
220
+ }
275
221
 
276
- Example:
277
- _STRINGS_OBJECTS = {"localizable": LocalizableStrings}
278
- """
222
+ _OBJECTS: dict[str, type]
279
223
 
280
224
  def __init__(self) -> None:
281
- for name, data_class in self._STRINGS_OBJECTS.items():
282
- setattr(self, name, data_class())
225
+ self._check_attributes()
226
+
227
+ for attribute, object_class in self._OBJECTS.items():
228
+ setattr(self, attribute, object_class(self))
283
229
 
284
230
 
285
231
  # endregion
286
232
 
287
233
  # region Managers
288
234
 
289
- class IDatabaseManager:
235
+ class ConfigManager(pyquoks.utils._HasRequiredAttributes):
290
236
  """
291
- Class for managing database connections
237
+ Class for managing data in configuration file
238
+
239
+ **Required attributes**::
240
+
241
+ _OBJECTS = {"settings": SettingsConfig}
242
+
243
+ # Predefined
244
+
245
+ _PATH = pyquoks.utils.get_path("config.ini")
246
+
247
+ Attributes:
248
+ _OBJECTS: Dictionary with names of attributes and child objects
249
+ _PATH: Path to the configuration file
292
250
  """
293
251
 
294
- class IDatabase(sqlite3.Connection):
295
- """
296
- Class that represents a database connection
252
+ class Config(pyquoks.utils._HasRequiredAttributes):
297
253
  """
254
+ Class that represents a section in configuration file
298
255
 
299
- _NAME: str = None
300
- """
301
- Name of the database
256
+ **Required attributes**::
302
257
 
303
- Example:
304
- _NAME = "users"
305
- """
258
+ _SECTION = "Settings"
306
259
 
307
- _SQL: str = None
308
- """
309
- SQL expression for creating a database
260
+ _VALUES = {"version": str, "beta": bool}
310
261
 
311
- Example:
312
- _SQL = f\"\"\"CREATE TABLE IF NOT EXISTS {_NAME} (user_id INTEGER PRIMARY KEY NOT NULL)\"\"\"
262
+ Attributes:
263
+ _SECTION: Name of the section in configuration file
264
+ _VALUES: Dictionary with settings and their types
265
+ _parent: Parent object
313
266
  """
314
267
 
315
- _FILENAME: str = "{0}.db"
316
- """
317
- File extension of database
318
- """
268
+ _REQUIRED_ATTRIBUTES = {
269
+ "_SECTION",
270
+ "_VALUES",
271
+ }
319
272
 
320
- def __init__(self, parent: IDatabaseManager) -> None:
321
- if isinstance(parent, IDatabaseManager):
322
- self._FILENAME = self._FILENAME.format(self._NAME)
273
+ _SECTION: str
323
274
 
324
- super().__init__(
325
- database=parent._PATH + self._FILENAME,
326
- check_same_thread=False,
327
- )
275
+ _VALUES: dict[str, type]
328
276
 
329
- cursor = self.cursor()
330
- cursor.execute(self._SQL)
331
- self.commit()
277
+ _incorrect_content_exception = configparser.ParsingError(
278
+ source="configuration file is filled incorrectly",
279
+ )
332
280
 
333
- _PATH: str = utils.get_path("db/")
334
- """
335
- Path to the directory with databases
336
- """
281
+ _parent: ConfigManager
337
282
 
338
- _DATABASE_OBJECTS: dict[str, type]
339
- """
340
- Dictionary with names of attributes and child objects
283
+ def __init__(self, parent: ConfigManager = None) -> None:
284
+ self._check_attributes()
341
285
 
342
- Example:
343
- _DATABASE_OBJECTS = {"users": UsersDatabase}
344
- """
286
+ if parent:
287
+ self._parent = parent
288
+ elif not hasattr(self, "_parent") or not self._parent:
289
+ raise AttributeError("This class cannot be initialized without a parent object!")
345
290
 
346
- def __init__(self) -> None:
347
- os.makedirs(self._PATH, exist_ok=True)
291
+ self._config = configparser.ConfigParser()
292
+ self._config.read(self._parent._PATH)
348
293
 
349
- for name, data_class in self._DATABASE_OBJECTS.items():
350
- setattr(self, name, data_class(self))
294
+ if not self._config.has_section(self._SECTION):
295
+ self._config.add_section(self._SECTION)
351
296
 
352
- def close_all(self) -> None:
353
- """
354
- Closes all database connections
355
- """
356
- for database in self._DATABASE_OBJECTS.keys():
357
- getattr(self, database).close()
297
+ for attribute, object_type in self._VALUES.items():
298
+ try:
299
+ setattr(self, attribute, self._config.get(self._SECTION, attribute))
300
+ except:
301
+ self._config.set(self._SECTION, attribute, object_type.__name__)
302
+ with open(self._parent._PATH, "w", encoding="utf-8") as file:
303
+ self._config.write(file)
358
304
 
305
+ for attribute, object_type in self._VALUES.items():
306
+ try:
307
+ match object_type.__name__:
308
+ case str.__name__:
309
+ pass
310
+ case int.__name__:
311
+ setattr(self, attribute, int(getattr(self, attribute)))
312
+ case float.__name__:
313
+ setattr(self, attribute, float(getattr(self, attribute)))
314
+ case bool.__name__:
315
+ if getattr(self, attribute) not in [str(True), str(False)]:
316
+ setattr(self, attribute, None)
317
+ raise self._incorrect_content_exception
318
+ else:
319
+ setattr(self, attribute, getattr(self, attribute) == str(True))
320
+ case dict.__name__ | list.__name__:
321
+ setattr(self, attribute, json.loads(getattr(self, attribute)))
322
+ case _:
323
+ raise ValueError(f"{object_type.__name__} type is not supported!")
324
+ except:
325
+ setattr(self, attribute, None)
359
326
 
360
- if sys.platform == "win32":
361
- import winreg
327
+ raise self._incorrect_content_exception
362
328
 
329
+ @property
330
+ def _values(self) -> dict | None:
331
+ """
332
+ :return: Values stored in this section
333
+ """
363
334
 
364
- class IRegistryManager:
365
- """
366
- Class for managing data in the Windows Registry
367
- """
335
+ try:
336
+ return {
337
+ attribute: getattr(self, attribute) for attribute in self._VALUES.keys()
338
+ }
339
+ except:
340
+ return None
368
341
 
369
- class IRegistry:
342
+ def update(self, **kwargs) -> None:
370
343
  """
371
- Class that represents a key with parameters in the Windows Registry
344
+ Updates provided attributes in object
372
345
  """
373
346
 
374
- _NAME: str = None
375
- """
376
- Name of key in the Windows Registry
347
+ for attribute, value in kwargs.items():
348
+ if attribute not in self._VALUES.keys():
349
+ raise AttributeError(f"{attribute} is not specified!")
350
+ elif type(value) is not self._VALUES.get(attribute):
351
+ raise AttributeError(
352
+ f"{attribute} has incorrect type! (must be {self._VALUES.get(attribute).__name__})",
353
+ )
377
354
 
378
- Example:
379
- _NAME = "OAuth"
380
- """
355
+ setattr(self, attribute, value)
381
356
 
382
- _REGISTRY_VALUES: dict[str, int]
383
- """
384
- Dictionary with settings and their types
385
- """
357
+ self._config.set(self._SECTION, attribute, value)
358
+ with open(self._parent._PATH, "w", encoding="utf-8") as file:
359
+ self._config.write(file)
386
360
 
387
- _path: winreg.HKEYType
361
+ _REQUIRED_ATTRIBUTES = {
362
+ "_OBJECTS",
363
+ "_PATH",
364
+ }
388
365
 
389
- def __init__(self, parent: IRegistryManager = None) -> None:
390
- if isinstance(parent, IRegistryManager):
391
- self._REGISTRY_VALUES = parent._REGISTRY_VALUES.get(self._NAME)
392
- self._path = winreg.CreateKey(parent._path, self._NAME)
366
+ _OBJECTS: dict[str, type]
393
367
 
394
- for setting in self._REGISTRY_VALUES.keys():
395
- try:
396
- setattr(self, setting, winreg.QueryValueEx(self._path, setting)[int()])
397
- except:
398
- setattr(self, setting, None)
368
+ _PATH: str = pyquoks.utils.get_path("config.ini")
399
369
 
400
- @property
401
- def values(self) -> dict | None:
402
- """
403
- :return: Values stored in key in the Windows Registry
404
- """
370
+ def __init__(self) -> None:
371
+ self._check_attributes()
405
372
 
406
- try:
407
- return {setting: getattr(self, setting) for setting in self._REGISTRY_VALUES.keys()}
408
- except:
409
- return None
373
+ for attribute, object_class in self._OBJECTS.items():
374
+ setattr(self, attribute, object_class(self))
410
375
 
411
- def refresh(self) -> IRegistryManager.IRegistry:
412
- """
413
- :return: Instance with refreshed values
414
- """
415
376
 
416
- self.__init__()
417
- return self
377
+ class DatabaseManager(pyquoks.utils._HasRequiredAttributes):
378
+ """
379
+ Class for managing database connections
418
380
 
419
- def update(self, **kwargs) -> None:
420
- """
421
- Updates provided settings in the Windows Registry
422
- """
381
+ **Required attributes**::
423
382
 
424
- for setting, value in kwargs.items():
425
- winreg.SetValueEx(self._path, setting, None, self._REGISTRY_VALUES.get(setting), value)
426
- setattr(self, setting, value)
383
+ _OBJECTS = {"users": UsersDatabase}
427
384
 
428
- _KEY: str
429
- """
430
- Path to key in the Windows Registry
385
+ # Predefined
431
386
 
432
- Example:
433
- _KEY = "Software\\\\\\\\diquoks Software\\\\\\\\pyquoks"
434
- """
387
+ _PATH = pyquoks.utils.get_path("db/")
435
388
 
436
- _REGISTRY_VALUES: dict[str, dict[str, int]]
437
- """
438
- Dictionary with keys, their settings and their types
389
+ Attributes:
390
+ _OBJECTS: Dictionary with names of attributes and child objects
391
+ _PATH: Path to the directory with databases
392
+ """
439
393
 
440
- Example:
441
- _REGISTRY_VALUES = {"OAuth": {"access_token": winreg.REG_SZ}}
394
+ class Database(sqlite3.Connection, pyquoks.utils._HasRequiredAttributes):
442
395
  """
396
+ Class that represents a database connection
443
397
 
444
- _REGISTRY_OBJECTS: dict[str, type]
445
- """
446
- Dictionary with names of attributes and child objects
398
+ **Required attributes**::
447
399
 
448
- Example:
449
- _REGISTRY_OBJECTS = {"oauth": OAuthRegistry}
400
+ _NAME = "users"
401
+
402
+ _SQL = f\"""CREATE TABLE IF NOT EXISTS {_NAME} (user_id INTEGER PRIMARY KEY NOT NULL)\"""
403
+
404
+ # Predefined
405
+
406
+ _FILENAME = "{0}.db"
407
+
408
+ Attributes:
409
+ _NAME: Name of the database
410
+ _SQL: SQL expression for creating a table
411
+ _FILENAME: Filename of the database
412
+ _parent: Parent object
450
413
  """
451
414
 
452
- _path: winreg.HKEYType
415
+ _REQUIRED_ATTRIBUTES = {
416
+ "_NAME",
417
+ "_SQL",
418
+ "_FILENAME",
419
+ }
453
420
 
454
- def __init__(self) -> None:
455
- self._path = winreg.CreateKey(winreg.HKEY_CURRENT_USER, self._KEY)
421
+ _NAME: str
456
422
 
457
- for name, data_class in self._REGISTRY_OBJECTS.items():
458
- setattr(self, name, data_class(self))
423
+ _SQL: str
459
424
 
460
- def refresh(self) -> IRegistryManager:
461
- """
462
- :return: Instance with refreshed values
463
- """
425
+ _FILENAME: str = "{0}.db"
426
+
427
+ _parent: DatabaseManager
428
+
429
+ def __init__(self, parent: DatabaseManager = None) -> None:
430
+ self._check_attributes()
431
+
432
+ if parent:
433
+ self._parent = parent
434
+ elif not hasattr(self, "_parent") or not self._parent:
435
+ raise AttributeError("This class cannot be initialized without a parent object!")
436
+
437
+ self._FILENAME = self._FILENAME.format(self._NAME)
438
+
439
+ super().__init__(
440
+ database=self._parent._PATH + self._FILENAME,
441
+ check_same_thread=False,
442
+ )
443
+ self.row_factory = sqlite3.Row
444
+
445
+ cursor = self.cursor()
446
+
447
+ cursor.execute(
448
+ self._SQL,
449
+ )
464
450
 
465
- self.__init__()
466
- return self
451
+ self.commit()
452
+
453
+ _REQUIRED_ATTRIBUTES = {
454
+ "_OBJECTS",
455
+ "_PATH",
456
+ }
457
+
458
+ _OBJECTS: dict[str, type]
459
+
460
+ _PATH: str = pyquoks.utils.get_path("db/")
461
+
462
+ def __init__(self):
463
+ self._check_attributes()
464
+
465
+ os.makedirs(
466
+ name=self._PATH,
467
+ exist_ok=True,
468
+ )
469
+
470
+ for attribute, object_class in self._OBJECTS.items():
471
+ setattr(self, attribute, object_class(self))
472
+
473
+ def close_all(self) -> None:
474
+ """
475
+ Closes all database connections
476
+ """
477
+
478
+ for database in self._OBJECTS.keys():
479
+ getattr(self, database).close()
467
480
 
468
481
 
469
482
  # endregion
@@ -473,61 +486,76 @@ if sys.platform == "win32":
473
486
  class LoggerService(logging.Logger):
474
487
  """
475
488
  Class that provides methods for parallel logging
476
- """
477
489
 
478
- _LOGS_PATH: str | None
479
- """
480
- Path to the logs file
490
+ Attributes:
491
+ _LOG_PATH: Path to the logs file
481
492
  """
482
493
 
494
+ _LOG_PATH: str | None
495
+
483
496
  def __init__(
484
497
  self,
485
- name: str,
486
- path: str = utils.get_path("logs/", only_abspath=True),
487
- filename: str = datetime.datetime.now().strftime("%d-%m-%y-%H-%M-%S"),
488
- file_handling: bool = True,
498
+ filename: str,
489
499
  level: int = logging.NOTSET,
500
+ file_handling: bool = True,
501
+ path: str = pyquoks.utils.get_path("logs/"),
490
502
  ) -> None:
491
- super().__init__(name, level)
503
+ super().__init__(filename, level)
492
504
 
493
- stream_handler = logging.StreamHandler(sys.stdout)
494
- stream_handler.setFormatter(
505
+ self.stream_handler = logging.StreamHandler(sys.stdout)
506
+ self.stream_handler.setFormatter(
495
507
  logging.Formatter(
496
508
  fmt="$levelname $asctime $name - $message",
497
509
  datefmt="%d-%m-%y %H:%M:%S",
498
510
  style="$",
499
511
  )
500
512
  )
501
- self.addHandler(stream_handler)
513
+ self.addHandler(self.stream_handler)
502
514
 
503
515
  if file_handling:
504
516
  os.makedirs(path, exist_ok=True)
505
- self._LOG_PATH = path + f"{filename}-{name}.log"
517
+ self._LOG_PATH = path + f"{int(datetime.datetime.now().timestamp())}.{filename}.log"
506
518
 
507
- file_handler = logging.FileHandler(
508
- self._LOG_PATH,
519
+ self.file_handler = logging.FileHandler(
520
+ filename=self._LOG_PATH,
509
521
  encoding="utf-8",
510
522
  )
511
- file_handler.setFormatter(
523
+ self.file_handler.setFormatter(
512
524
  logging.Formatter(
513
525
  fmt="$levelname $asctime - $message",
514
526
  datefmt="%d-%m-%y %H:%M:%S",
515
527
  style="$",
516
528
  ),
517
529
  )
518
- self.addHandler(file_handler)
530
+ self.addHandler(self.file_handler)
531
+ else:
532
+ self._LOG_PATH = None
519
533
 
520
- def get_logs_file(self) -> io.BufferedReader:
534
+ @property
535
+ def file(self) -> typing.BinaryIO | None:
521
536
  """
522
537
  :return: Opened file-like object of current logs
523
538
  """
524
- return open(self._LOG_PATH, "rb")
525
539
 
526
- def log_exception(self, e: Exception) -> None:
540
+ if self._LOG_PATH:
541
+ return open(self._LOG_PATH, "rb")
542
+ else:
543
+ return None
544
+
545
+ def log_error(self, exception: Exception, raise_again: bool = False) -> None:
527
546
  """
528
547
  Logs an exception with detailed traceback
548
+
549
+ :param exception: Exception to be logged
550
+ :param raise_again: Whether or not exception should be raised again
529
551
  """
530
552
 
531
- self.error(msg=e, exc_info=True)
553
+ self.error(
554
+ msg=exception,
555
+ exc_info=True,
556
+ )
557
+
558
+ if raise_again:
559
+ raise exception
532
560
 
533
561
  # endregion