pyquoks 1.4.0__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,470 +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
- )
328
- self.row_factory = sqlite3.Row
275
+ _VALUES: dict[str, type]
329
276
 
330
- cursor = self.cursor()
331
- cursor.execute(self._SQL)
332
- self.commit()
277
+ _incorrect_content_exception = configparser.ParsingError(
278
+ source="configuration file is filled incorrectly",
279
+ )
333
280
 
334
- _PATH: str = utils.get_path("db/")
335
- """
336
- Path to the directory with databases
337
- """
281
+ _parent: ConfigManager
338
282
 
339
- _DATABASE_OBJECTS: dict[str, type]
340
- """
341
- Dictionary with names of attributes and child objects
283
+ def __init__(self, parent: ConfigManager = None) -> None:
284
+ self._check_attributes()
342
285
 
343
- Example:
344
- _DATABASE_OBJECTS = {"users": UsersDatabase}
345
- """
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!")
346
290
 
347
- def __init__(self) -> None:
348
- os.makedirs(self._PATH, exist_ok=True)
291
+ self._config = configparser.ConfigParser()
292
+ self._config.read(self._parent._PATH)
349
293
 
350
- for name, data_class in self._DATABASE_OBJECTS.items():
351
- setattr(self, name, data_class(self))
294
+ if not self._config.has_section(self._SECTION):
295
+ self._config.add_section(self._SECTION)
352
296
 
353
- def close_all(self) -> None:
354
- """
355
- Closes all database connections
356
- """
357
- for database in self._DATABASE_OBJECTS.keys():
358
- 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)
359
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)
360
326
 
361
- if sys.platform == "win32":
362
- import winreg
327
+ raise self._incorrect_content_exception
363
328
 
329
+ @property
330
+ def _values(self) -> dict | None:
331
+ """
332
+ :return: Values stored in this section
333
+ """
364
334
 
365
- class IRegistryManager:
366
- """
367
- Class for managing data in the Windows Registry
368
- """
335
+ try:
336
+ return {
337
+ attribute: getattr(self, attribute) for attribute in self._VALUES.keys()
338
+ }
339
+ except:
340
+ return None
369
341
 
370
- class IRegistry:
342
+ def update(self, **kwargs) -> None:
371
343
  """
372
- Class that represents a key with parameters in the Windows Registry
344
+ Updates provided attributes in object
373
345
  """
374
346
 
375
- _NAME: str = None
376
- """
377
- 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
+ )
378
354
 
379
- Example:
380
- _NAME = "OAuth"
381
- """
355
+ setattr(self, attribute, value)
382
356
 
383
- _REGISTRY_VALUES: dict[str, int]
384
- """
385
- Dictionary with settings and their types
386
- """
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)
387
360
 
388
- _path: winreg.HKEYType
361
+ _REQUIRED_ATTRIBUTES = {
362
+ "_OBJECTS",
363
+ "_PATH",
364
+ }
389
365
 
390
- def __init__(self, parent: IRegistryManager = None) -> None:
391
- if isinstance(parent, IRegistryManager):
392
- self._REGISTRY_VALUES = parent._REGISTRY_VALUES.get(self._NAME)
393
- self._path = winreg.CreateKey(parent._path, self._NAME)
366
+ _OBJECTS: dict[str, type]
394
367
 
395
- for setting in self._REGISTRY_VALUES.keys():
396
- try:
397
- setattr(self, setting, winreg.QueryValueEx(self._path, setting)[int()])
398
- except:
399
- setattr(self, setting, None)
368
+ _PATH: str = pyquoks.utils.get_path("config.ini")
400
369
 
401
- @property
402
- def values(self) -> dict | None:
403
- """
404
- :return: Values stored in key in the Windows Registry
405
- """
370
+ def __init__(self) -> None:
371
+ self._check_attributes()
406
372
 
407
- try:
408
- return {setting: getattr(self, setting) for setting in self._REGISTRY_VALUES.keys()}
409
- except:
410
- return None
373
+ for attribute, object_class in self._OBJECTS.items():
374
+ setattr(self, attribute, object_class(self))
411
375
 
412
- def refresh(self) -> IRegistryManager.IRegistry:
413
- """
414
- :return: Instance with refreshed values
415
- """
416
376
 
417
- self.__init__()
418
- return self
377
+ class DatabaseManager(pyquoks.utils._HasRequiredAttributes):
378
+ """
379
+ Class for managing database connections
419
380
 
420
- def update(self, **kwargs) -> None:
421
- """
422
- Updates provided settings in the Windows Registry
423
- """
381
+ **Required attributes**::
424
382
 
425
- for setting, value in kwargs.items():
426
- winreg.SetValueEx(self._path, setting, None, self._REGISTRY_VALUES.get(setting), value)
427
- setattr(self, setting, value)
383
+ _OBJECTS = {"users": UsersDatabase}
428
384
 
429
- _KEY: str
430
- """
431
- Path to key in the Windows Registry
385
+ # Predefined
432
386
 
