pyquoks 1.2.2__py3-none-any.whl → 1.3.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 +366 -55
- pyquoks/localhost.py +21 -3
- pyquoks/models.py +100 -25
- pyquoks/utils.py +4 -0
- {pyquoks-1.2.2.dist-info → pyquoks-1.3.0.dist-info}/METADATA +3 -3
- pyquoks-1.3.0.dist-info/RECORD +10 -0
- pyquoks-1.2.2.dist-info/RECORD +0 -10
- {pyquoks-1.2.2.dist-info → pyquoks-1.3.0.dist-info}/WHEEL +0 -0
- {pyquoks-1.2.2.dist-info → pyquoks-1.3.0.dist-info}/licenses/LICENSE +0 -0
- {pyquoks-1.2.2.dist-info → pyquoks-1.3.0.dist-info}/top_level.txt +0 -0
pyquoks/data.py
CHANGED
|
@@ -1,211 +1,522 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
-
import configparser, datetime, logging, json, sys, io, os
|
|
2
|
+
import configparser, datetime, logging, sqlite3, json, sys, io, os
|
|
3
3
|
import requests, PIL.Image, PIL.ImageDraw
|
|
4
4
|
from . import utils
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
# Providers
|
|
7
|
+
# region Providers
|
|
8
|
+
|
|
8
9
|
class IDataProvider:
|
|
9
|
-
|
|
10
|
+
"""
|
|
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
|
+
|
|
19
|
+
_FILENAME: str = "{0}.json"
|
|
20
|
+
"""
|
|
21
|
+
Filename of JSON-like files
|
|
22
|
+
"""
|
|
23
|
+
|
|
10
24
|
_DATA_VALUES: dict[str, type]
|
|
25
|
+
"""
|
|
26
|
+
Dictionary with filenames and containers
|
|
27
|
+
|
|
28
|
+
Example:
|
|
29
|
+
_DATA_VALUES = {"users": UsersContainer}
|
|
30
|
+
"""
|
|
11
31
|
|
|
12
32
|
def __init__(self) -> None:
|
|
13
|
-
for
|
|
33
|
+
for filename, container in self._DATA_VALUES.items():
|
|
14
34
|
try:
|
|
15
|
-
with open(self._PATH.format(
|
|
16
|
-
setattr(self,
|
|
35
|
+
with open(self._PATH + self._FILENAME.format(filename), "rb") as file:
|
|
36
|
+
setattr(self, filename, container(json.loads(file.read())))
|
|
17
37
|
except:
|
|
18
|
-
setattr(self,
|
|
38
|
+
setattr(self, filename, None)
|
|
19
39
|
|
|
20
40
|
|
|
21
41
|
class IConfigProvider:
|
|
42
|
+
"""
|
|
43
|
+
Class for providing data from configuration file
|
|
44
|
+
"""
|
|
45
|
+
|
|
22
46
|
class IConfig:
|
|
47
|
+
"""
|
|
48
|
+
Class that represents a section in configuration file
|
|
49
|
+
"""
|
|
50
|
+
|
|
23
51
|
_SECTION: str = None
|
|
52
|
+
"""
|
|
53
|
+
Name of the section in configuration file
|
|
54
|
+
|
|
55
|
+
Example:
|
|
56
|
+
_SECTION = "Settings"
|
|
57
|
+
"""
|
|
58
|
+
|
|
24
59
|
_CONFIG_VALUES: dict[str, type]
|
|
60
|
+
"""
|
|
61
|
+
Dictionary with settings and their types
|
|
62
|
+
"""
|
|
25
63
|
|
|
26
64
|
def __init__(self, parent: IConfigProvider = None) -> None:
|
|
27
65
|
if isinstance(parent, IConfigProvider):
|
|
28
66
|
self._CONFIG_VALUES = parent._CONFIG_VALUES.get(self._SECTION)
|
|
29
|
-
|
|
67
|
+
|
|
68
|
+
self._incorrect_content_exception = configparser.ParsingError(
|
|
69
|
+
"configuration file is filled incorrectly!"
|
|
70
|
+
)
|
|
30
71
|
self._config = configparser.ConfigParser()
|
|
31
|
-
self._config.read(
|
|
72
|
+
self._config.read(parent._PATH)
|
|
73
|
+
|
|
32
74
|
if not self._config.has_section(self._SECTION):
|
|
33
75
|
self._config.add_section(self._SECTION)
|
|
34
|
-
|
|
76
|
+
|
|
77
|
+
for setting, data_type in self._CONFIG_VALUES.items():
|
|
35
78
|
try:
|
|
36
|
-
setattr(self,
|
|
79
|
+
setattr(self, setting, self._config.get(self._SECTION, setting))
|
|
37
80
|
except:
|
|
38
|
-
self._config.set(self._SECTION,
|
|
39
|
-
with open(
|
|
40
|
-
self._config.write(
|
|
41
|
-
|
|
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():
|
|
42
86
|
try:
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
87
|
+
match data_type.__name__:
|
|
88
|
+
case "int":
|
|
89
|
+
setattr(self, setting, int(getattr(self, setting)))
|
|
90
|
+
case "bool":
|
|
91
|
+
if getattr(self, setting) not in (str(True), str(False)):
|
|
92
|
+
setattr(self, setting, None)
|
|
93
|
+
raise self._incorrect_content_exception
|
|
94
|
+
else:
|
|
95
|
+
setattr(self, setting, getattr(self, setting) == str(True))
|
|
96
|
+
case "dict" | "list":
|
|
97
|
+
setattr(self, setting, json.loads(getattr(self, setting)))
|
|
53
98
|
except:
|
|
54
|
-
setattr(self,
|
|
99
|
+
setattr(self, setting, None)
|
|
55
100
|
raise self._incorrect_content_exception
|
|
101
|
+
|
|
56
102
|
if not self.values:
|
|
57
103
|
raise self._incorrect_content_exception
|
|
58
104
|
|
|
59
105
|
@property
|
|
60
106
|
def values(self) -> dict | None:
|
|
107
|
+
"""
|
|
108
|
+
:return: Values stored in section
|
|
109
|
+
"""
|
|
110
|
+
|
|
61
111
|
try:
|
|
62
|
-
return {
|
|
112
|
+
return {setting: getattr(self, setting) for setting in self._CONFIG_VALUES.keys()}
|
|
63
113
|
except:
|
|
64
114
|
return None
|
|
65
115
|
|
|
116
|
+
_PATH: str = utils.get_path("config.ini")
|
|
117
|
+
"""
|
|
118
|
+
Path to the configuration file
|
|
119
|
+
"""
|
|
120
|
+
|
|
66
121
|
_CONFIG_VALUES: dict[str, dict[str, type]]
|
|
122
|
+
"""
|
|
123
|
+
Dictionary with sections, their settings and their types
|
|
124
|
+
|
|
125
|
+
Example:
|
|
126
|
+
_CONFIG_VALUES = {"Settings": {"version": str}}
|
|
127
|
+
"""
|
|
128
|
+
|
|
67
129
|
_CONFIG_OBJECTS: dict[str, type]
|
|
130
|
+
"""
|
|
131
|
+
Dictionary with names of attributes and child objects
|
|
132
|
+
|
|
133
|
+
Example:
|
|
134
|
+
_CONFIG_OBJECTS = {"settings": SettingsConfig}
|
|
135
|
+
"""
|
|
68
136
|
|
|
69
137
|
def __init__(self) -> None:
|
|
70
|
-
for
|
|
71
|
-
setattr(self,
|
|
138
|
+
for name, data_class in self._CONFIG_OBJECTS.items():
|
|
139
|
+
setattr(self, name, data_class(self))
|
|
72
140
|
|
|
73
141
|
|
|
74
142
|
class IAssetsProvider:
|
|
143
|
+
"""
|
|
144
|
+
Class for providing various assets data
|
|
145
|
+
"""
|
|
146
|
+
|
|
75
147
|
class IDirectory:
|
|
148
|
+
"""
|
|
149
|
+
Class that represents a directory with various assets
|
|
150
|
+
"""
|
|
151
|
+
|
|
76
152
|
_PATH: str = None
|
|
153
|
+
"""
|
|
154
|
+
Path to the directory with assets files
|
|
155
|
+
|
|
156
|
+
Example:
|
|
157
|
+
_PATH = "images/"
|
|
158
|
+
"""
|
|
159
|
+
|
|
160
|
+
_FILENAME: str = None
|
|
161
|
+
"""
|
|
162
|
+
Filename of assets files
|
|
163
|
+
|
|
164
|
+
Example:
|
|
165
|
+
_FILENAME = "{0}.png"
|
|
166
|
+
"""
|
|
167
|
+
|
|
77
168
|
_NAMES: set[str]
|
|
169
|
+
"""
|
|
170
|
+
Names of files in the directory
|
|
171
|
+
|
|
172
|
+
Example:
|
|
173
|
+
_NAMES = {"picture1", "picture2"}
|
|
174
|
+
"""
|
|
78
175
|
|
|
79
176
|
def __init__(self, parent: IAssetsProvider) -> None:
|
|
80
|
-
|
|
81
|
-
|
|
177
|
+
self._PATH = parent._PATH + self._PATH
|
|
178
|
+
|
|
179
|
+
if isinstance(parent, IAssetsProvider):
|
|
180
|
+
for filename in self._NAMES:
|
|
181
|
+
setattr(self, filename, parent.file_image(self._PATH + self._FILENAME.format(filename)))
|
|
82
182
|
|
|
83
183
|
class INetwork:
|
|
184
|
+
"""
|
|
185
|
+
Class that represents a set of images obtained from a network
|
|
186
|
+
"""
|
|
187
|
+
|
|
84
188
|
_URLS: dict[str, str]
|
|
189
|
+
"""
|
|
190
|
+
Dictionary with names of attributes and URLs
|
|
191
|
+
|
|
192
|
+
Example:
|
|
193
|
+
_URLS = {"example": "https://example.com/image.png"}
|
|
194
|
+
"""
|
|
85
195
|
|
|
86
196
|
def __init__(self, parent: IAssetsProvider) -> None:
|
|
87
|
-
|
|
88
|
-
|
|
197
|
+
if isinstance(parent, IAssetsProvider):
|
|
198
|
+
for name, url in self._URLS.items():
|
|
199
|
+
setattr(self, name, parent.network_image(url))
|
|
200
|
+
|
|
201
|
+
_PATH: str = utils.get_path("assets/")
|
|
202
|
+
"""
|
|
203
|
+
Path to the directory with assets folders
|
|
204
|
+
"""
|
|
89
205
|
|
|
90
|
-
_PATH: str
|
|
91
206
|
_ASSETS_OBJECTS: dict[str, type]
|
|
207
|
+
"""
|
|
208
|
+
Dictionary with names of attributes and child objects
|
|
209
|
+
|
|
210
|
+
Example:
|
|
211
|
+
_ASSETS_OBJECTS = {"images": ImagesAssets, "example": ExampleNetwork}
|
|
212
|
+
"""
|
|
92
213
|
|
|
93
214
|
def __init__(self) -> None:
|
|
94
|
-
for
|
|
95
|
-
setattr(self,
|
|
215
|
+
for name, data_class in self._ASSETS_OBJECTS.items():
|
|
216
|
+
setattr(self, name, data_class(self))
|
|
96
217
|
|
|
97
218
|
@staticmethod
|
|
98
219
|
def file_image(path: str) -> PIL.Image.Image:
|
|
220
|
+
"""
|
|
221
|
+
:return: Image object from a file
|
|
222
|
+
"""
|
|
223
|
+
|
|
99
224
|
with open(path, "rb") as file:
|
|
100
225
|
return PIL.Image.open(io.BytesIO(file.read()))
|
|
101
226
|
|
|
102
227
|
@staticmethod
|
|
103
228
|
def network_image(url: str) -> PIL.Image.Image:
|
|
229
|
+
"""
|
|
230
|
+
:return: Image object from a URL
|
|
231
|
+
"""
|
|
232
|
+
|
|
104
233
|
return PIL.Image.open(io.BytesIO(requests.get(url).content))
|
|
105
234
|
|
|
106
235
|
@staticmethod
|
|
107
236
|
def round_corners(image: PIL.Image.Image, radius: int) -> PIL.Image.Image:
|
|
237
|
+
"""
|
|
238
|
+
:return: Image with rounded edges of the specified radius
|
|
239
|
+
"""
|
|
240
|
+
|
|
108
241
|
if image.mode != "RGB":
|
|
109
242
|
image = image.convert("RGB")
|
|
110
243
|
width, height = image.size
|
|
244
|
+
|
|
111
245
|
shape = PIL.Image.new("L", (radius * 2, radius * 2), 0)
|
|
112
|
-
alpha = PIL.Image.new("L", image.size, "white")
|
|
113
246
|
PIL.ImageDraw.Draw(shape).ellipse((0, 0, radius * 2, radius * 2), fill=255)
|
|
247
|
+
|
|
248
|
+
alpha = PIL.Image.new("L", image.size, "white")
|
|
114
249
|
alpha.paste(shape.crop((0, 0, radius, radius)), (0, 0))
|
|
115
250
|
alpha.paste(shape.crop((0, radius, radius, radius * 2)), (0, height - radius))
|
|
116
251
|
alpha.paste(shape.crop((radius, 0, radius * 2, radius)), (width - radius, 0))
|
|
117
252
|
alpha.paste(shape.crop((radius, radius, radius * 2, radius * 2)), (width - radius, height - radius))
|
|
118
253
|
image.putalpha(alpha)
|
|
254
|
+
|
|
119
255
|
return image
|
|
120
256
|
|
|
121
257
|
|
|
122
258
|
class IStringsProvider:
|
|
259
|
+
"""
|
|
260
|
+
Class for providing various strings data
|
|
261
|
+
"""
|
|
262
|
+
|
|
123
263
|
class IStrings:
|
|
264
|
+
"""
|
|
265
|
+
Class that represents a container for strings
|
|
266
|
+
"""
|
|
267
|
+
|
|
124
268
|
pass
|
|
125
269
|
|
|
126
270
|
_STRINGS_OBJECTS: dict[str, type]
|
|
271
|
+
"""
|
|
272
|
+
Dictionary with names of attributes and child objects
|
|
273
|
+
|
|
274
|
+
Example:
|
|
275
|
+
_STRINGS_OBJECTS = {"localizable": LocalizableStrings}
|
|
276
|
+
"""
|
|
277
|
+
|
|
278
|
+
def __init__(self) -> None:
|
|
279
|
+
for name, data_class in self._STRINGS_OBJECTS.items():
|
|
280
|
+
setattr(self, name, data_class())
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
# endregion
|
|
284
|
+
|
|
285
|
+
# region Managers
|
|
286
|
+
|
|
287
|
+
class IDatabaseManager:
|
|
288
|
+
"""
|
|
289
|
+
Class for managing database connections
|
|
290
|
+
"""
|
|
291
|
+
|
|
292
|
+
class IDatabase(sqlite3.Connection):
|
|
293
|
+
"""
|
|
294
|
+
Class that represents a database connection
|
|
295
|
+
"""
|
|
296
|
+
|
|
297
|
+
_NAME: str = None
|
|
298
|
+
"""
|
|
299
|
+
Name of the database
|
|
300
|
+
|
|
301
|
+
Example:
|
|
302
|
+
_NAME = "users"
|
|
303
|
+
"""
|
|
304
|
+
|
|
305
|
+
_SQL: str = None
|
|
306
|
+
"""
|
|
307
|
+
SQL expression for creating a database
|
|
308
|
+
|
|
309
|
+
Example:
|
|
310
|
+
_SQL = f\"\"\"CREATE TABLE IF NOT EXISTS {_NAME} (user_id INTEGER PRIMARY KEY NOT NULL)\"\"\"
|
|
311
|
+
"""
|
|
312
|
+
|
|
313
|
+
_FILENAME: str = "{0}.db"
|
|
314
|
+
"""
|
|
315
|
+
File extension of database
|
|
316
|
+
"""
|
|
317
|
+
|
|
318
|
+
def __init__(self, parent: IDatabaseManager) -> None:
|
|
319
|
+
if isinstance(parent, IDatabaseManager):
|
|
320
|
+
self._FILENAME = self._FILENAME.format(self._NAME)
|
|
321
|
+
|
|
322
|
+
super().__init__(
|
|
323
|
+
database=parent._PATH + self._FILENAME,
|
|
324
|
+
check_same_thread=False,
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
self._cursor = self.cursor()
|
|
328
|
+
self._db_cursor.execute(self._SQL)
|
|
329
|
+
self.commit()
|
|
330
|
+
|
|
331
|
+
@property
|
|
332
|
+
def _db_cursor(self) -> sqlite3.Cursor:
|
|
333
|
+
return self._cursor
|
|
334
|
+
|
|
335
|
+
_PATH: str = utils.get_path("db/")
|
|
336
|
+
"""
|
|
337
|
+
Path to the directory with databases
|
|
338
|
+
"""
|
|
339
|
+
|
|
340
|
+
_DATABASE_OBJECTS: dict[str, type]
|
|
341
|
+
"""
|
|
342
|
+
Dictionary with names of attributes and child objects
|
|
343
|
+
|
|
344
|
+
Example:
|
|
345
|
+
_DATABASE_OBJECTS = {"users": UsersDatabase}
|
|
346
|
+
"""
|
|
127
347
|
|
|
128
348
|
def __init__(self) -> None:
|
|
129
|
-
|
|
130
|
-
|
|
349
|
+
os.makedirs(self._PATH, exist_ok=True)
|
|
350
|
+
|
|
351
|
+
for name, data_class in self._DATABASE_OBJECTS.items():
|
|
352
|
+
setattr(self, name, data_class(self))
|
|
353
|
+
|
|
354
|
+
def close_all(self) -> None:
|
|
355
|
+
"""
|
|
356
|
+
Closes all database connections
|
|
357
|
+
"""
|
|
358
|
+
for database in self._DATABASE_OBJECTS.keys():
|
|
359
|
+
getattr(self, database).close()
|
|
131
360
|
|
|
132
361
|
|
|
133
|
-
# Managers
|
|
134
362
|
if sys.platform == "win32":
|
|
135
363
|
import winreg
|
|
136
364
|
|
|
137
365
|
|
|
138
366
|
class IRegistryManager:
|
|
367
|
+
"""
|
|
368
|
+
Class for managing data in the Windows Registry
|
|
369
|
+
"""
|
|
370
|
+
|
|
139
371
|
class IRegistry:
|
|
372
|
+
"""
|
|
373
|
+
Class that represents a key with parameters in the Windows Registry
|
|
374
|
+
"""
|
|
375
|
+
|
|
140
376
|
_NAME: str = None
|
|
377
|
+
"""
|
|
378
|
+
Name of key in the Windows Registry
|
|
379
|
+
|
|
380
|
+
Example:
|
|
381
|
+
_NAME = "OAuth"
|
|
382
|
+
"""
|
|
383
|
+
|
|
141
384
|
_REGISTRY_VALUES: dict[str, int]
|
|
385
|
+
"""
|
|
386
|
+
Dictionary with settings and their types
|
|
387
|
+
"""
|
|
388
|
+
|
|
389
|
+
_path: winreg.HKEYType
|
|
142
390
|
|
|
143
391
|
def __init__(self, parent: IRegistryManager = None) -> None:
|
|
144
392
|
if isinstance(parent, IRegistryManager):
|
|
145
393
|
self._REGISTRY_VALUES = parent._REGISTRY_VALUES.get(self._NAME)
|
|
146
394
|
self._path = winreg.CreateKey(parent._path, self._NAME)
|
|
147
|
-
|
|
395
|
+
|
|
396
|
+
for setting in self._REGISTRY_VALUES.keys():
|
|
148
397
|
try:
|
|
149
|
-
setattr(self,
|
|
398
|
+
setattr(self, setting, winreg.QueryValueEx(self._path, setting)[int()])
|
|
150
399
|
except:
|
|
151
|
-
setattr(self,
|
|
400
|
+
setattr(self, setting, None)
|
|
152
401
|
|
|
153
402
|
@property
|
|
154
403
|
def values(self) -> dict | None:
|
|
404
|
+
"""
|
|
405
|
+
:return: Values stored in key in the Windows Registry
|
|
406
|
+
"""
|
|
407
|
+
|
|
155
408
|
try:
|
|
156
|
-
return {
|
|
409
|
+
return {setting: getattr(self, setting) for setting in self._REGISTRY_VALUES.keys()}
|
|
157
410
|
except:
|
|
158
411
|
return None
|
|
159
412
|
|
|
160
413
|
def refresh(self) -> IRegistryManager.IRegistry:
|
|
414
|
+
"""
|
|
415
|
+
:return: Instance with refreshed values
|
|
416
|
+
"""
|
|
417
|
+
|
|
161
418
|
self.__init__()
|
|
162
419
|
return self
|
|
163
420
|
|
|
164
421
|
def update(self, **kwargs) -> None:
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
422
|
+
"""
|
|
423
|
+
Updates provided settings in the Windows Registry
|
|
424
|
+
"""
|
|
425
|
+
|
|
426
|
+
for setting, value in kwargs.items():
|
|
427
|
+
winreg.SetValueEx(self._path, setting, None, self._REGISTRY_VALUES.get(setting), value)
|
|
428
|
+
setattr(self, setting, value)
|
|
168
429
|
|
|
169
430
|
_KEY: str
|
|
431
|
+
"""
|
|
432
|
+
Path to key in the Windows Registry
|
|
433
|
+
|
|
434
|
+
Example:
|
|
435
|
+
_KEY = "Software\\\\\\\\diquoks Software\\\\\\\\pyquoks"
|
|
436
|
+
"""
|
|
437
|
+
|
|
170
438
|
_REGISTRY_VALUES: dict[str, dict[str, int]]
|
|
439
|
+
"""
|
|
440
|
+
Dictionary with keys, their settings and their types
|
|
441
|
+
|
|
442
|
+
Example:
|
|
443
|
+
_REGISTRY_VALUES = {"OAuth": {"access_token": winreg.REG_SZ}}
|
|
444
|
+
"""
|
|
445
|
+
|
|
171
446
|
_REGISTRY_OBJECTS: dict[str, type]
|
|
447
|
+
"""
|
|
448
|
+
Dictionary with names of attributes and child objects
|
|
449
|
+
|
|
450
|
+
Example:
|
|
451
|
+
_REGISTRY_OBJECTS = {"oauth": OAuthRegistry}
|
|
452
|
+
"""
|
|
453
|
+
|
|
172
454
|
_path: winreg.HKEYType
|
|
173
455
|
|
|
174
456
|
def __init__(self) -> None:
|
|
175
457
|
self._path = winreg.CreateKey(winreg.HKEY_CURRENT_USER, self._KEY)
|
|
176
|
-
|
|
177
|
-
|
|
458
|
+
|
|
459
|
+
for name, data_class in self._REGISTRY_OBJECTS.items():
|
|
460
|
+
setattr(self, name, data_class(self))
|
|
178
461
|
|
|
179
462
|
def refresh(self) -> IRegistryManager:
|
|
463
|
+
"""
|
|
464
|
+
:return: Instance with refreshed values
|
|
465
|
+
"""
|
|
466
|
+
|
|
180
467
|
self.__init__()
|
|
181
468
|
return self
|
|
182
469
|
|
|
183
470
|
|
|
184
|
-
#
|
|
471
|
+
# endregion
|
|
472
|
+
|
|
473
|
+
# region Services
|
|
474
|
+
|
|
185
475
|
class LoggerService(logging.Logger):
|
|
476
|
+
"""
|
|
477
|
+
Class that provides methods for parallel logging
|
|
478
|
+
"""
|
|
479
|
+
|
|
186
480
|
def __init__(
|
|
187
481
|
self,
|
|
188
482
|
name: str, file_handling: bool = True,
|
|
189
483
|
filename: str = datetime.datetime.now().strftime("%d-%m-%y-%H-%M-%S"),
|
|
190
484
|
level: int = logging.NOTSET,
|
|
191
|
-
|
|
485
|
+
path: str = utils.get_path("logs/", only_abspath=True),
|
|
192
486
|
) -> None:
|
|
193
487
|
super().__init__(name, level)
|
|
488
|
+
|
|
194
489
|
stream_handler = logging.StreamHandler(sys.stdout)
|
|
195
490
|
stream_handler.setFormatter(
|
|
196
|
-
logging.Formatter(
|
|
491
|
+
logging.Formatter(
|
|
492
|
+
fmt="$levelname $asctime $name - $message",
|
|
493
|
+
datefmt="%d-%m-%y %H:%M:%S",
|
|
494
|
+
style="$",
|
|
495
|
+
)
|
|
197
496
|
)
|
|
198
497
|
self.addHandler(stream_handler)
|
|
498
|
+
|
|
199
499
|
if file_handling:
|
|
200
|
-
os.makedirs(
|
|
500
|
+
os.makedirs(path, exist_ok=True)
|
|
501
|
+
|
|
201
502
|
file_handler = logging.FileHandler(
|
|
202
|
-
|
|
503
|
+
path + f"{filename}-{name}.log",
|
|
203
504
|
encoding="utf-8",
|
|
204
505
|
)
|
|
205
506
|
file_handler.setFormatter(
|
|
206
|
-
logging.Formatter(
|
|
507
|
+
logging.Formatter(
|
|
508
|
+
fmt="$levelname $asctime - $message",
|
|
509
|
+
datefmt="%d-%m-%y %H:%M:%S",
|
|
510
|
+
style="$",
|
|
511
|
+
),
|
|
207
512
|
)
|
|
208
513
|
self.addHandler(file_handler)
|
|
209
514
|
|
|
210
515
|
def log_exception(self, e: Exception) -> None:
|
|
516
|
+
"""
|
|
517
|
+
Logs an exception with detailed traceback
|
|
518
|
+
"""
|
|
519
|
+
|
|
211
520
|
self.error(msg=e, exc_info=True)
|
|
521
|
+
|
|
522
|
+
# endregion
|
pyquoks/localhost.py
CHANGED
|
@@ -1,17 +1,35 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
-
import
|
|
2
|
+
import typing
|
|
3
|
+
import waitress, flask
|
|
3
4
|
|
|
4
5
|
|
|
5
6
|
class ILocalhostFlask(flask.Flask):
|
|
7
|
+
"""
|
|
8
|
+
Class for creating a simple localhost server
|
|
9
|
+
"""
|
|
10
|
+
|
|
6
11
|
_RULES: dict[str, typing.Callable]
|
|
12
|
+
"""
|
|
13
|
+
Dictionary with rules and functions
|
|
14
|
+
|
|
15
|
+
Example:
|
|
16
|
+
_RULES = {"/": base_redirect}
|
|
17
|
+
"""
|
|
7
18
|
|
|
8
19
|
def __init__(self, import_name: str) -> None:
|
|
9
20
|
super().__init__(import_name)
|
|
10
21
|
|
|
11
|
-
for
|
|
12
|
-
self.add_url_rule(
|
|
22
|
+
for rule, view_func in self._RULES.items():
|
|
23
|
+
self.add_url_rule(
|
|
24
|
+
rule=rule,
|
|
25
|
+
view_func=view_func,
|
|
26
|
+
)
|
|
13
27
|
|
|
14
28
|
def serve(self, port: int) -> None:
|
|
29
|
+
"""
|
|
30
|
+
Starts this Flask application
|
|
31
|
+
"""
|
|
32
|
+
|
|
15
33
|
waitress.serve(
|
|
16
34
|
app=self,
|
|
17
35
|
host="127.0.0.1",
|
pyquoks/models.py
CHANGED
|
@@ -2,62 +2,137 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
class IContainer:
|
|
5
|
-
|
|
5
|
+
"""
|
|
6
|
+
Class for storing lists of models and another parameters
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
_ATTRIBUTES: set[str] | dict[str, set[str]] = None
|
|
10
|
+
"""
|
|
11
|
+
Set of parameters that also can be stored one key deep
|
|
12
|
+
|
|
13
|
+
Example:
|
|
14
|
+
_ATTRIBUTES = {"beatmap_id", "score_id"}
|
|
15
|
+
|
|
16
|
+
_ATTRIBUTES = {"attributes": {"max_combo", "star_rating"}}
|
|
17
|
+
"""
|
|
18
|
+
|
|
6
19
|
_DATA: dict[str, type] = None
|
|
20
|
+
"""
|
|
21
|
+
Dictionary with name and type of models stored in list inside ``json_data``
|
|
22
|
+
|
|
23
|
+
Example:
|
|
24
|
+
_DATA = {"scores": ScoreModel}
|
|
25
|
+
"""
|
|
26
|
+
|
|
7
27
|
_OBJECTS: dict[str, type] = None
|
|
8
|
-
|
|
28
|
+
"""
|
|
29
|
+
Dictionary with keys of ``json_data`` and types of models stored in a list inside
|
|
30
|
+
|
|
31
|
+
Example:
|
|
32
|
+
_OBJECTS = {"beatmaps": BeatmapModel, "scores": ScoreModel}
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
_data: dict
|
|
36
|
+
"""
|
|
37
|
+
Initial data that was passed into object
|
|
38
|
+
"""
|
|
9
39
|
|
|
10
40
|
def __init__(self, json_data: dict) -> None:
|
|
11
|
-
|
|
41
|
+
self._data = json_data
|
|
42
|
+
|
|
12
43
|
if isinstance(self._ATTRIBUTES, set):
|
|
13
|
-
for
|
|
14
|
-
setattr(self,
|
|
44
|
+
for name in self._ATTRIBUTES:
|
|
45
|
+
setattr(self, name, self._data.get(name, None))
|
|
46
|
+
elif isinstance(self._ATTRIBUTES, dict):
|
|
47
|
+
for key, attributes in self._ATTRIBUTES.items():
|
|
48
|
+
if isinstance(attributes, set):
|
|
49
|
+
for name in attributes:
|
|
50
|
+
try:
|
|
51
|
+
setattr(self, name, self._data.get(key).get(name, None))
|
|
52
|
+
except:
|
|
53
|
+
setattr(self, name, None)
|
|
54
|
+
|
|
15
55
|
if isinstance(self._DATA, dict):
|
|
16
|
-
for
|
|
56
|
+
for name, data_class in self._DATA.items():
|
|
17
57
|
try:
|
|
18
|
-
setattr(self,
|
|
58
|
+
setattr(self, name, [data_class(data) for data in self._data])
|
|
19
59
|
except:
|
|
20
|
-
setattr(self,
|
|
60
|
+
setattr(self, name, None)
|
|
21
61
|
elif isinstance(self._OBJECTS, dict):
|
|
22
|
-
for
|
|
62
|
+
for name, data_class in self._OBJECTS.items():
|
|
23
63
|
try:
|
|
24
|
-
setattr(self,
|
|
64
|
+
setattr(self, name, [data_class(data) for data in self._data.get(name)])
|
|
25
65
|
except:
|
|
26
|
-
setattr(self,
|
|
66
|
+
setattr(self, name, None)
|
|
27
67
|
|
|
28
68
|
|
|
29
69
|
class IModel:
|
|
70
|
+
"""
|
|
71
|
+
Class for storing parameters and models
|
|
72
|
+
"""
|
|
73
|
+
|
|
30
74
|
_ATTRIBUTES: set[str] | dict[str, set[str]] = None
|
|
75
|
+
"""
|
|
76
|
+
Set of parameters that also can be stored one key deep
|
|
77
|
+
|
|
78
|
+
Example:
|
|
79
|
+
_ATTRIBUTES = {"beatmap_id", "score_id"}
|
|
80
|
+
|
|
81
|
+
_ATTRIBUTES = {"attributes": {"max_combo", "star_rating"}}
|
|
82
|
+
"""
|
|
83
|
+
|
|
31
84
|
_OBJECTS: dict[str, type] = None
|
|
32
|
-
|
|
85
|
+
"""
|
|
86
|
+
Dictionary with attributes and their models
|
|
87
|
+
|
|
88
|
+
Example:
|
|
89
|
+
_OBJECTS = {"score": ScoreModel}
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
_data: dict | list[dict]
|
|
93
|
+
"""
|
|
94
|
+
Initial data that was passed into object
|
|
95
|
+
"""
|
|
33
96
|
|
|
34
97
|
def __init__(self, json_data: dict | list[dict]) -> None:
|
|
35
|
-
|
|
98
|
+
self._data = json_data
|
|
99
|
+
|
|
36
100
|
if isinstance(self._ATTRIBUTES, set):
|
|
37
|
-
for
|
|
38
|
-
setattr(self,
|
|
101
|
+
for name in self._ATTRIBUTES:
|
|
102
|
+
setattr(self, name, self._data.get(name, None))
|
|
39
103
|
elif isinstance(self._ATTRIBUTES, dict):
|
|
40
|
-
for
|
|
41
|
-
if isinstance(
|
|
42
|
-
for
|
|
104
|
+
for key, attributes in self._ATTRIBUTES.items():
|
|
105
|
+
if isinstance(attributes, set):
|
|
106
|
+
for name in attributes:
|
|
43
107
|
try:
|
|
44
|
-
setattr(self,
|
|
108
|
+
setattr(self, name, self._data.get(key).get(name, None))
|
|
45
109
|
except:
|
|
46
|
-
setattr(self,
|
|
110
|
+
setattr(self, name, None)
|
|
111
|
+
|
|
47
112
|
if isinstance(self._OBJECTS, dict):
|
|
48
|
-
for
|
|
113
|
+
for name, data_class in self._OBJECTS.items():
|
|
49
114
|
try:
|
|
50
|
-
setattr(self,
|
|
115
|
+
setattr(self, name, data_class(self._data.get(name)))
|
|
51
116
|
except:
|
|
52
|
-
setattr(self,
|
|
117
|
+
setattr(self, name, None)
|
|
53
118
|
|
|
54
119
|
|
|
55
120
|
class IValues:
|
|
121
|
+
"""
|
|
122
|
+
Class for storing various parameters and values
|
|
123
|
+
"""
|
|
124
|
+
|
|
56
125
|
_ATTRIBUTES: set[str] = None
|
|
126
|
+
"""
|
|
127
|
+
Attributes that can be stored in this class
|
|
128
|
+
|
|
129
|
+
Example:
|
|
130
|
+
_ATTRIBUTES = {"settings", "path"}
|
|
131
|
+
"""
|
|
57
132
|
|
|
58
133
|
def __init__(self, **kwargs) -> None:
|
|
59
|
-
for
|
|
60
|
-
setattr(self,
|
|
134
|
+
for name in self._ATTRIBUTES:
|
|
135
|
+
setattr(self, name, kwargs.get(name, None))
|
|
61
136
|
|
|
62
137
|
def update(self, **kwargs) -> None:
|
|
63
138
|
self.__init__(**kwargs)
|
pyquoks/utils.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyquoks
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
4
4
|
Summary: Пакет PyPI для часто используемых модулей в проектах diquoks
|
|
5
5
|
Home-page: https://diquoks.ru
|
|
6
6
|
Author: Denis Titovets
|
|
@@ -12,7 +12,7 @@ Requires-Python: >=3.10
|
|
|
12
12
|
Description-Content-Type: text/markdown
|
|
13
13
|
License-File: LICENSE
|
|
14
14
|
Requires-Dist: blinker==1.9.0
|
|
15
|
-
Requires-Dist: certifi==2025.
|
|
15
|
+
Requires-Dist: certifi==2025.10.5
|
|
16
16
|
Requires-Dist: charset-normalizer==3.4.3
|
|
17
17
|
Requires-Dist: click==8.3.0
|
|
18
18
|
Requires-Dist: colorama==0.4.6
|
|
@@ -20,7 +20,7 @@ Requires-Dist: Flask==3.1.2
|
|
|
20
20
|
Requires-Dist: idna==3.10
|
|
21
21
|
Requires-Dist: itsdangerous==2.2.0
|
|
22
22
|
Requires-Dist: Jinja2==3.1.6
|
|
23
|
-
Requires-Dist: MarkupSafe==3.0.
|
|
23
|
+
Requires-Dist: MarkupSafe==3.0.3
|
|
24
24
|
Requires-Dist: pillow==11.3.0
|
|
25
25
|
Requires-Dist: requests==2.32.5
|
|
26
26
|
Requires-Dist: urllib3==2.5.0
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
pyquoks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
pyquoks/data.py,sha256=5PkERLBiNmuEYy8mYTe2AWUUEPt3lAtH5tG9TlyBl4w,15399
|
|
3
|
+
pyquoks/localhost.py,sha256=2sl1CovYrnIzBYW-eKDIYOfi5bUsSVTAdQobzAHpIys,828
|
|
4
|
+
pyquoks/models.py,sha256=kdduuHgh8RLe8_S5Lmv3jS6hqsZG-oYQhPTgk2C11ug,4171
|
|
5
|
+
pyquoks/utils.py,sha256=lcHZyIFIflGMqAhDkqm8GWOUFwb2f-IHgIPZxEiHBz0,484
|
|
6
|
+
pyquoks-1.3.0.dist-info/licenses/LICENSE,sha256=eEd8UIYxvKUY7vqrV1XTFo70_FQdiW6o1fznseCXRJs,1095
|
|
7
|
+
pyquoks-1.3.0.dist-info/METADATA,sha256=sCQPZSFYT7zfM4hFygVOeCWHXz1QEt7i9oOzhmKGNgw,1802
|
|
8
|
+
pyquoks-1.3.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
9
|
+
pyquoks-1.3.0.dist-info/top_level.txt,sha256=4Eqn44TCCp-D8V6To92e8-KKr49ZGf0dCcsdRQmPkhc,8
|
|
10
|
+
pyquoks-1.3.0.dist-info/RECORD,,
|
pyquoks-1.2.2.dist-info/RECORD
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
pyquoks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
pyquoks/data.py,sha256=S74VxkQtTQipfa1Z7LYiU5RTWSxhnGNhbtv0PB3glf4,8124
|
|
3
|
-
pyquoks/localhost.py,sha256=mu55k4d1AZJ77IwavvOEEz9m3fNI6s_q97MSb90vy2Y,494
|
|
4
|
-
pyquoks/models.py,sha256=_2mxn7nBZzm8jALJSKcFHal06RGh2whHYp7De-bBxqk,2187
|
|
5
|
-
pyquoks/utils.py,sha256=5WswiChwsjJK3IWADizh_JnW0hmIP4PJQyXmhNlPExs,409
|
|
6
|
-
pyquoks-1.2.2.dist-info/licenses/LICENSE,sha256=eEd8UIYxvKUY7vqrV1XTFo70_FQdiW6o1fznseCXRJs,1095
|
|
7
|
-
pyquoks-1.2.2.dist-info/METADATA,sha256=Kx5JgLsBKQT4nAjuySiVNUiW2PObgh5H7isZZCwpykA,1801
|
|
8
|
-
pyquoks-1.2.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
9
|
-
pyquoks-1.2.2.dist-info/top_level.txt,sha256=4Eqn44TCCp-D8V6To92e8-KKr49ZGf0dCcsdRQmPkhc,8
|
|
10
|
-
pyquoks-1.2.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|