pyquoks 1.2.1__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 +408 -80
- pyquoks/localhost.py +21 -3
- pyquoks/models.py +100 -25
- pyquoks/utils.py +5 -0
- {pyquoks-1.2.1.dist-info → pyquoks-1.3.0.dist-info}/METADATA +4 -3
- pyquoks-1.3.0.dist-info/RECORD +10 -0
- pyquoks-1.2.1.dist-info/RECORD +0 -10
- {pyquoks-1.2.1.dist-info → pyquoks-1.3.0.dist-info}/WHEEL +0 -0
- {pyquoks-1.2.1.dist-info → pyquoks-1.3.0.dist-info}/licenses/LICENSE +0 -0
- {pyquoks-1.2.1.dist-info → pyquoks-1.3.0.dist-info}/top_level.txt +0 -0
pyquoks/data.py
CHANGED
|
@@ -1,194 +1,522 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
-
import configparser, datetime, logging,
|
|
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
|
+
"""
|
|
127
277
|
|
|
128
278
|
def __init__(self) -> None:
|
|
129
|
-
for
|
|
130
|
-
setattr(self,
|
|
279
|
+
for name, data_class in self._STRINGS_OBJECTS.items():
|
|
280
|
+
setattr(self, name, data_class())
|
|
281
|
+
|
|
131
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
|
+
"""
|
|
132
296
|
|
|
133
|
-
# Managers
|
|
134
|
-
class IRegistryManager:
|
|
135
|
-
class IRegistry:
|
|
136
297
|
_NAME: str = None
|
|
137
|
-
|
|
298
|
+
"""
|
|
299
|
+
Name of the database
|
|
138
300
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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()
|
|
148
330
|
|
|
149
331
|
@property
|
|
150
|
-
def
|
|
151
|
-
|
|
152
|
-
return {i: getattr(self, i) for i in self._REGISTRY_VALUES}
|
|
153
|
-
except:
|
|
154
|
-
return None
|
|
332
|
+
def _db_cursor(self) -> sqlite3.Cursor:
|
|
333
|
+
return self._cursor
|
|
155
334
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
335
|
+
_PATH: str = utils.get_path("db/")
|
|
336
|
+
"""
|
|
337
|
+
Path to the directory with databases
|
|
338
|
+
"""
|
|
159
339
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
setattr(self, k, v)
|
|
340
|
+
_DATABASE_OBJECTS: dict[str, type]
|
|
341
|
+
"""
|
|
342
|
+
Dictionary with names of attributes and child objects
|
|
164
343
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
_path: winreg.HKEYType
|
|
344
|
+
Example:
|
|
345
|
+
_DATABASE_OBJECTS = {"users": UsersDatabase}
|
|
346
|
+
"""
|
|
169
347
|
|
|
170
348
|
def __init__(self) -> None:
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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()
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
if sys.platform == "win32":
|
|
363
|
+
import winreg
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
class IRegistryManager:
|
|
367
|
+
"""
|
|
368
|
+
Class for managing data in the Windows Registry
|
|
369
|
+
"""
|
|
370
|
+
|
|
371
|
+
class IRegistry:
|
|
372
|
+
"""
|
|
373
|
+
Class that represents a key with parameters in the Windows Registry
|
|
374
|
+
"""
|
|
375
|
+
|
|
376
|
+
_NAME: str = None
|
|
377
|
+
"""
|
|
378
|
+
Name of key in the Windows Registry
|
|
379
|
+
|
|
380
|
+
Example:
|
|
381
|
+
_NAME = "OAuth"
|
|
382
|
+
"""
|
|
383
|
+
|
|
384
|
+
_REGISTRY_VALUES: dict[str, int]
|
|
385
|
+
"""
|
|
386
|
+
Dictionary with settings and their types
|
|
387
|
+
"""
|
|
388
|
+
|
|
389
|
+
_path: winreg.HKEYType
|
|
390
|
+
|
|
391
|
+
def __init__(self, parent: IRegistryManager = None) -> None:
|
|
392
|
+
if isinstance(parent, IRegistryManager):
|
|
393
|
+
self._REGISTRY_VALUES = parent._REGISTRY_VALUES.get(self._NAME)
|
|
394
|
+
self._path = winreg.CreateKey(parent._path, self._NAME)
|
|
395
|
+
|
|
396
|
+
for setting in self._REGISTRY_VALUES.keys():
|
|
397
|
+
try:
|
|
398
|
+
setattr(self, setting, winreg.QueryValueEx(self._path, setting)[int()])
|
|
399
|
+
except:
|
|
400
|
+
setattr(self, setting, None)
|
|
174
401
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
402
|
+
@property
|
|
403
|
+
def values(self) -> dict | None:
|
|
404
|
+
"""
|
|
405
|
+
:return: Values stored in key in the Windows Registry
|
|
406
|
+
"""
|
|
178
407
|
|
|
408
|
+
try:
|
|
409
|
+
return {setting: getattr(self, setting) for setting in self._REGISTRY_VALUES.keys()}
|
|
410
|
+
except:
|
|
411
|
+
return None
|
|
412
|
+
|
|
413
|
+
def refresh(self) -> IRegistryManager.IRegistry:
|
|
414
|
+
"""
|
|
415
|
+
:return: Instance with refreshed values
|
|
416
|
+
"""
|
|
417
|
+
|
|
418
|
+
self.__init__()
|
|
419
|
+
return self
|
|
420
|
+
|
|
421
|
+
def update(self, **kwargs) -> None:
|
|
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)
|
|
429
|
+
|
|
430
|
+
_KEY: str
|
|
431
|
+
"""
|
|
432
|
+
Path to key in the Windows Registry
|
|
433
|
+
|
|
434
|
+
Example:
|
|
435
|
+
_KEY = "Software\\\\\\\\diquoks Software\\\\\\\\pyquoks"
|
|
436
|
+
"""
|
|
437
|
+
|
|
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
|
+
|
|
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
|
+
|
|
454
|
+
_path: winreg.HKEYType
|
|
455
|
+
|
|
456
|
+
def __init__(self) -> None:
|
|
457
|
+
self._path = winreg.CreateKey(winreg.HKEY_CURRENT_USER, self._KEY)
|
|
458
|
+
|
|
459
|
+
for name, data_class in self._REGISTRY_OBJECTS.items():
|
|
460
|
+
setattr(self, name, data_class(self))
|
|
461
|
+
|
|
462
|
+
def refresh(self) -> IRegistryManager:
|
|
463
|
+
"""
|
|
464
|
+
:return: Instance with refreshed values
|
|
465
|
+
"""
|
|
466
|
+
|
|
467
|
+
self.__init__()
|
|
468
|
+
return self
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
# endregion
|
|
472
|
+
|
|
473
|
+
# region Services
|
|
179
474
|
|
|
180
|
-
# Services
|
|
181
475
|
class LoggerService(logging.Logger):
|
|
182
|
-
|
|
476
|
+
"""
|
|
477
|
+
Class that provides methods for parallel logging
|
|
478
|
+
"""
|
|
479
|
+
|
|
480
|
+
def __init__(
|
|
481
|
+
self,
|
|
482
|
+
name: str, file_handling: bool = True,
|
|
483
|
+
filename: str = datetime.datetime.now().strftime("%d-%m-%y-%H-%M-%S"),
|
|
484
|
+
level: int = logging.NOTSET,
|
|
485
|
+
path: str = utils.get_path("logs/", only_abspath=True),
|
|
486
|
+
) -> None:
|
|
183
487
|
super().__init__(name, level)
|
|
488
|
+
|
|
184
489
|
stream_handler = logging.StreamHandler(sys.stdout)
|
|
185
|
-
stream_handler.setFormatter(
|
|
490
|
+
stream_handler.setFormatter(
|
|
491
|
+
logging.Formatter(
|
|
492
|
+
fmt="$levelname $asctime $name - $message",
|
|
493
|
+
datefmt="%d-%m-%y %H:%M:%S",
|
|
494
|
+
style="$",
|
|
495
|
+
)
|
|
496
|
+
)
|
|
186
497
|
self.addHandler(stream_handler)
|
|
498
|
+
|
|
187
499
|
if file_handling:
|
|
188
|
-
os.makedirs(
|
|
189
|
-
|
|
190
|
-
file_handler
|
|
500
|
+
os.makedirs(path, exist_ok=True)
|
|
501
|
+
|
|
502
|
+
file_handler = logging.FileHandler(
|
|
503
|
+
path + f"{filename}-{name}.log",
|
|
504
|
+
encoding="utf-8",
|
|
505
|
+
)
|
|
506
|
+
file_handler.setFormatter(
|
|
507
|
+
logging.Formatter(
|
|
508
|
+
fmt="$levelname $asctime - $message",
|
|
509
|
+
datefmt="%d-%m-%y %H:%M:%S",
|
|
510
|
+
style="$",
|
|
511
|
+
),
|
|
512
|
+
)
|
|
191
513
|
self.addHandler(file_handler)
|
|
192
514
|
|
|
193
515
|
def log_exception(self, e: Exception) -> None:
|
|
516
|
+
"""
|
|
517
|
+
Logs an exception with detailed traceback
|
|
518
|
+
"""
|
|
519
|
+
|
|
194
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 typing
|
|
2
3
|
import waitress, flask
|
|
3
4
|
|
|
4
5
|
|
|
5
6
|
class ILocalhostFlask(flask.Flask):
|
|
6
|
-
|
|
7
|
+
"""
|
|
8
|
+
Class for creating a simple localhost server
|
|
9
|
+
"""
|
|
10
|
+
|
|
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
|
@@ -3,7 +3,12 @@ import sys, os
|
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
def get_path(relative_path: str, only_abspath: bool = False) -> str:
|
|
6
|
+
"""
|
|
7
|
+
:return: Absolute path for provided relative path
|
|
8
|
+
"""
|
|
9
|
+
|
|
6
10
|
try:
|
|
11
|
+
# noinspection PyUnresolvedReferences
|
|
7
12
|
base_path = sys._MEIPASS
|
|
8
13
|
except:
|
|
9
14
|
base_path = os.path.abspath(".")
|
|
@@ -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
|
|
@@ -46,6 +46,7 @@ Dynamic: license-file
|
|
|
46
46
|
|
|
47
47
|
#### Связь с разработчиком
|
|
48
48
|
|
|
49
|
+
- [План разработки pyquoks](https://www.icloud.com/notes/0e0C-Bm4IkqXuBYqJrq00yhog)
|
|
49
50
|
- [Telegram для связи](https://t.me/diquoks)
|
|
50
51
|
- [Почта для связи](mailto:diquoks@yandex.ru)
|
|
51
52
|
|
|
@@ -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.1.dist-info/RECORD
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
pyquoks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
pyquoks/data.py,sha256=9vL7UciizlTp5-wHnvdTey2FuqGxC5wf-NOKX9wpN-I,7752
|
|
3
|
-
pyquoks/localhost.py,sha256=X3JPGybKjiQLl7jqkyGFt_1D2jTNyFr_2qGu9FmKceI,479
|
|
4
|
-
pyquoks/models.py,sha256=_2mxn7nBZzm8jALJSKcFHal06RGh2whHYp7De-bBxqk,2187
|
|
5
|
-
pyquoks/utils.py,sha256=uQd1GoMOBHzab6xfAVovgG89MoXKKDqhD3ep8bs1lc4,362
|
|
6
|
-
pyquoks-1.2.1.dist-info/licenses/LICENSE,sha256=eEd8UIYxvKUY7vqrV1XTFo70_FQdiW6o1fznseCXRJs,1095
|
|
7
|
-
pyquoks-1.2.1.dist-info/METADATA,sha256=ZreScvmW3tV8Dj7r6l2Be0sH7trgjr25GQKorGVnskA,1702
|
|
8
|
-
pyquoks-1.2.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
9
|
-
pyquoks-1.2.1.dist-info/top_level.txt,sha256=4Eqn44TCCp-D8V6To92e8-KKr49ZGf0dCcsdRQmPkhc,8
|
|
10
|
-
pyquoks-1.2.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|