433
- Example:
434
- _KEY = "Software\\\\\\\\diquoks Software\\\\\\\\pyquoks"
435
- """
387
+ _PATH = pyquoks.utils.get_path("db/")
436
388
 
437
- _REGISTRY_VALUES: dict[str, dict[str, int]]
438
- """
439
- 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
+ """
440
393
 
441
- Example:
442
- _REGISTRY_VALUES = {"OAuth": {"access_token": winreg.REG_SZ}}
394
+ class Database(sqlite3.Connection, pyquoks.utils._HasRequiredAttributes):
443
395
  """
396
+ Class that represents a database connection
444
397
 
445
- _REGISTRY_OBJECTS: dict[str, type]
446
- """
447
- Dictionary with names of attributes and child objects
398
+ **Required attributes**::
448
399
 
449
- Example:
450
- _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
451
413
  """
452
414
 
453
- _path: winreg.HKEYType
415
+ _REQUIRED_ATTRIBUTES = {
416
+ "_NAME",
417
+ "_SQL",
418
+ "_FILENAME",
419
+ }
454
420
 
455
- def __init__(self) -> None:
456
- self._path = winreg.CreateKey(winreg.HKEY_CURRENT_USER, self._KEY)
421
+ _NAME: str
457
422
 
458
- for name, data_class in self._REGISTRY_OBJECTS.items():
459
- setattr(self, name, data_class(self))
423
+ _SQL: str
460
424
 
461
- def refresh(self) -> IRegistryManager:
462
- """
463
- :return: Instance with refreshed values
464
- """
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
+ )
465
450
 
466
- self.__init__()
467
- 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()
468
480
 
469
481
 
470
482
  # endregion
@@ -474,61 +486,76 @@ if sys.platform == "win32":
474
486
  class LoggerService(logging.Logger):
475
487
  """
476
488
  Class that provides methods for parallel logging
477
- """
478
489
 
479
- _LOGS_PATH: str | None
480
- """
481
- Path to the logs file
490
+ Attributes:
491
+ _LOG_PATH: Path to the logs file
482
492
  """
483
493
 
494
+ _LOG_PATH: str | None
495
+
484
496
  def __init__(
485
497
  self,
486
- name: str,
487
- path: str = utils.get_path("logs/", only_abspath=True),
488
- filename: str = datetime.datetime.now().strftime("%d-%m-%y-%H-%M-%S"),
489
- file_handling: bool = True,
498
+ filename: str,
490
499
  level: int = logging.NOTSET,
500
+ file_handling: bool = True,
501
+ path: str = pyquoks.utils.get_path("logs/"),
491
502
  ) -> None:
492
- super().__init__(name, level)
503
+ super().__init__(filename, level)
493
504
 
494
- stream_handler = logging.StreamHandler(sys.stdout)
495
- stream_handler.setFormatter(
505
+ self.stream_handler = logging.StreamHandler(sys.stdout)
506
+ self.stream_handler.setFormatter(
496
507
  logging.Formatter(
497
508
  fmt="$levelname $asctime $name - $message",
498
509
  datefmt="%d-%m-%y %H:%M:%S",
499
510
  style="$",
500
511
  )
501
512
  )
502
- self.addHandler(stream_handler)
513
+ self.addHandler(self.stream_handler)
503
514
 
504
515
  if file_handling:
505
516
  os.makedirs(path, exist_ok=True)
506
- self._LOG_PATH = path + f"{filename}-{name}.log"
517
+ self._LOG_PATH = path + f"{int(datetime.datetime.now().timestamp())}.{filename}.log"
507
518
 
508
- file_handler = logging.FileHandler(
509
- self._LOG_PATH,
519
+ self.file_handler = logging.FileHandler(
520
+ filename=self._LOG_PATH,
510
521
  encoding="utf-8",
511
522
  )
512
- file_handler.setFormatter(
523
+ self.file_handler.setFormatter(
513
524
  logging.Formatter(
514
525
  fmt="$levelname $asctime - $message",
515
526
  datefmt="%d-%m-%y %H:%M:%S",
516
527
  style="$",
517
528
  ),
518
529
  )
519
- self.addHandler(file_handler)
530
+ self.addHandler(self.file_handler)
531
+ else:
532
+ self._LOG_PATH = None
520
533
 
521
- def get_logs_file(self) -> io.BufferedReader:
534
+ @property
535
+ def file(self) -> typing.BinaryIO | None:
522
536
  """
523
537
  :return: Opened file-like object of current logs
524
538
  """
525
- return open(self._LOG_PATH, "rb")
526
539
 
527
- 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:
528
546
  """
529
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
530
551
  """
531
552
 
532
- 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
533
560
 
534
561
  # endregion