pyquoks 1.4.0__py3-none-any.whl → 2.0.1__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 +9 -0
- pyquoks/data.py +371 -344
- pyquoks/localhost.py +20 -8
- pyquoks/models.py +94 -96
- pyquoks/test.py +86 -0
- pyquoks/utils.py +41 -8
- {pyquoks-1.4.0.dist-info → pyquoks-2.0.1.dist-info}/METADATA +56 -55
- pyquoks-2.0.1.dist-info/RECORD +10 -0
- {pyquoks-1.4.0.dist-info → pyquoks-2.0.1.dist-info}/WHEEL +1 -2
- pyquoks-1.4.0.dist-info/RECORD +0 -10
- pyquoks-1.4.0.dist-info/top_level.txt +0 -1
- {pyquoks-1.4.0.dist-info → pyquoks-2.0.1.dist-info}/licenses/LICENSE +0 -0
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
|
|
4
|
-
|
|
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
|
|
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
|
-
|
|
20
|
-
"""
|
|
21
|
-
Filename of JSON-like files
|
|
22
|
-
"""
|
|
13
|
+
**Required attributes**::
|
|
23
14
|
|
|
24
|
-
|
|
25
|
-
"""
|
|
26
|
-
Dictionary with filenames and containers
|
|
15
|
+
_OBJECTS = {"users": UsersContainer}
|
|
27
16
|
|
|
28
|
-
|
|
29
|
-
_DATA_VALUES = {"users": UsersContainer}
|
|
30
|
-
"""
|
|
17
|
+
# Predefined:
|
|
31
18
|
|
|
32
|
-
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
47
|
-
""
|
|
48
|
-
|
|
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
|
-
|
|
56
|
-
_SECTION = "Settings"
|
|
57
|
-
"""
|
|
35
|
+
_OBJECTS: dict[str, type]
|
|
58
36
|
|
|
59
|
-
|
|
60
|
-
"""
|
|
61
|
-
Dictionary with settings and their types
|
|
62
|
-
"""
|
|
37
|
+
_PATH: str = pyquoks.utils.get_path("data/")
|
|
63
38
|
|
|
64
|
-
|
|
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
|
-
|
|
108
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
132
|
-
"""
|
|
133
|
-
Dictionary with names of attributes and child objects
|
|
56
|
+
**Required attributes**::
|
|
134
57
|
|
|
135
|
-
|
|
136
|
-
_CONFIG_OBJECTS = {"settings": SettingsConfig}
|
|
137
|
-
"""
|
|
58
|
+
_OBJECTS = {"images": ImagesDirectory, "example": ExampleNetwork}
|
|
138
59
|
|
|
139
|
-
|
|
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
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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
|
|
69
|
+
class Directory(pyquoks.utils._HasRequiredAttributes):
|
|
150
70
|
"""
|
|
151
71
|
Class that represents a directory with various assets
|
|
152
|
-
"""
|
|
153
72
|
|
|
154
|
-
|
|
155
|
-
"""
|
|
156
|
-
Path to the directory with assets files
|
|
73
|
+
**Required attributes**::
|
|
157
74
|
|
|
158
|
-
|
|
159
|
-
_PATH = "images/"
|
|
160
|
-
"""
|
|
75
|
+
_ATTRIBUTES = {"picture1", "picture2"}
|
|
161
76
|
|
|
162
|
-
|
|
163
|
-
"""
|
|
164
|
-
Filename of assets files
|
|
77
|
+
_PATH = "images/"
|
|
165
78
|
|
|
166
|
-
Example:
|
|
167
79
|
_FILENAME = "{0}.png"
|
|
168
|
-
"""
|
|
169
80
|
|
|
170
|
-
|
|
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
|
-
|
|
175
|
-
|
|
176
|
-
|
|
88
|
+
_REQUIRED_ATTRIBUTES = {
|
|
89
|
+
"_ATTRIBUTES",
|
|
90
|
+
"_PATH",
|
|
91
|
+
"_FILENAME",
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
_ATTRIBUTES: set[str]
|
|
95
|
+
|
|
96
|
+
_PATH: str
|
|
177
97
|
|
|
178
|
-
|
|
179
|
-
self._PATH = parent._PATH + self._PATH
|
|
98
|
+
_FILENAME: str
|
|
180
99
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
setattr(self, name, parent.network_image(url))
|
|
133
|
+
_REQUIRED_ATTRIBUTES = {
|
|
134
|
+
"_URLS",
|
|
135
|
+
}
|
|
202
136
|
|
|
203
|
-
|
|
204
|
-
"""
|
|
205
|
-
Path to the directory with assets folders
|
|
206
|
-
"""
|
|
137
|
+
_URLS: dict[str, str]
|
|
207
138
|
|
|
208
|
-
|
|
209
|
-
"""
|
|
210
|
-
Dictionary with names of attributes and child objects
|
|
139
|
+
_parent: AssetsProvider | None
|
|
211
140
|
|
|
212
|
-
|
|
213
|
-
|
|
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
|
-
|
|
218
|
-
|
|
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(
|
|
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(
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
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
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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
|
-
|
|
202
|
+
**Required attributes**::
|
|
258
203
|
|
|
204
|
+
_OBJECTS = {"menu": MenuStrings}
|
|
259
205
|
|
|
260
|
-
|
|
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
|
|
210
|
+
class Strings:
|
|
266
211
|
"""
|
|
267
212
|
Class that represents a container for strings
|
|
268
213
|
"""
|
|
269
214
|
|
|
270
|
-
|
|
215
|
+
# noinspection PyUnusedLocal
|
|
216
|
+
def __init__(self, parent: StringsProvider) -> None: ... # TODO
|
|
271
217
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
218
|
+
_REQUIRED_ATTRIBUTES = {
|
|
219
|
+
"_OBJECTS",
|
|
220
|
+
}
|
|
275
221
|
|
|
276
|
-
|
|
277
|
-
_STRINGS_OBJECTS = {"localizable": LocalizableStrings}
|
|
278
|
-
"""
|
|
222
|
+
_OBJECTS: dict[str, type]
|
|
279
223
|
|
|
280
224
|
def __init__(self) -> None:
|
|
281
|
-
|
|
282
|
-
|
|
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
|
|
235
|
+
class ConfigManager(pyquoks.utils._HasRequiredAttributes):
|
|
290
236
|
"""
|
|
291
|
-
Class for managing
|
|
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
|
|
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
|
-
|
|
300
|
-
"""
|
|
301
|
-
Name of the database
|
|
256
|
+
**Required attributes**::
|
|
302
257
|
|
|
303
|
-
|
|
304
|
-
_NAME = "users"
|
|
305
|
-
"""
|
|
258
|
+
_SECTION = "Settings"
|
|
306
259
|
|
|
307
|
-
|
|
308
|
-
"""
|
|
309
|
-
SQL expression for creating a database
|
|
260
|
+
_VALUES = {"version": str, "beta": bool}
|
|
310
261
|
|
|
311
|
-
|
|
312
|
-
|
|
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
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
268
|
+
_REQUIRED_ATTRIBUTES = {
|
|
269
|
+
"_SECTION",
|
|
270
|
+
"_VALUES",
|
|
271
|
+
}
|
|
319
272
|
|
|
320
|
-
|
|
321
|
-
if isinstance(parent, IDatabaseManager):
|
|
322
|
-
self._FILENAME = self._FILENAME.format(self._NAME)
|
|
273
|
+
_SECTION: str
|
|
323
274
|
|
|
324
|
-
|
|
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
|
-
|
|
331
|
-
|
|
332
|
-
|
|
277
|
+
_incorrect_content_exception = configparser.ParsingError(
|
|
278
|
+
source="configuration file is filled incorrectly",
|
|
279
|
+
)
|
|
333
280
|
|
|
334
|
-
|
|
335
|
-
"""
|
|
336
|
-
Path to the directory with databases
|
|
337
|
-
"""
|
|
281
|
+
_parent: ConfigManager
|
|
338
282
|
|
|
339
|
-
|
|
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
|
-
|
|
344
|
-
|
|
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
|
-
|
|
348
|
-
|
|
291
|
+
self._config = configparser.ConfigParser()
|
|
292
|
+
self._config.read(self._parent._PATH)
|
|
349
293
|
|
|
350
|
-
|
|
351
|
-
|
|
294
|
+
if not self._config.has_section(self._SECTION):
|
|
295
|
+
self._config.add_section(self._SECTION)
|
|
352
296
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
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
|
-
|
|
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
|
-
|
|
366
|
-
|
|
367
|
-
|
|
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
|
-
|
|
342
|
+
def update(self, **kwargs) -> None:
|
|
371
343
|
"""
|
|
372
|
-
|
|
344
|
+
Updates provided attributes in object
|
|
373
345
|
"""
|
|
374
346
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
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
|
-
|
|
380
|
-
_NAME = "OAuth"
|
|
381
|
-
"""
|
|
355
|
+
setattr(self, attribute, value)
|
|
382
356
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
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
|
-
|
|
361
|
+
_REQUIRED_ATTRIBUTES = {
|
|
362
|
+
"_OBJECTS",
|
|
363
|
+
"_PATH",
|
|
364
|
+
}
|
|
389
365
|
|
|
390
|
-
|
|
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
|
-
|
|
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
|
-
|
|
402
|
-
|
|
403
|
-
"""
|
|
404
|
-
:return: Values stored in key in the Windows Registry
|
|
405
|
-
"""
|
|
370
|
+
def __init__(self) -> None:
|
|
371
|
+
self._check_attributes()
|
|
406
372
|
|
|
407
|
-
|
|
408
|
-
|
|
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
|
-
|
|
418
|
-
|
|
377
|
+
class DatabaseManager(pyquoks.utils._HasRequiredAttributes):
|
|
378
|
+
"""
|
|
379
|
+
Class for managing database connections
|
|
419
380
|
|
|
420
|
-
|
|
421
|
-
"""
|
|
422
|
-
Updates provided settings in the Windows Registry
|
|
423
|
-
"""
|
|
381
|
+
**Required attributes**::
|
|
424
382
|
|
|
425
|
-
|
|
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
|
-
|
|
430
|
-
"""
|
|
431
|
-
Path to key in the Windows Registry
|
|
385
|
+
# Predefined
|
|
432
386
|
|
|
433
|
-
|
|
434
|
-
_KEY = "Software\\\\\\\\diquoks Software\\\\\\\\pyquoks"
|
|
435
|
-
"""
|
|
387
|
+
_PATH = pyquoks.utils.get_path("db/")
|
|
436
388
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
389
|
+
Attributes:
|
|
390
|
+
_OBJECTS: Dictionary with names of attributes and child objects
|
|
391
|
+
_PATH: Path to the directory with databases
|
|
392
|
+
"""
|
|
440
393
|
|
|
441
|
-
|
|
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
|
-
|
|
446
|
-
"""
|
|
447
|
-
Dictionary with names of attributes and child objects
|
|
398
|
+
**Required attributes**::
|
|
448
399
|
|
|
449
|
-
|
|
450
|
-
|
|
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
|
-
|
|
415
|
+
_REQUIRED_ATTRIBUTES = {
|
|
416
|
+
"_NAME",
|
|
417
|
+
"_SQL",
|
|
418
|
+
"_FILENAME",
|
|
419
|
+
}
|
|
454
420
|
|
|
455
|
-
|
|
456
|
-
self._path = winreg.CreateKey(winreg.HKEY_CURRENT_USER, self._KEY)
|
|
421
|
+
_NAME: str
|
|
457
422
|
|
|
458
|
-
|
|
459
|
-
setattr(self, name, data_class(self))
|
|
423
|
+
_SQL: str
|
|
460
424
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
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.
|
|
467
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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__(
|
|
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"{
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
553
|
+
self.error(
|
|
554
|
+
msg=exception,
|
|
555
|
+
exc_info=True,
|
|
556
|
+
)
|
|
557
|
+
|
|
558
|
+
if raise_again:
|
|
559
|
+
raise exception
|
|
533
560
|
|
|
534
561
|
# endregion
|