pyquoks 1.2.1__tar.gz → 1.3.0__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyquoks
3
- Version: 1.2.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.8.3
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.2
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
 
@@ -16,6 +16,7 @@
16
16
 
17
17
  #### Связь с разработчиком
18
18
 
19
+ - [План разработки pyquoks](https://www.icloud.com/notes/0e0C-Bm4IkqXuBYqJrq00yhog)
19
20
  - [Telegram для связи](https://t.me/diquoks)
20
21
  - [Почта для связи](mailto:diquoks@yandex.ru)
21
22
 
@@ -1,5 +1,5 @@
1
1
  blinker==1.9.0
2
- certifi==2025.8.3
2
+ certifi==2025.10.5
3
3
  charset-normalizer==3.4.3
4
4
  click==8.3.0
5
5
  colorama==0.4.6
@@ -7,9 +7,9 @@ Flask==3.1.2
7
7
  idna==3.10
8
8
  itsdangerous==2.2.0
9
9
  Jinja2==3.1.6
10
- MarkupSafe==3.0.2
10
+ MarkupSafe==3.0.3
11
11
  pillow==11.3.0
12
12
  requests==2.32.5
13
13
  urllib3==2.5.0
14
14
  waitress==3.0.2
15
- Werkzeug==3.1.3
15
+ Werkzeug==3.1.3
@@ -1,6 +1,6 @@
1
1
  [metadata]
2
2
  name = pyquoks
3
- version = 1.2.1
3
+ version = 1.3.0
4
4
  author = Denis Titovets
5
5
  author_email = den232titovets@yandex.ru
6
6
  description = Пакет PyPI для часто используемых модулей в проектах diquoks
@@ -0,0 +1,522 @@
1
+ from __future__ import annotations
2
+ import configparser, datetime, logging, sqlite3, json, sys, io, os
3
+ import requests, PIL.Image, PIL.ImageDraw
4
+ from . import utils
5
+
6
+
7
+ # region Providers
8
+
9
+ class IDataProvider:
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
+
24
+ _DATA_VALUES: dict[str, type]
25
+ """
26
+ Dictionary with filenames and containers
27
+
28
+ Example:
29
+ _DATA_VALUES = {"users": UsersContainer}
30
+ """
31
+
32
+ def __init__(self) -> None:
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)
39
+
40
+
41
+ class IConfigProvider:
42
+ """
43
+ Class for providing data from configuration file
44
+ """
45
+
46
+ class IConfig:
47
+ """
48
+ Class that represents a section in configuration file
49
+ """
50
+
51
+ _SECTION: str = None
52
+ """
53
+ Name of the section in configuration file
54
+
55
+ Example:
56
+ _SECTION = "Settings"
57
+ """
58
+
59
+ _CONFIG_VALUES: dict[str, type]
60
+ """
61
+ Dictionary with settings and their types
62
+ """
63
+
64
+ def __init__(self, parent: IConfigProvider = None) -> None:
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 "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)))
98
+ except:
99
+ setattr(self, setting, None)
100
+ raise self._incorrect_content_exception
101
+
102
+ if not self.values:
103
+ raise self._incorrect_content_exception
104
+
105
+ @property
106
+ def values(self) -> dict | None:
107
+ """
108
+ :return: Values stored in section
109
+ """
110
+
111
+ try:
112
+ return {setting: getattr(self, setting) for setting in self._CONFIG_VALUES.keys()}
113
+ except:
114
+ return None
115
+
116
+ _PATH: str = utils.get_path("config.ini")
117
+ """
118
+ Path to the configuration file
119
+ """
120
+
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
+
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
+ """
136
+
137
+ def __init__(self) -> None:
138
+ for name, data_class in self._CONFIG_OBJECTS.items():
139
+ setattr(self, name, data_class(self))
140
+
141
+
142
+ class IAssetsProvider:
143
+ """
144
+ Class for providing various assets data
145
+ """
146
+
147
+ class IDirectory:
148
+ """
149
+ Class that represents a directory with various assets
150
+ """
151
+
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
+
168
+ _NAMES: set[str]
169
+ """
170
+ Names of files in the directory
171
+
172
+ Example:
173
+ _NAMES = {"picture1", "picture2"}
174
+ """
175
+
176
+ def __init__(self, parent: IAssetsProvider) -> None:
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)))
182
+
183
+ class INetwork:
184
+ """
185
+ Class that represents a set of images obtained from a network
186
+ """
187
+
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
+ """
195
+
196
+ def __init__(self, parent: IAssetsProvider) -> None:
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
+ """
205
+
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
+ """
213
+
214
+ def __init__(self) -> None:
215
+ for name, data_class in self._ASSETS_OBJECTS.items():
216
+ setattr(self, name, data_class(self))
217
+
218
+ @staticmethod
219
+ def file_image(path: str) -> PIL.Image.Image:
220
+ """
221
+ :return: Image object from a file
222
+ """
223
+
224
+ with open(path, "rb") as file:
225
+ return PIL.Image.open(io.BytesIO(file.read()))
226
+
227
+ @staticmethod
228
+ def network_image(url: str) -> PIL.Image.Image:
229
+ """
230
+ :return: Image object from a URL
231
+ """
232
+
233
+ return PIL.Image.open(io.BytesIO(requests.get(url).content))
234
+
235
+ @staticmethod
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
+
241
+ if image.mode != "RGB":
242
+ image = image.convert("RGB")
243
+ width, height = image.size
244
+
245
+ shape = PIL.Image.new("L", (radius * 2, radius * 2), 0)
246
+ PIL.ImageDraw.Draw(shape).ellipse((0, 0, radius * 2, radius * 2), fill=255)
247
+
248
+ alpha = PIL.Image.new("L", image.size, "white")
249
+ alpha.paste(shape.crop((0, 0, radius, radius)), (0, 0))
250
+ alpha.paste(shape.crop((0, radius, radius, radius * 2)), (0, height - radius))
251
+ alpha.paste(shape.crop((radius, 0, radius * 2, radius)), (width - radius, 0))
252
+ alpha.paste(shape.crop((radius, radius, radius * 2, radius * 2)), (width - radius, height - radius))
253
+ image.putalpha(alpha)
254
+
255
+ return image
256
+
257
+
258
+ class IStringsProvider:
259
+ """
260
+ Class for providing various strings data
261
+ """
262
+
263
+ class IStrings:
264
+ """
265
+ Class that represents a container for strings
266
+ """
267
+
268
+ pass
269
+
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
+ """
347
+
348
+ def __init__(self) -> None:
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)
401
+
402
+ @property
403
+ def values(self) -> dict | None:
404
+ """
405
+ :return: Values stored in key in the Windows Registry
406
+ """
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
474
+
475
+ class LoggerService(logging.Logger):
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:
487
+ super().__init__(name, level)
488
+
489
+ stream_handler = logging.StreamHandler(sys.stdout)
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
+ )
497
+ self.addHandler(stream_handler)
498
+
499
+ if file_handling:
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
+ )
513
+ self.addHandler(file_handler)
514
+
515
+ def log_exception(self, e: Exception) -> None:
516
+ """
517
+ Logs an exception with detailed traceback
518
+ """
519
+
520
+ self.error(msg=e, exc_info=True)
521
+
522
+ # endregion
@@ -0,0 +1,37 @@
1
+ from __future__ import annotations
2
+ import typing
3
+ import waitress, flask
4
+
5
+
6
+ class ILocalhostFlask(flask.Flask):
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
+ """
18
+
19
+ def __init__(self, import_name: str) -> None:
20
+ super().__init__(import_name)
21
+
22
+ for rule, view_func in self._RULES.items():
23
+ self.add_url_rule(
24
+ rule=rule,
25
+ view_func=view_func,
26
+ )
27
+
28
+ def serve(self, port: int) -> None:
29
+ """
30
+ Starts this Flask application
31
+ """
32
+
33
+ waitress.serve(
34
+ app=self,
35
+ host="127.0.0.1",
36
+ port=port,
37
+ )
@@ -0,0 +1,138 @@
1
+ from __future__ import annotations
2
+
3
+
4
+ class IContainer:
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
+
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
+
27
+ _OBJECTS: dict[str, type] = None
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
+ """
39
+
40
+ def __init__(self, json_data: dict) -> None:
41
+ self._data = json_data
42
+
43
+ if isinstance(self._ATTRIBUTES, set):
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
+
55
+ if isinstance(self._DATA, dict):
56
+ for name, data_class in self._DATA.items():
57
+ try:
58
+ setattr(self, name, [data_class(data) for data in self._data])
59
+ except:
60
+ setattr(self, name, None)
61
+ elif isinstance(self._OBJECTS, dict):
62
+ for name, data_class in self._OBJECTS.items():
63
+ try:
64
+ setattr(self, name, [data_class(data) for data in self._data.get(name)])
65
+ except:
66
+ setattr(self, name, None)
67
+
68
+
69
+ class IModel:
70
+ """
71
+ Class for storing parameters and models
72
+ """
73
+
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
+
84
+ _OBJECTS: dict[str, type] = None
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
+ """
96
+
97
+ def __init__(self, json_data: dict | list[dict]) -> None:
98
+ self._data = json_data
99
+
100
+ if isinstance(self._ATTRIBUTES, set):
101
+ for name in self._ATTRIBUTES:
102
+ setattr(self, name, self._data.get(name, None))
103
+ elif isinstance(self._ATTRIBUTES, dict):
104
+ for key, attributes in self._ATTRIBUTES.items():
105
+ if isinstance(attributes, set):
106
+ for name in attributes:
107
+ try:
108
+ setattr(self, name, self._data.get(key).get(name, None))
109
+ except:
110
+ setattr(self, name, None)
111
+
112
+ if isinstance(self._OBJECTS, dict):
113
+ for name, data_class in self._OBJECTS.items():
114
+ try:
115
+ setattr(self, name, data_class(self._data.get(name)))
116
+ except:
117
+ setattr(self, name, None)
118
+
119
+
120
+ class IValues:
121
+ """
122
+ Class for storing various parameters and values
123
+ """
124
+
125
+ _ATTRIBUTES: set[str] = None
126
+ """
127
+ Attributes that can be stored in this class
128
+
129
+ Example:
130
+ _ATTRIBUTES = {"settings", "path"}
131
+ """
132
+
133
+ def __init__(self, **kwargs) -> None:
134
+ for name in self._ATTRIBUTES:
135
+ setattr(self, name, kwargs.get(name, None))
136
+
137
+ def update(self, **kwargs) -> None:
138
+ self.__init__(**kwargs)
@@ -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.2.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.8.3
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.2
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
 
@@ -1,5 +1,5 @@
1
1
  blinker==1.9.0
2
- certifi==2025.8.3
2
+ certifi==2025.10.5
3
3
  charset-normalizer==3.4.3
4
4
  click==8.3.0
5
5
  colorama==0.4.6
@@ -7,7 +7,7 @@ Flask==3.1.2
7
7
  idna==3.10
8
8
  itsdangerous==2.2.0
9
9
  Jinja2==3.1.6
10
- MarkupSafe==3.0.2
10
+ MarkupSafe==3.0.3
11
11
  pillow==11.3.0
12
12
  requests==2.32.5
13
13
  urllib3==2.5.0
@@ -1,194 +0,0 @@
1
- from __future__ import annotations
2
- import configparser, datetime, logging, winreg, json, sys, io, os
3
- import requests, PIL.Image, PIL.ImageDraw
4
- from . import utils
5
-
6
-
7
- # Providers
8
- class IDataProvider:
9
- _PATH: str
10
- _DATA_VALUES: dict[str, type]
11
-
12
- def __init__(self) -> None:
13
- for k, v in self._DATA_VALUES.items():
14
- try:
15
- with open(self._PATH.format(k), "rb") as file:
16
- setattr(self, k, v(json_data=json.loads(file.read())))
17
- except:
18
- setattr(self, k, None)
19
-
20
-
21
- class IConfigProvider:
22
- class IConfig:
23
- _SECTION: str = None
24
- _CONFIG_VALUES: dict[str, type]
25
-
26
- def __init__(self, parent: IConfigProvider = None) -> None:
27
- if isinstance(parent, IConfigProvider):
28
- self._CONFIG_VALUES = parent._CONFIG_VALUES.get(self._SECTION)
29
- self._incorrect_content_exception = configparser.ParsingError("config.ini is filled incorrectly!")
30
- self._config = configparser.ConfigParser()
31
- self._config.read(utils.get_path("config.ini"))
32
- if not self._config.has_section(self._SECTION):
33
- self._config.add_section(self._SECTION)
34
- for k, v in self._CONFIG_VALUES.items():
35
- try:
36
- setattr(self, k, self._config.get(self._SECTION, k))
37
- except:
38
- self._config.set(self._SECTION, k, v.__name__)
39
- with open(utils.get_path("config.ini"), "w", encoding="utf-8") as file:
40
- self._config.write(fp=file)
41
- for k, v in self._CONFIG_VALUES.items():
42
- try:
43
- if v == int:
44
- setattr(self, k, int(getattr(self, k)))
45
- elif v == bool:
46
- if getattr(self, k) not in (str(True), str(False)):
47
- setattr(self, k, None)
48
- raise self._incorrect_content_exception
49
- else:
50
- setattr(self, k, getattr(self, k) == str(True))
51
- elif v in (dict, list):
52
- setattr(self, k, json.loads(getattr(self, k)))
53
- except:
54
- setattr(self, k, None)
55
- raise self._incorrect_content_exception
56
- if not self.values:
57
- raise self._incorrect_content_exception
58
-
59
- @property
60
- def values(self) -> dict | None:
61
- try:
62
- return {i: getattr(self, i) for i in self._CONFIG_VALUES}
63
- except:
64
- return None
65
-
66
- _CONFIG_VALUES: dict[str, dict[str, type]]
67
- _CONFIG_OBJECTS: dict[str, type]
68
-
69
- def __init__(self) -> None:
70
- for k, v in self._CONFIG_OBJECTS.items():
71
- setattr(self, k, v(self))
72
-
73
-
74
- class IAssetsProvider:
75
- class IDirectory:
76
- _PATH: str = None
77
- _NAMES: set[str]
78
-
79
- def __init__(self, parent: IAssetsProvider) -> None:
80
- for i in self._NAMES:
81
- setattr(self, i, parent.file_image(parent._PATH.format(self._PATH.format(i))))
82
-
83
- class INetwork:
84
- _URLS: dict[str, str]
85
-
86
- def __init__(self, parent: IAssetsProvider) -> None:
87
- for k, v in self._URLS:
88
- setattr(self, k, parent.network_image(v))
89
-
90
- _PATH: str
91
- _ASSETS_OBJECTS: dict[str, type]
92
-
93
- def __init__(self) -> None:
94
- for k, v in self._ASSETS_OBJECTS.items():
95
- setattr(self, k, v(self))
96
-
97
- @staticmethod
98
- def file_image(path: str) -> PIL.Image.Image:
99
- with open(path, "rb") as file:
100
- return PIL.Image.open(io.BytesIO(file.read()))
101
-
102
- @staticmethod
103
- def network_image(url: str) -> PIL.Image.Image:
104
- return PIL.Image.open(io.BytesIO(requests.get(url).content))
105
-
106
- @staticmethod
107
- def round_corners(image: PIL.Image.Image, radius: int) -> PIL.Image.Image:
108
- if image.mode != "RGB":
109
- image = image.convert("RGB")
110
- width, height = image.size
111
- shape = PIL.Image.new("L", (radius * 2, radius * 2), 0)
112
- alpha = PIL.Image.new("L", image.size, "white")
113
- PIL.ImageDraw.Draw(shape).ellipse((0, 0, radius * 2, radius * 2), fill=255)
114
- alpha.paste(shape.crop((0, 0, radius, radius)), (0, 0))
115
- alpha.paste(shape.crop((0, radius, radius, radius * 2)), (0, height - radius))
116
- alpha.paste(shape.crop((radius, 0, radius * 2, radius)), (width - radius, 0))
117
- alpha.paste(shape.crop((radius, radius, radius * 2, radius * 2)), (width - radius, height - radius))
118
- image.putalpha(alpha)
119
- return image
120
-
121
-
122
- class IStringsProvider:
123
- class IStrings:
124
- pass
125
-
126
- _STRINGS_OBJECTS: dict[str, type]
127
-
128
- def __init__(self) -> None:
129
- for k, v in self._STRINGS_OBJECTS.items():
130
- setattr(self, k, v())
131
-
132
-
133
- # Managers
134
- class IRegistryManager:
135
- class IRegistry:
136
- _NAME: str = None
137
- _REGISTRY_VALUES: dict[str, int]
138
-
139
- def __init__(self, parent: IRegistryManager = None) -> None:
140
- if isinstance(parent, IRegistryManager):
141
- self._REGISTRY_VALUES = parent._REGISTRY_VALUES.get(self._NAME)
142
- self._path = winreg.CreateKey(parent._path, self._NAME)
143
- for i in self._REGISTRY_VALUES.keys():
144
- try:
145
- setattr(self, i, winreg.QueryValueEx(self._path, i)[int()])
146
- except:
147
- setattr(self, i, None)
148
-
149
- @property
150
- def values(self) -> dict | None:
151
- try:
152
- return {i: getattr(self, i) for i in self._REGISTRY_VALUES}
153
- except:
154
- return None
155
-
156
- def refresh(self) -> IRegistryManager.IRegistry:
157
- self.__init__()
158
- return self
159
-
160
- def update(self, **kwargs) -> None:
161
- for k, v in kwargs.items():
162
- winreg.SetValueEx(self._path, k, None, self._REGISTRY_VALUES.get(k), v)
163
- setattr(self, k, v)
164
-
165
- _KEY: str
166
- _REGISTRY_VALUES: dict[str, dict[str, int]]
167
- _REGISTRY_OBJECTS: dict[str, type]
168
- _path: winreg.HKEYType
169
-
170
- def __init__(self) -> None:
171
- self._path = winreg.CreateKey(winreg.HKEY_CURRENT_USER, self._KEY)
172
- for k, v in self._REGISTRY_OBJECTS.items():
173
- setattr(self, k, v(self))
174
-
175
- def refresh(self) -> IRegistryManager:
176
- self.__init__()
177
- return self
178
-
179
-
180
- # Services
181
- class LoggerService(logging.Logger):
182
- def __init__(self, name: str, file_handling: bool = True, filename: str = datetime.datetime.now().strftime("%d-%m-%y-%H-%M-%S"), level: int = logging.NOTSET, folder_name: str = "logs") -> None:
183
- super().__init__(name, level)
184
- stream_handler = logging.StreamHandler(sys.stdout)
185
- stream_handler.setFormatter(logging.Formatter(fmt="$levelname $asctime $name - $message", datefmt="%d-%m-%y %H:%M:%S", style="$"))
186
- self.addHandler(stream_handler)
187
- if file_handling:
188
- os.makedirs(utils.get_path(folder_name, only_abspath=True), exist_ok=True)
189
- file_handler = logging.FileHandler(utils.get_path(f"{folder_name}/{filename}-{name}.log", only_abspath=True), encoding="utf-8")
190
- file_handler.setFormatter(logging.Formatter(fmt="$levelname $asctime - $message", datefmt="%d-%m-%y %H:%M:%S", style="$"))
191
- self.addHandler(file_handler)
192
-
193
- def log_exception(self, e: Exception) -> None:
194
- self.error(msg=e, exc_info=True)
@@ -1,19 +0,0 @@
1
- from __future__ import annotations
2
- import waitress, flask
3
-
4
-
5
- class ILocalhostFlask(flask.Flask):
6
- _RULES: dict[str, function]
7
-
8
- def __init__(self, import_name: str) -> None:
9
- super().__init__(import_name)
10
-
11
- for k, v in self._RULES.items():
12
- self.add_url_rule(rule=k, view_func=v)
13
-
14
- def serve(self, port: int) -> None:
15
- waitress.serve(
16
- app=self,
17
- host="127.0.0.1",
18
- port=port,
19
- )
@@ -1,63 +0,0 @@
1
- from __future__ import annotations
2
-
3
-
4
- class IContainer:
5
- _ATTRIBUTES: set[str] = None
6
- _DATA: dict[str, type] = None
7
- _OBJECTS: dict[str, type] = None
8
- data: dict
9
-
10
- def __init__(self, json_data: dict) -> None:
11
- setattr(self, "data", json_data)
12
- if isinstance(self._ATTRIBUTES, set):
13
- for i in self._ATTRIBUTES:
14
- setattr(self, i, self.data.get(i, None))
15
- if isinstance(self._DATA, dict):
16
- for k, v in self._DATA.items():
17
- try:
18
- setattr(self, k, [v(i) for i in self.data])
19
- except:
20
- setattr(self, k, None)
21
- elif isinstance(self._OBJECTS, dict):
22
- for k, v in self._OBJECTS.items():
23
- try:
24
- setattr(self, k, [v(i) for i in self.data.get(k)])
25
- except:
26
- setattr(self, k, None)
27
-
28
-
29
- class IModel:
30
- _ATTRIBUTES: set[str] | dict[str, set[str]] = None
31
- _OBJECTS: dict[str, type] = None
32
- data: dict | list[dict]
33
-
34
- def __init__(self, json_data: dict | list[dict]) -> None:
35
- setattr(self, "data", json_data)
36
- if isinstance(self._ATTRIBUTES, set):
37
- for i in self._ATTRIBUTES:
38
- setattr(self, i, self.data.get(i, None))
39
- elif isinstance(self._ATTRIBUTES, dict):
40
- for k, v in self._ATTRIBUTES.items():
41
- if isinstance(v, set):
42
- for i in v:
43
- try:
44
- setattr(self, i, self.data.get(k).get(i, None))
45
- except:
46
- setattr(self, i, None)
47
- if isinstance(self._OBJECTS, dict):
48
- for k, v in self._OBJECTS.items():
49
- try:
50
- setattr(self, k, v(self.data.get(k)))
51
- except:
52
- setattr(self, k, None)
53
-
54
-
55
- class IValues:
56
- _ATTRIBUTES: set[str] = None
57
-
58
- def __init__(self, **kwargs) -> None:
59
- for i in self._ATTRIBUTES:
60
- setattr(self, i, kwargs.get(i, None))
61
-
62
- def update(self, **kwargs) -> None:
63
- self.__init__(**kwargs)
File without changes
File without changes
File without changes