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/__init__.py +9 -0
- pyquoks/data.py +371 -343
- pyquoks/localhost.py +20 -8
- pyquoks/models.py +94 -96
- pyquoks/test.py +86 -0
- pyquoks/utils.py +27 -7
- {pyquoks-1.3.2.1.dist-info → pyquoks-2.0.0.dist-info}/METADATA +56 -55
- pyquoks-2.0.0.dist-info/RECORD +10 -0
- {pyquoks-1.3.2.1.dist-info → pyquoks-2.0.0.dist-info}/WHEEL +1 -2
- pyquoks-1.3.2.1.dist-info/RECORD +0 -10
- pyquoks-1.3.2.1.dist-info/top_level.txt +0 -1
- {pyquoks-1.3.2.1.dist-info → pyquoks-2.0.0.dist-info}/licenses/LICENSE +0 -0
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
|
|
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
|
-
)
|
|
275
|
+
_VALUES: dict[str, type]
|
|
328
276
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
277
|
+
_incorrect_content_exception = configparser.ParsingError(
|
|
278
|
+
source="configuration file is filled incorrectly",
|
|
279
|
+
)
|
|
332
280
|
|
|
333
|
-
|
|
334
|
-
"""
|
|
335
|
-
Path to the directory with databases
|
|
336
|
-
"""
|
|
281
|
+
_parent: ConfigManager
|
|
337
282
|
|
|
338
|
-
|
|
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
|
-
|
|
343
|
-
|
|
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
|
-
|
|
347
|
-
|
|
291
|
+
self._config = configparser.ConfigParser()
|
|
292
|
+
self._config.read(self._parent._PATH)
|
|
348
293
|
|
|
349
|
-
|
|
350
|
-
|
|
294
|
+
if not self._config.has_section(self._SECTION):
|
|
295
|
+
self._config.add_section(self._SECTION)
|
|
351
296
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
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
|
-
|
|
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
|
-
|
|
365
|
-
|
|
366
|
-
|
|
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
|
-
|
|
342
|
+
def update(self, **kwargs) -> None:
|
|
370
343
|
"""
|
|
371
|
-
|
|
344
|
+
Updates provided attributes in object
|
|
372
345
|
"""
|
|
373
346
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
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
|
-
|
|
379
|
-
_NAME = "OAuth"
|
|
380
|
-
"""
|
|
355
|
+
setattr(self, attribute, value)
|
|
381
356
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
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
|
-
|
|
361
|
+
_REQUIRED_ATTRIBUTES = {
|
|
362
|
+
"_OBJECTS",
|
|
363
|
+
"_PATH",
|
|
364
|
+
}
|
|
388
365
|
|
|
389
|
-
|
|
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
|
-
|
|
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
|
-
|
|
401
|
-
|
|
402
|
-
"""
|
|
403
|
-
:return: Values stored in key in the Windows Registry
|
|
404
|
-
"""
|
|
370
|
+
def __init__(self) -> None:
|
|
371
|
+
self._check_attributes()
|
|
405
372
|
|
|
406
|
-
|
|
407
|
-
|
|
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
|
-
|
|
417
|
-
|
|
377
|
+
class DatabaseManager(pyquoks.utils._HasRequiredAttributes):
|
|
378
|
+
"""
|
|
379
|
+
Class for managing database connections
|
|
418
380
|
|
|
419
|
-
|
|
420
|
-
"""
|
|
421
|
-
Updates provided settings in the Windows Registry
|
|
422
|
-
"""
|
|
381
|
+
**Required attributes**::
|
|
423
382
|
|
|
424
|
-
|
|
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
|
-
|
|
429
|
-
"""
|
|
430
|
-
Path to key in the Windows Registry
|
|
385
|
+
# Predefined
|
|
431
386
|
|
|
432
|
-
|
|
433
|
-
_KEY = "Software\\\\\\\\diquoks Software\\\\\\\\pyquoks"
|
|
434
|
-
"""
|
|
387
|
+
_PATH = pyquoks.utils.get_path("db/")
|
|
435
388
|
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
389
|
+
Attributes:
|
|
390
|
+
_OBJECTS: Dictionary with names of attributes and child objects
|
|
391
|
+
_PATH: Path to the directory with databases
|
|
392
|
+
"""
|
|
439
393
|
|
|
440
|
-
|
|
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
|
-
|
|
445
|
-
"""
|
|
446
|
-
Dictionary with names of attributes and child objects
|
|
398
|
+
**Required attributes**::
|
|
447
399
|
|
|
448
|
-
|
|
449
|
-
|
|
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
|
-
|
|
415
|
+
_REQUIRED_ATTRIBUTES = {
|
|
416
|
+
"_NAME",
|
|
417
|
+
"_SQL",
|
|
418
|
+
"_FILENAME",
|
|
419
|
+
}
|
|
453
420
|
|
|
454
|
-
|
|
455
|
-
self._path = winreg.CreateKey(winreg.HKEY_CURRENT_USER, self._KEY)
|
|
421
|
+
_NAME: str
|
|
456
422
|
|
|
457
|
-
|
|
458
|
-
setattr(self, name, data_class(self))
|
|
423
|
+
_SQL: str
|
|
459
424
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
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.
|
|
466
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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__(
|
|
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"{
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
553
|
+
self.error(
|
|
554
|
+
msg=exception,
|
|
555
|
+
exc_info=True,
|
|
556
|
+
)
|
|
557
|
+
|
|
558
|
+
if raise_again:
|
|
559
|
+
raise exception
|
|
532
560
|
|
|
533
561
|
# endregion
|