pyquoks 2.3.2.1__py3-none-any.whl → 2.5.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/__init__.py +6 -6
- pyquoks/managers/__init__.py +9 -0
- pyquoks/managers/config.py +154 -0
- pyquoks/managers/data.py +94 -0
- pyquoks/managers/database.py +103 -0
- pyquoks/providers/__init__.py +9 -0
- pyquoks/providers/assets.py +141 -0
- pyquoks/providers/environment.py +18 -0
- pyquoks/providers/strings.py +23 -0
- pyquoks/services/__init__.py +5 -0
- pyquoks/services/logger.py +86 -0
- pyquoks/utils.py +4 -1
- {pyquoks-2.3.2.1.dist-info → pyquoks-2.5.0.dist-info}/METADATA +6 -23
- pyquoks-2.5.0.dist-info/RECORD +16 -0
- {pyquoks-2.3.2.1.dist-info → pyquoks-2.5.0.dist-info}/WHEEL +1 -1
- pyquoks/data.py +0 -640
- pyquoks/localhost.py +0 -51
- pyquoks/test.py +0 -127
- pyquoks-2.3.2.1.dist-info/RECORD +0 -9
- {pyquoks-2.3.2.1.dist-info → pyquoks-2.5.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,36 +1,19 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyquoks
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.5.0
|
|
4
4
|
Summary: Пакет PyPI для часто используемых модулей в проектах diquoks
|
|
5
5
|
License: MIT
|
|
6
6
|
License-File: LICENSE
|
|
7
7
|
Author: Denis Titovets
|
|
8
8
|
Author-email: den232titovets@yandex.ru
|
|
9
|
-
Requires-Python: >=3.14
|
|
9
|
+
Requires-Python: >=3.14,<4.0
|
|
10
10
|
Classifier: License :: OSI Approved :: MIT License
|
|
11
11
|
Classifier: Programming Language :: Python :: 3
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.14
|
|
13
|
-
Requires-Dist:
|
|
14
|
-
Requires-Dist:
|
|
15
|
-
Requires-Dist:
|
|
16
|
-
Requires-Dist:
|
|
17
|
-
Requires-Dist: click (==8.3.1)
|
|
18
|
-
Requires-Dist: colorama (==0.4.6)
|
|
19
|
-
Requires-Dist: flask (==3.1.2)
|
|
20
|
-
Requires-Dist: idna (==3.11)
|
|
21
|
-
Requires-Dist: itsdangerous (==2.2.0)
|
|
22
|
-
Requires-Dist: jinja2 (==3.1.6)
|
|
23
|
-
Requires-Dist: markupsafe (==3.0.3)
|
|
24
|
-
Requires-Dist: pillow (==12.1.0)
|
|
25
|
-
Requires-Dist: psutil (==7.2.1)
|
|
26
|
-
Requires-Dist: pydantic (==2.12.5)
|
|
27
|
-
Requires-Dist: pydantic-core (==2.41.5)
|
|
28
|
-
Requires-Dist: requests (==2.32.5)
|
|
29
|
-
Requires-Dist: typing-extensions (==4.15.0)
|
|
30
|
-
Requires-Dist: typing-inspection (==0.4.2)
|
|
31
|
-
Requires-Dist: urllib3 (==2.6.2)
|
|
32
|
-
Requires-Dist: waitress (==3.0.2)
|
|
33
|
-
Requires-Dist: werkzeug (==3.1.4)
|
|
13
|
+
Requires-Dist: pillow (>=12.1.0)
|
|
14
|
+
Requires-Dist: psutil (>=7.2.2)
|
|
15
|
+
Requires-Dist: pydantic (>=2.12.5)
|
|
16
|
+
Requires-Dist: requests (>=2.32.5)
|
|
34
17
|
Description-Content-Type: text/markdown
|
|
35
18
|
|
|
36
19
|
# pyquoks
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
pyquoks/__init__.py,sha256=RDWFmPLDon9bhs1iKr7j1lCqj5gNSbf7Ly5KPu-9Spo,178
|
|
2
|
+
pyquoks/managers/__init__.py,sha256=J0EplmBJ9bURlIGCrfk1FaO_qGsUvEJGtl4l6NMhGks,129
|
|
3
|
+
pyquoks/managers/config.py,sha256=Z6sZYscZI53k2SUuz0MKXTN0tSoYz3pXZuW2gJq51ns,4972
|
|
4
|
+
pyquoks/managers/data.py,sha256=tHrnGIii4VZxDMjFlb83GfvJjjixtSCcZ-CnkhAxskY,2989
|
|
5
|
+
pyquoks/managers/database.py,sha256=eHtXKVHZF08q8JqwCwmKZVYbXItxW24bPF3kgiJY8TU,2335
|
|
6
|
+
pyquoks/providers/__init__.py,sha256=7L6ts-7E5Nl9uucP7Swp8o3_iUD-RdAl-b06qV19xnc,141
|
|
7
|
+
pyquoks/providers/assets.py,sha256=NuPfVSZTcxD1mQ-WV2f7UjKGUhDyiu3uN2SAkiZWxxg,3415
|
|
8
|
+
pyquoks/providers/environment.py,sha256=6vz0_Qx7cx4Hh9BP-38L9BNcV5EHJlBySB8UbNLV7WQ,421
|
|
9
|
+
pyquoks/providers/strings.py,sha256=fQ7vnLK8_UJZH5HSgwGZO9iGcv94lv3KVoz20XWu9kU,692
|
|
10
|
+
pyquoks/services/__init__.py,sha256=BEQ36U4rpwnZ6-HH89USDEGgjgEvSuA3zLBfz1M0xqE,55
|
|
11
|
+
pyquoks/services/logger.py,sha256=Pqgi4ysvQUHZL6gOCM2eRAdwwP1rS_Y56CpJ_1nvuFc,2342
|
|
12
|
+
pyquoks/utils.py,sha256=TjMvSgWxKR1Sb05Dq754L4fXtwIDm09GnykicfjFeW8,1904
|
|
13
|
+
pyquoks-2.5.0.dist-info/licenses/LICENSE,sha256=WYK66zwaBlCe_GnOwRCLlJCQibJ8oyqvpZ1CCsrcg2k,1095
|
|
14
|
+
pyquoks-2.5.0.dist-info/METADATA,sha256=J7ctTgpxFyoUeba3kJS0rSn-CzULPFYRHMU0T_h-k_s,1256
|
|
15
|
+
pyquoks-2.5.0.dist-info/WHEEL,sha256=kJCRJT_g0adfAJzTx2GUMmS80rTJIVHRCfG0DQgLq3o,88
|
|
16
|
+
pyquoks-2.5.0.dist-info/RECORD,,
|
pyquoks/data.py
DELETED
|
@@ -1,640 +0,0 @@
|
|
|
1
|
-
import configparser
|
|
2
|
-
import datetime
|
|
3
|
-
import io
|
|
4
|
-
import json
|
|
5
|
-
import logging
|
|
6
|
-
import os
|
|
7
|
-
import sqlite3
|
|
8
|
-
import sys
|
|
9
|
-
import typing
|
|
10
|
-
|
|
11
|
-
import PIL.Image
|
|
12
|
-
import pydantic
|
|
13
|
-
import requests
|
|
14
|
-
|
|
15
|
-
from . import utils
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
# region Providers
|
|
19
|
-
|
|
20
|
-
class AssetsProvider(utils._HasRequiredAttributes):
|
|
21
|
-
"""
|
|
22
|
-
Class for providing various assets data
|
|
23
|
-
|
|
24
|
-
**Required attributes**::
|
|
25
|
-
|
|
26
|
-
# Predefined:
|
|
27
|
-
|
|
28
|
-
_PATH = pyquoks.utils.get_path("assets/")
|
|
29
|
-
|
|
30
|
-
Attributes:
|
|
31
|
-
_PATH: Path to the directory with assets folders
|
|
32
|
-
"""
|
|
33
|
-
|
|
34
|
-
class Directory(utils._HasRequiredAttributes):
|
|
35
|
-
"""
|
|
36
|
-
Class that represents a directory with various assets
|
|
37
|
-
|
|
38
|
-
**Required attributes**::
|
|
39
|
-
|
|
40
|
-
_ATTRIBUTES = {"picture1", "picture2"}
|
|
41
|
-
|
|
42
|
-
_PATH = "images/"
|
|
43
|
-
|
|
44
|
-
_FILENAME = "{0}.png"
|
|
45
|
-
|
|
46
|
-
Attributes:
|
|
47
|
-
_ATTRIBUTES: Names of files in the directory
|
|
48
|
-
_PATH: Path to the directory with assets files
|
|
49
|
-
_FILENAME: Filename of assets files
|
|
50
|
-
_parent: Parent object
|
|
51
|
-
"""
|
|
52
|
-
|
|
53
|
-
_REQUIRED_ATTRIBUTES = {
|
|
54
|
-
"_ATTRIBUTES",
|
|
55
|
-
"_PATH",
|
|
56
|
-
"_FILENAME",
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
_ATTRIBUTES: set[str]
|
|
60
|
-
|
|
61
|
-
_PATH: str
|
|
62
|
-
|
|
63
|
-
_FILENAME: str
|
|
64
|
-
|
|
65
|
-
_parent: AssetsProvider | None
|
|
66
|
-
|
|
67
|
-
def __init__(self, parent: AssetsProvider = None) -> None:
|
|
68
|
-
self._check_attributes()
|
|
69
|
-
|
|
70
|
-
if parent:
|
|
71
|
-
self._parent = parent
|
|
72
|
-
elif not hasattr(self, "_parent") or not self._parent:
|
|
73
|
-
raise AttributeError("This class cannot be initialized without a parent object!")
|
|
74
|
-
|
|
75
|
-
self._PATH = self._parent._PATH + self._PATH
|
|
76
|
-
|
|
77
|
-
for attribute in self._ATTRIBUTES:
|
|
78
|
-
try:
|
|
79
|
-
setattr(self, attribute, self._parent.file_image(
|
|
80
|
-
path=self._PATH + self._FILENAME.format(attribute),
|
|
81
|
-
))
|
|
82
|
-
except Exception:
|
|
83
|
-
setattr(self, attribute, None)
|
|
84
|
-
|
|
85
|
-
class Network(utils._HasRequiredAttributes):
|
|
86
|
-
"""
|
|
87
|
-
Class that represents a set of images obtained from a network
|
|
88
|
-
|
|
89
|
-
**Required attributes**::
|
|
90
|
-
|
|
91
|
-
_URLS = {"example": "https://example.com/image.png"}
|
|
92
|
-
|
|
93
|
-
Attributes:
|
|
94
|
-
_URLS: Dictionary with names of attributes and URLs
|
|
95
|
-
_parent: Parent object
|
|
96
|
-
"""
|
|
97
|
-
|
|
98
|
-
_REQUIRED_ATTRIBUTES = {
|
|
99
|
-
"_URLS",
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
_URLS: dict[str, str]
|
|
103
|
-
|
|
104
|
-
_parent: AssetsProvider | None
|
|
105
|
-
|
|
106
|
-
def __init__(self, parent: AssetsProvider = None) -> None:
|
|
107
|
-
self._check_attributes()
|
|
108
|
-
|
|
109
|
-
if parent:
|
|
110
|
-
self._parent = parent
|
|
111
|
-
elif not hasattr(self, "_parent") or not self._parent:
|
|
112
|
-
raise AttributeError("This class cannot be initialized without a parent object!")
|
|
113
|
-
|
|
114
|
-
for attribute, url in self._URLS.items():
|
|
115
|
-
try:
|
|
116
|
-
setattr(self, attribute, self._parent.network_image(
|
|
117
|
-
url=url,
|
|
118
|
-
))
|
|
119
|
-
except Exception:
|
|
120
|
-
setattr(self, attribute, None)
|
|
121
|
-
|
|
122
|
-
_REQUIRED_ATTRIBUTES = {
|
|
123
|
-
"_PATH",
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
_PATH: str = utils.get_path("assets/")
|
|
127
|
-
|
|
128
|
-
def __init__(self) -> None:
|
|
129
|
-
self._check_attributes()
|
|
130
|
-
|
|
131
|
-
for attribute, child_class in self.__class__.__annotations__.items():
|
|
132
|
-
if issubclass(child_class, AssetsProvider.Directory | AssetsProvider.Network):
|
|
133
|
-
setattr(self, attribute, child_class(self))
|
|
134
|
-
else:
|
|
135
|
-
raise AttributeError(
|
|
136
|
-
f"{attribute} has incorrect type! (must be subclass of {AssetsProvider.Directory.__name__} or {AssetsProvider.Network.__name__})",
|
|
137
|
-
)
|
|
138
|
-
|
|
139
|
-
@staticmethod
|
|
140
|
-
def file_image(path: str) -> PIL.Image.Image:
|
|
141
|
-
"""
|
|
142
|
-
:param path: Absolute path of the image file
|
|
143
|
-
:return: Image object from a file
|
|
144
|
-
"""
|
|
145
|
-
|
|
146
|
-
with open(path, "rb") as file:
|
|
147
|
-
return PIL.Image.open(
|
|
148
|
-
fp=io.BytesIO(file.read()),
|
|
149
|
-
)
|
|
150
|
-
|
|
151
|
-
@staticmethod
|
|
152
|
-
def network_image(url: str) -> PIL.Image.Image:
|
|
153
|
-
"""
|
|
154
|
-
:param url: URL of the image file
|
|
155
|
-
:return: Image object from a URL
|
|
156
|
-
"""
|
|
157
|
-
|
|
158
|
-
return PIL.Image.open(
|
|
159
|
-
fp=io.BytesIO(requests.get(url).content),
|
|
160
|
-
)
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
class EnvironmentProvider:
|
|
164
|
-
"""
|
|
165
|
-
Class for providing environment variables
|
|
166
|
-
"""
|
|
167
|
-
|
|
168
|
-
def __init__(self) -> None:
|
|
169
|
-
self.load_variables()
|
|
170
|
-
|
|
171
|
-
def load_variables(self) -> None:
|
|
172
|
-
"""
|
|
173
|
-
Loads specified environment variables
|
|
174
|
-
"""
|
|
175
|
-
|
|
176
|
-
for attribute in self.__class__.__annotations__.keys():
|
|
177
|
-
setattr(self, attribute, os.getenv(attribute, None))
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
class StringsProvider:
|
|
181
|
-
"""
|
|
182
|
-
Class for providing various strings data
|
|
183
|
-
"""
|
|
184
|
-
|
|
185
|
-
class Strings:
|
|
186
|
-
"""
|
|
187
|
-
Class that represents a container for strings
|
|
188
|
-
"""
|
|
189
|
-
|
|
190
|
-
# noinspection PyUnusedLocal
|
|
191
|
-
def __init__(self, parent: StringsProvider) -> None:
|
|
192
|
-
... # TODO
|
|
193
|
-
|
|
194
|
-
def __init__(self) -> None:
|
|
195
|
-
for attribute, child_class in self.__class__.__annotations__.items():
|
|
196
|
-
if issubclass(child_class, StringsProvider.Strings):
|
|
197
|
-
setattr(self, attribute, child_class(self))
|
|
198
|
-
else:
|
|
199
|
-
raise AttributeError(
|
|
200
|
-
f"{attribute} has incorrect type! (must be subclass of {StringsProvider.Strings.__name__})",
|
|
201
|
-
)
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
# endregion
|
|
205
|
-
|
|
206
|
-
# region Managers
|
|
207
|
-
|
|
208
|
-
class ConfigManager(utils._HasRequiredAttributes):
|
|
209
|
-
"""
|
|
210
|
-
Class for managing data in configuration file
|
|
211
|
-
|
|
212
|
-
**Required attributes**::
|
|
213
|
-
|
|
214
|
-
# Predefined
|
|
215
|
-
|
|
216
|
-
_PATH = pyquoks.utils.get_path("config.ini")
|
|
217
|
-
|
|
218
|
-
Attributes:
|
|
219
|
-
_PATH: Path to the configuration file
|
|
220
|
-
"""
|
|
221
|
-
|
|
222
|
-
class Config(utils._HasRequiredAttributes):
|
|
223
|
-
"""
|
|
224
|
-
Class that represents a section in configuration file
|
|
225
|
-
|
|
226
|
-
**Required attributes**::
|
|
227
|
-
|
|
228
|
-
_SECTION = "Settings"
|
|
229
|
-
|
|
230
|
-
_VALUES = {"version": str, "beta": bool}
|
|
231
|
-
|
|
232
|
-
Attributes:
|
|
233
|
-
_SECTION: Name of the section in configuration file
|
|
234
|
-
_VALUES: Dictionary with settings and their types
|
|
235
|
-
_parent: Parent object
|
|
236
|
-
"""
|
|
237
|
-
|
|
238
|
-
_REQUIRED_ATTRIBUTES = {
|
|
239
|
-
"_SECTION",
|
|
240
|
-
"_VALUES",
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
_SECTION: str
|
|
244
|
-
|
|
245
|
-
_VALUES: dict[str, type]
|
|
246
|
-
|
|
247
|
-
_incorrect_content_exception = configparser.ParsingError(
|
|
248
|
-
source="configuration file is filled incorrectly",
|
|
249
|
-
)
|
|
250
|
-
|
|
251
|
-
_parent: ConfigManager
|
|
252
|
-
|
|
253
|
-
def __init__(self, parent: ConfigManager = None) -> None:
|
|
254
|
-
self._check_attributes()
|
|
255
|
-
|
|
256
|
-
if parent:
|
|
257
|
-
self._parent = parent
|
|
258
|
-
elif not hasattr(self, "_parent") or not self._parent:
|
|
259
|
-
raise AttributeError("This class cannot be initialized without a parent object!")
|
|
260
|
-
|
|
261
|
-
self._config = configparser.ConfigParser()
|
|
262
|
-
self._config.read(self._parent._PATH)
|
|
263
|
-
|
|
264
|
-
if not self._config.has_section(self._SECTION):
|
|
265
|
-
self._config.add_section(self._SECTION)
|
|
266
|
-
|
|
267
|
-
for attribute, object_type in self._VALUES.items():
|
|
268
|
-
try:
|
|
269
|
-
setattr(self, attribute, self._config.get(self._SECTION, attribute))
|
|
270
|
-
except Exception:
|
|
271
|
-
self._config.set(self._SECTION, attribute, object_type.__name__)
|
|
272
|
-
with open(self._parent._PATH, "w", encoding="utf-8") as file:
|
|
273
|
-
self._config.write(file)
|
|
274
|
-
|
|
275
|
-
for attribute, object_type in self._VALUES.items():
|
|
276
|
-
try:
|
|
277
|
-
match object_type():
|
|
278
|
-
case bool():
|
|
279
|
-
if getattr(self, attribute) not in [str(True), str(False)]:
|
|
280
|
-
setattr(self, attribute, None)
|
|
281
|
-
raise self._incorrect_content_exception
|
|
282
|
-
else:
|
|
283
|
-
setattr(self, attribute, getattr(self, attribute) == str(True))
|
|
284
|
-
case int():
|
|
285
|
-
setattr(self, attribute, int(getattr(self, attribute)))
|
|
286
|
-
case float():
|
|
287
|
-
setattr(self, attribute, float(getattr(self, attribute)))
|
|
288
|
-
case str():
|
|
289
|
-
pass
|
|
290
|
-
case dict() | list():
|
|
291
|
-
setattr(self, attribute, json.loads(getattr(self, attribute)))
|
|
292
|
-
case _:
|
|
293
|
-
raise ValueError(f"{object_type.__name__} type is not supported!")
|
|
294
|
-
except Exception:
|
|
295
|
-
setattr(self, attribute, None)
|
|
296
|
-
|
|
297
|
-
raise self._incorrect_content_exception
|
|
298
|
-
|
|
299
|
-
@property
|
|
300
|
-
def _values(self) -> dict | None:
|
|
301
|
-
"""
|
|
302
|
-
:return: Values stored in this section
|
|
303
|
-
"""
|
|
304
|
-
|
|
305
|
-
try:
|
|
306
|
-
return {
|
|
307
|
-
attribute: getattr(self, attribute) for attribute in self._VALUES.keys()
|
|
308
|
-
}
|
|
309
|
-
except Exception:
|
|
310
|
-
return None
|
|
311
|
-
|
|
312
|
-
def update(self, **kwargs) -> None:
|
|
313
|
-
"""
|
|
314
|
-
Updates provided attributes in object
|
|
315
|
-
"""
|
|
316
|
-
|
|
317
|
-
for attribute, value in kwargs.items():
|
|
318
|
-
|
|
319
|
-
if attribute not in self._VALUES.keys():
|
|
320
|
-
raise AttributeError(f"{attribute} is not specified!")
|
|
321
|
-
|
|
322
|
-
object_type = self._VALUES.get(attribute)
|
|
323
|
-
|
|
324
|
-
if not isinstance(
|
|
325
|
-
value,
|
|
326
|
-
typing.get_origin(object_type) if typing.get_origin(object_type) else object_type,
|
|
327
|
-
):
|
|
328
|
-
raise AttributeError(
|
|
329
|
-
f"{attribute} has incorrect type! (must be {object_type.__name__})",
|
|
330
|
-
)
|
|
331
|
-
|
|
332
|
-
setattr(self, attribute, value)
|
|
333
|
-
|
|
334
|
-
match object_type():
|
|
335
|
-
case bool() | int() | float() | str():
|
|
336
|
-
self._config.set(self._SECTION, attribute, str(value))
|
|
337
|
-
case dict() | list():
|
|
338
|
-
self._config.set(self._SECTION, attribute, json.dumps(value))
|
|
339
|
-
case _:
|
|
340
|
-
raise ValueError(f"{object_type.__name__} type is not supported!")
|
|
341
|
-
|
|
342
|
-
with open(self._parent._PATH, "w", encoding="utf-8") as file:
|
|
343
|
-
self._config.write(file)
|
|
344
|
-
|
|
345
|
-
_REQUIRED_ATTRIBUTES = {
|
|
346
|
-
"_PATH",
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
_PATH: str = utils.get_path("config.ini")
|
|
350
|
-
|
|
351
|
-
def __init__(self) -> None:
|
|
352
|
-
self._check_attributes()
|
|
353
|
-
|
|
354
|
-
for attribute, child_class in self.__class__.__annotations__.items():
|
|
355
|
-
if issubclass(child_class, ConfigManager.Config):
|
|
356
|
-
setattr(self, attribute, child_class(self))
|
|
357
|
-
else:
|
|
358
|
-
raise AttributeError(
|
|
359
|
-
f"{attribute} has incorrect type! (must be subclass of {ConfigManager.Config.__name__})",
|
|
360
|
-
)
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
class DataManager(utils._HasRequiredAttributes):
|
|
364
|
-
"""
|
|
365
|
-
Class for managing data from JSON-like files
|
|
366
|
-
|
|
367
|
-
**Required attributes**::
|
|
368
|
-
|
|
369
|
-
# Predefined:
|
|
370
|
-
|
|
371
|
-
_PATH = pyquoks.utils.get_path("data/")
|
|
372
|
-
|
|
373
|
-
_FILENAME = "{0}.json"
|
|
374
|
-
|
|
375
|
-
Attributes:
|
|
376
|
-
_PATH: Path to the directory with JSON-like files
|
|
377
|
-
_FILENAME: Filename of JSON-like files
|
|
378
|
-
"""
|
|
379
|
-
|
|
380
|
-
_REQUIRED_ATTRIBUTES = {
|
|
381
|
-
"_PATH",
|
|
382
|
-
"_FILENAME",
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
_PATH: str = utils.get_path("data/")
|
|
386
|
-
|
|
387
|
-
_FILENAME: str = "{0}.json"
|
|
388
|
-
|
|
389
|
-
def __init__(self) -> None:
|
|
390
|
-
self._check_attributes()
|
|
391
|
-
|
|
392
|
-
for attribute, object_type in self.__class__.__annotations__.items():
|
|
393
|
-
if issubclass(
|
|
394
|
-
typing.get_args(object_type)[0],
|
|
395
|
-
pydantic.BaseModel,
|
|
396
|
-
) if typing.get_origin(object_type) else issubclass(
|
|
397
|
-
object_type,
|
|
398
|
-
pydantic.BaseModel,
|
|
399
|
-
):
|
|
400
|
-
try:
|
|
401
|
-
with open(self._PATH + self._FILENAME.format(attribute), "rb") as file:
|
|
402
|
-
data = json.loads(file.read())
|
|
403
|
-
|
|
404
|
-
if typing.get_origin(object_type) == list:
|
|
405
|
-
setattr(self, attribute, [typing.get_args(object_type)[0](**model) for model in data])
|
|
406
|
-
else:
|
|
407
|
-
setattr(self, attribute, object_type(**data))
|
|
408
|
-
except Exception:
|
|
409
|
-
setattr(self, attribute, None)
|
|
410
|
-
else:
|
|
411
|
-
raise AttributeError(
|
|
412
|
-
f"{attribute} has incorrect type! (must be subclass of {pydantic.BaseModel.__name__} or {list.__name__} of its subclasses)",
|
|
413
|
-
)
|
|
414
|
-
|
|
415
|
-
def update(self, **kwargs) -> None:
|
|
416
|
-
"""
|
|
417
|
-
Updates provided attributes in object
|
|
418
|
-
"""
|
|
419
|
-
|
|
420
|
-
for attribute, value in kwargs.items():
|
|
421
|
-
value: pydantic.BaseModel | list[pydantic.BaseModel]
|
|
422
|
-
|
|
423
|
-
if attribute not in self.__class__.__annotations__.keys():
|
|
424
|
-
raise AttributeError(f"{attribute} is not specified!")
|
|
425
|
-
|
|
426
|
-
object_type = self.__class__.__annotations__.get(attribute)
|
|
427
|
-
|
|
428
|
-
if not isinstance(
|
|
429
|
-
value,
|
|
430
|
-
typing.get_origin(object_type) if typing.get_origin(object_type) else object_type,
|
|
431
|
-
):
|
|
432
|
-
raise AttributeError(
|
|
433
|
-
f"{attribute} has incorrect type! (must be {object_type.__name__})",
|
|
434
|
-
)
|
|
435
|
-
|
|
436
|
-
setattr(self, attribute, value)
|
|
437
|
-
|
|
438
|
-
os.makedirs(
|
|
439
|
-
name=self._PATH,
|
|
440
|
-
exist_ok=True,
|
|
441
|
-
)
|
|
442
|
-
|
|
443
|
-
with open(self._PATH + self._FILENAME.format(attribute), "w", encoding="utf-8") as file:
|
|
444
|
-
json.dump(
|
|
445
|
-
[model.model_dump() for model in value] if typing.get_origin(
|
|
446
|
-
object_type,
|
|
447
|
-
) == list else value.model_dump(),
|
|
448
|
-
fp=file,
|
|
449
|
-
ensure_ascii=False,
|
|
450
|
-
indent=2,
|
|
451
|
-
)
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
class DatabaseManager(utils._HasRequiredAttributes):
|
|
455
|
-
"""
|
|
456
|
-
Class for managing database connections
|
|
457
|
-
|
|
458
|
-
**Required attributes**::
|
|
459
|
-
|
|
460
|
-
# Predefined
|
|
461
|
-
|
|
462
|
-
_PATH = pyquoks.utils.get_path("db/")
|
|
463
|
-
|
|
464
|
-
Attributes:
|
|
465
|
-
_PATH: Path to the directory with databases
|
|
466
|
-
"""
|
|
467
|
-
|
|
468
|
-
class Database(sqlite3.Connection, utils._HasRequiredAttributes):
|
|
469
|
-
"""
|
|
470
|
-
Class that represents a database connection
|
|
471
|
-
|
|
472
|
-
**Required attributes**::
|
|
473
|
-
|
|
474
|
-
_NAME = "users"
|
|
475
|
-
|
|
476
|
-
_SQL = f\"""CREATE TABLE IF NOT EXISTS {_NAME} (user_id INTEGER PRIMARY KEY NOT NULL)\"""
|
|
477
|
-
|
|
478
|
-
# Predefined
|
|
479
|
-
|
|
480
|
-
_FILENAME = "{0}.db"
|
|
481
|
-
|
|
482
|
-
Attributes:
|
|
483
|
-
_NAME: Name of the database
|
|
484
|
-
_SQL: SQL expression for creating a table
|
|
485
|
-
_FILENAME: Filename of the database
|
|
486
|
-
_parent: Parent object
|
|
487
|
-
"""
|
|
488
|
-
|
|
489
|
-
_REQUIRED_ATTRIBUTES = {
|
|
490
|
-
"_NAME",
|
|
491
|
-
"_SQL",
|
|
492
|
-
"_FILENAME",
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
_NAME: str
|
|
496
|
-
|
|
497
|
-
_SQL: str
|
|
498
|
-
|
|
499
|
-
_FILENAME: str = "{0}.db"
|
|
500
|
-
|
|
501
|
-
_parent: DatabaseManager
|
|
502
|
-
|
|
503
|
-
def __init__(self, parent: DatabaseManager = None) -> None:
|
|
504
|
-
self._check_attributes()
|
|
505
|
-
|
|
506
|
-
if parent:
|
|
507
|
-
self._parent = parent
|
|
508
|
-
elif not hasattr(self, "_parent") or not self._parent:
|
|
509
|
-
raise AttributeError("This class cannot be initialized without a parent object!")
|
|
510
|
-
|
|
511
|
-
self._FILENAME = self._FILENAME.format(self._NAME)
|
|
512
|
-
|
|
513
|
-
super().__init__(
|
|
514
|
-
database=self._parent._PATH + self._FILENAME,
|
|
515
|
-
check_same_thread=False,
|
|
516
|
-
)
|
|
517
|
-
self.row_factory = sqlite3.Row
|
|
518
|
-
|
|
519
|
-
cursor = self.cursor()
|
|
520
|
-
|
|
521
|
-
cursor.execute(
|
|
522
|
-
self._SQL,
|
|
523
|
-
)
|
|
524
|
-
|
|
525
|
-
self.commit()
|
|
526
|
-
|
|
527
|
-
_REQUIRED_ATTRIBUTES = {
|
|
528
|
-
"_PATH",
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
_PATH: str = utils.get_path("db/")
|
|
532
|
-
|
|
533
|
-
def __init__(self) -> None:
|
|
534
|
-
self._check_attributes()
|
|
535
|
-
|
|
536
|
-
os.makedirs(
|
|
537
|
-
name=self._PATH,
|
|
538
|
-
exist_ok=True,
|
|
539
|
-
)
|
|
540
|
-
|
|
541
|
-
for attribute, child_class in self.__class__.__annotations__.items():
|
|
542
|
-
if issubclass(child_class, DatabaseManager.Database):
|
|
543
|
-
setattr(self, attribute, child_class(self))
|
|
544
|
-
else:
|
|
545
|
-
raise AttributeError(
|
|
546
|
-
f"{attribute} has incorrect type! (must be subclass of {DatabaseManager.Database.__name__})",
|
|
547
|
-
)
|
|
548
|
-
|
|
549
|
-
def close_all(self) -> None:
|
|
550
|
-
"""
|
|
551
|
-
Closes all database connections
|
|
552
|
-
"""
|
|
553
|
-
|
|
554
|
-
for attribute in self.__class__.__annotations__.keys():
|
|
555
|
-
getattr(self, attribute).close()
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
# endregion
|
|
559
|
-
|
|
560
|
-
# region Services
|
|
561
|
-
|
|
562
|
-
class LoggerService(logging.Logger):
|
|
563
|
-
"""
|
|
564
|
-
Class that provides methods for parallel logging
|
|
565
|
-
|
|
566
|
-
Attributes:
|
|
567
|
-
_LOG_PATH: Path to the logs file
|
|
568
|
-
"""
|
|
569
|
-
|
|
570
|
-
_LOG_PATH: str | None
|
|
571
|
-
|
|
572
|
-
def __init__(
|
|
573
|
-
self,
|
|
574
|
-
filename: str,
|
|
575
|
-
level: int = logging.NOTSET,
|
|
576
|
-
file_handling: bool = True,
|
|
577
|
-
path: str = utils.get_path("logs/"),
|
|
578
|
-
) -> None:
|
|
579
|
-
super().__init__(filename, level)
|
|
580
|
-
|
|
581
|
-
self.stream_handler = logging.StreamHandler(sys.stdout)
|
|
582
|
-
self.stream_handler.setFormatter(
|
|
583
|
-
logging.Formatter(
|
|
584
|
-
fmt="$levelname $asctime $name - $message",
|
|
585
|
-
datefmt="%d-%m-%y %H:%M:%S",
|
|
586
|
-
style="$",
|
|
587
|
-
)
|
|
588
|
-
)
|
|
589
|
-
self.addHandler(self.stream_handler)
|
|
590
|
-
|
|
591
|
-
if file_handling:
|
|
592
|
-
os.makedirs(
|
|
593
|
-
name=path,
|
|
594
|
-
exist_ok=True
|
|
595
|
-
)
|
|
596
|
-
self._LOG_PATH = path + f"{int(datetime.datetime.now().timestamp())}.{filename}.log"
|
|
597
|
-
|
|
598
|
-
self.file_handler = logging.FileHandler(
|
|
599
|
-
filename=self._LOG_PATH,
|
|
600
|
-
encoding="utf-8",
|
|
601
|
-
)
|
|
602
|
-
self.file_handler.setFormatter(
|
|
603
|
-
logging.Formatter(
|
|
604
|
-
fmt="$levelname $asctime - $message",
|
|
605
|
-
datefmt="%d-%m-%y %H:%M:%S",
|
|
606
|
-
style="$",
|
|
607
|
-
),
|
|
608
|
-
)
|
|
609
|
-
self.addHandler(self.file_handler)
|
|
610
|
-
else:
|
|
611
|
-
self._LOG_PATH = None
|
|
612
|
-
|
|
613
|
-
@property
|
|
614
|
-
def file(self) -> typing.IO | None:
|
|
615
|
-
"""
|
|
616
|
-
:return: Opened file-like object of current logs
|
|
617
|
-
"""
|
|
618
|
-
|
|
619
|
-
if self._LOG_PATH:
|
|
620
|
-
return open(self._LOG_PATH, "rb")
|
|
621
|
-
else:
|
|
622
|
-
return None
|
|
623
|
-
|
|
624
|
-
def log_error(self, exception: Exception, raise_again: bool = False) -> None:
|
|
625
|
-
"""
|
|
626
|
-
Logs an exception with detailed traceback
|
|
627
|
-
|
|
628
|
-
:param exception: Exception to be logged
|
|
629
|
-
:param raise_again: Whether or not exception should be raised again
|
|
630
|
-
"""
|
|
631
|
-
|
|
632
|
-
self.error(
|
|
633
|
-
msg=exception,
|
|
634
|
-
exc_info=True,
|
|
635
|
-
)
|
|
636
|
-
|
|
637
|
-
if raise_again:
|
|
638
|
-
raise exception
|
|
639
|
-
|
|
640
|
-
# endregion
|