pyquoks 2.4.0__py3-none-any.whl → 2.5.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.
@@ -0,0 +1,16 @@
1
+ pyquoks/__init__.py,sha256=RDWFmPLDon9bhs1iKr7j1lCqj5gNSbf7Ly5KPu-9Spo,178
2
+ pyquoks/managers/__init__.py,sha256=J0EplmBJ9bURlIGCrfk1FaO_qGsUvEJGtl4l6NMhGks,129
3
+ pyquoks/managers/config.py,sha256=Z6sZYscZI53k2SUuz0MKXTN0tSoYz3pXZuW2gJq51ns,4972
4
+ pyquoks/managers/data.py,sha256=tHrnGIii4VZxDMjFlb83GfvJjjixtSCcZ-CnkhAxskY,2989
5
+ pyquoks/managers/database.py,sha256=eHtXKVHZF08q8JqwCwmKZVYbXItxW24bPF3kgiJY8TU,2335
6
+ pyquoks/providers/__init__.py,sha256=7L6ts-7E5Nl9uucP7Swp8o3_iUD-RdAl-b06qV19xnc,141
7
+ pyquoks/providers/assets.py,sha256=NuPfVSZTcxD1mQ-WV2f7UjKGUhDyiu3uN2SAkiZWxxg,3415
8
+ pyquoks/providers/environment.py,sha256=6vz0_Qx7cx4Hh9BP-38L9BNcV5EHJlBySB8UbNLV7WQ,421
9
+ pyquoks/providers/strings.py,sha256=fQ7vnLK8_UJZH5HSgwGZO9iGcv94lv3KVoz20XWu9kU,692
10
+ pyquoks/services/__init__.py,sha256=BEQ36U4rpwnZ6-HH89USDEGgjgEvSuA3zLBfz1M0xqE,55
11
+ pyquoks/services/logger.py,sha256=Pqgi4ysvQUHZL6gOCM2eRAdwwP1rS_Y56CpJ_1nvuFc,2342
12
+ pyquoks/utils.py,sha256=TjMvSgWxKR1Sb05Dq754L4fXtwIDm09GnykicfjFeW8,1904
13
+ pyquoks-2.5.0.dist-info/licenses/LICENSE,sha256=WYK66zwaBlCe_GnOwRCLlJCQibJ8oyqvpZ1CCsrcg2k,1095
14
+ pyquoks-2.5.0.dist-info/METADATA,sha256=J7ctTgpxFyoUeba3kJS0rSn-CzULPFYRHMU0T_h-k_s,1256
15
+ pyquoks-2.5.0.dist-info/WHEEL,sha256=kJCRJT_g0adfAJzTx2GUMmS80rTJIVHRCfG0DQgLq3o,88
16
+ pyquoks-2.5.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 2.2.1
2
+ Generator: poetry-core 2.3.1
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
pyquoks/data.py DELETED
@@ -1,640 +0,0 @@
1
- import configparser
2
- import datetime
3
- import io
4
- import json
5
- import logging
6
- import os
7
- import sqlite3
8
- import sys
9
- import typing
10
-
11
- import PIL.Image
12
- import pydantic
13
- import requests
14
-
15
- from . import utils
16
-
17
-
18
- # region Providers
19
-
20
- class AssetsProvider(utils._HasRequiredAttributes):
21
- """
22
- Class for providing various assets data
23
-
24
- **Required attributes**::
25
-
26
- # Predefined:
27
-
28
- _PATH = pyquoks.utils.get_path("assets/")
29
-
30
- Attributes:
31
- _PATH: Path to the directory with assets folders
32
- """
33
-
34
- class Directory(utils._HasRequiredAttributes):
35
- """
36
- Class that represents a directory with various assets
37
-
38
- **Required attributes**::
39
-
40
- _ATTRIBUTES = {"picture1", "picture2"}
41
-
42
- _PATH = "images/"
43
-
44
- _FILENAME = "{0}.png"
45
-
46
- Attributes:
47
- _ATTRIBUTES: Names of files in the directory
48
- _PATH: Path to the directory with assets files
49
- _FILENAME: Filename of assets files
50
- _parent: Parent object
51
- """
52
-
53
- _REQUIRED_ATTRIBUTES = {
54
- "_ATTRIBUTES",
55
- "_PATH",
56
- "_FILENAME",
57
- }
58
-
59
- _ATTRIBUTES: set[str]
60
-
61
- _PATH: str
62
-
63
- _FILENAME: str
64
-
65
- _parent: AssetsProvider | None
66
-
67
- def __init__(self, parent: AssetsProvider = None) -> None:
68
- self._check_attributes()
69
-
70
- if parent:
71
- self._parent = parent
72
- elif not hasattr(self, "_parent") or not self._parent:
73
- raise AttributeError("This class cannot be initialized without a parent object!")
74
-
75
- self._PATH = self._parent._PATH + self._PATH
76
-
77
- for attribute in self._ATTRIBUTES:
78
- try:
79
- setattr(self, attribute, self._parent.file_image(
80
- path=self._PATH + self._FILENAME.format(attribute),
81
- ))
82
- except Exception:
83
- setattr(self, attribute, None)
84
-
85
- class Network(utils._HasRequiredAttributes):
86
- """
87
- Class that represents a set of images obtained from a network
88
-
89
- **Required attributes**::
90
-
91
- _URLS = {"example": "https://example.com/image.png"}
92
-
93
- Attributes:
94
- _URLS: Dictionary with names of attributes and URLs
95
- _parent: Parent object
96
- """
97
-
98
- _REQUIRED_ATTRIBUTES = {
99
- "_URLS",
100
- }
101
-
102
- _URLS: dict[str, str]
103
-
104
- _parent: AssetsProvider | None
105
-
106
- def __init__(self, parent: AssetsProvider = None) -> None:
107
- self._check_attributes()
108
-
109
- if parent:
110
- self._parent = parent
111
- elif not hasattr(self, "_parent") or not self._parent:
112
- raise AttributeError("This class cannot be initialized without a parent object!")
113
-
114
- for attribute, url in self._URLS.items():
115
- try:
116
- setattr(self, attribute, self._parent.network_image(
117
- url=url,
118
- ))
119
- except Exception:
120
- setattr(self, attribute, None)
121
-
122
- _REQUIRED_ATTRIBUTES = {
123
- "_PATH",
124
- }
125
-
126
- _PATH: str = utils.get_path("assets/")
127
-
128
- def __init__(self) -> None:
129
- self._check_attributes()
130
-
131
- for attribute, child_class in self.__class__.__annotations__.items():
132
- if issubclass(child_class, AssetsProvider.Directory | AssetsProvider.Network):
133
- setattr(self, attribute, child_class(self))
134
- else:
135
- raise AttributeError(
136
- f"{attribute} has incorrect type! (must be subclass of {AssetsProvider.Directory.__name__} or {AssetsProvider.Network.__name__})",
137
- )
138
-
139
- @staticmethod
140
- def file_image(path: str) -> PIL.Image.Image:
141
- """
142
- :param path: Absolute path of the image file
143
- :return: Image object from a file
144
- """
145
-
146
- with open(path, "rb") as file:
147
- return PIL.Image.open(
148
- fp=io.BytesIO(file.read()),
149
- )
150
-
151
- @staticmethod
152
- def network_image(url: str) -> PIL.Image.Image:
153
- """
154
- :param url: URL of the image file
155
- :return: Image object from a URL
156
- """
157
-
158
- return PIL.Image.open(
159
- fp=io.BytesIO(requests.get(url).content),
160
- )
161
-
162
-
163
- class EnvironmentProvider:
164
- """
165
- Class for providing environment variables
166
- """
167
-
168
- def __init__(self) -> None:
169
- self.load_variables()
170
-
171
- def load_variables(self) -> None:
172
- """
173
- Loads specified environment variables
174
- """
175
-
176
- for attribute in self.__class__.__annotations__.keys():
177
- setattr(self, attribute, os.getenv(attribute, None))
178
-
179
-
180
- class StringsProvider:
181
- """
182
- Class for providing various strings data
183
- """
184
-
185
- class Strings:
186
- """
187
- Class that represents a container for strings
188
- """
189
-
190
- # noinspection PyUnusedLocal
191
- def __init__(self, parent: StringsProvider) -> None:
192
- ... # TODO
193
-
194
- def __init__(self) -> None:
195
- for attribute, child_class in self.__class__.__annotations__.items():
196
- if issubclass(child_class, StringsProvider.Strings):
197
- setattr(self, attribute, child_class(self))
198
- else:
199
- raise AttributeError(
200
- f"{attribute} has incorrect type! (must be subclass of {StringsProvider.Strings.__name__})",
201
- )
202
-
203
-
204
- # endregion
205
-
206
- # region Managers
207
-
208
- class ConfigManager(utils._HasRequiredAttributes):
209
- """
210
- Class for managing data in configuration file
211
-
212
- **Required attributes**::
213
-
214
- # Predefined
215
-
216
- _PATH = pyquoks.utils.get_path("config.ini")
217
-
218
- Attributes:
219
- _PATH: Path to the configuration file
220
- """
221
-
222
- class Config(utils._HasRequiredAttributes):
223
- """
224
- Class that represents a section in configuration file
225
-
226
- **Required attributes**::
227
-
228
- _SECTION = "Settings"
229
-
230
- _VALUES = {"version": str, "beta": bool}
231
-
232
- Attributes:
233
- _SECTION: Name of the section in configuration file
234
- _VALUES: Dictionary with settings and their types
235
- _parent: Parent object
236
- """
237
-
238
- _REQUIRED_ATTRIBUTES = {
239
- "_SECTION",
240
- "_VALUES",
241
- }
242
-
243
- _SECTION: str
244
-
245
- _VALUES: dict[str, type]
246
-
247
- _incorrect_content_exception = configparser.ParsingError(
248
- source="configuration file is filled incorrectly",
249
- )
250
-
251
- _parent: ConfigManager
252
-
253
- def __init__(self, parent: ConfigManager = None) -> None:
254
- self._check_attributes()
255
-
256
- if parent:
257
- self._parent = parent
258
- elif not hasattr(self, "_parent") or not self._parent:
259
- raise AttributeError("This class cannot be initialized without a parent object!")
260
-
261
- self._config = configparser.ConfigParser()
262
- self._config.read(self._parent._PATH)
263
-
264
- if not self._config.has_section(self._SECTION):
265
- self._config.add_section(self._SECTION)
266
-
267
- for attribute, object_type in self._VALUES.items():
268
- try:
269
- setattr(self, attribute, self._config.get(self._SECTION, attribute))
270
- except Exception:
271
- self._config.set(self._SECTION, attribute, object_type.__name__)
272
- with open(self._parent._PATH, "w", encoding="utf-8") as file:
273
- self._config.write(file)
274
-
275
- for attribute, object_type in self._VALUES.items():
276
- try:
277
- match object_type():
278
- case bool():
279
- if getattr(self, attribute) not in [str(True), str(False)]:
280
- setattr(self, attribute, None)
281
- raise self._incorrect_content_exception
282
- else:
283
- setattr(self, attribute, getattr(self, attribute) == str(True))
284
- case int():
285
- setattr(self, attribute, int(getattr(self, attribute)))
286
- case float():
287
- setattr(self, attribute, float(getattr(self, attribute)))
288
- case str():
289
- pass
290
- case dict() | list():
291
- setattr(self, attribute, json.loads(getattr(self, attribute)))
292
- case _:
293
- raise ValueError(f"{object_type.__name__} type is not supported!")
294
- except Exception:
295
- setattr(self, attribute, None)
296
-
297
- raise self._incorrect_content_exception
298
-
299
- @property
300
- def _values(self) -> dict | None:
301
- """
302
- :return: Values stored in this section
303
- """
304
-
305
- try:
306
- return {
307
- attribute: getattr(self, attribute) for attribute in self._VALUES.keys()
308
- }
309
- except Exception:
310
- return None
311
-
312
- def update(self, **kwargs) -> None:
313
- """
314
- Updates provided attributes in object
315
- """
316
-
317
- for attribute, value in kwargs.items():
318
-
319
- if attribute not in self._VALUES.keys():
320
- raise AttributeError(f"{attribute} is not specified!")
321
-
322
- object_type = self._VALUES.get(attribute)
323
-
324
- if not isinstance(
325
- value,
326
- typing.get_origin(object_type) if typing.get_origin(object_type) else object_type,
327
- ):
328
- raise AttributeError(
329
- f"{attribute} has incorrect type! (must be {object_type.__name__})",
330
- )
331
-
332
- setattr(self, attribute, value)
333
-
334
- match object_type():
335
- case bool() | int() | float() | str():
336
- self._config.set(self._SECTION, attribute, str(value))
337
- case dict() | list():
338
- self._config.set(self._SECTION, attribute, json.dumps(value))
339
- case _:
340
- raise ValueError(f"{object_type.__name__} type is not supported!")
341
-
342
- with open(self._parent._PATH, "w", encoding="utf-8") as file:
343
- self._config.write(file)
344
-
345
- _REQUIRED_ATTRIBUTES = {
346
- "_PATH",
347
- }
348
-
349
- _PATH: str = utils.get_path("config.ini")
350
-
351
- def __init__(self) -> None:
352
- self._check_attributes()
353
-
354
- for attribute, child_class in self.__class__.__annotations__.items():
355
- if issubclass(child_class, ConfigManager.Config):
356
- setattr(self, attribute, child_class(self))
357
- else:
358
- raise AttributeError(
359
- f"{attribute} has incorrect type! (must be subclass of {ConfigManager.Config.__name__})",
360
- )
361
-
362
-
363
- class DataManager(utils._HasRequiredAttributes):
364
- """
365
- Class for managing data from JSON-like files
366
-
367
- **Required attributes**::
368
-
369
- # Predefined:
370
-
371
- _PATH = pyquoks.utils.get_path("data/")
372
-
373
- _FILENAME = "{0}.json"
374
-
375
- Attributes:
376
- _PATH: Path to the directory with JSON-like files
377
- _FILENAME: Filename of JSON-like files
378
- """
379
-
380
- _REQUIRED_ATTRIBUTES = {
381
- "_PATH",
382
- "_FILENAME",
383
- }
384
-
385
- _PATH: str = utils.get_path("data/")
386
-
387
- _FILENAME: str = "{0}.json"
388
-
389
- def __init__(self) -> None:
390
- self._check_attributes()
391
-
392
- for attribute, object_type in self.__class__.__annotations__.items():
393
- if issubclass(
394
- typing.get_args(object_type)[0],
395
- pydantic.BaseModel,
396
- ) if typing.get_origin(object_type) else issubclass(
397
- object_type,
398
- pydantic.BaseModel,
399
- ):
400
- try:
401
- with open(self._PATH + self._FILENAME.format(attribute), "rb") as file:
402
- data = json.loads(file.read())
403
-
404
- if typing.get_origin(object_type) == list:
405
- setattr(self, attribute, [typing.get_args(object_type)[0](**model) for model in data])
406
- else:
407
- setattr(self, attribute, object_type(**data))
408
- except Exception:
409
- setattr(self, attribute, None)
410
- else:
411
- raise AttributeError(
412
- f"{attribute} has incorrect type! (must be subclass of {pydantic.BaseModel.__name__} or {list.__name__} of its subclasses)",
413
- )
414
-
415
- def update(self, **kwargs) -> None:
416
- """
417
- Updates provided attributes in object
418
- """
419
-
420
- for attribute, value in kwargs.items():
421
- value: pydantic.BaseModel | list[pydantic.BaseModel]
422
-
423
- if attribute not in self.__class__.__annotations__.keys():
424
- raise AttributeError(f"{attribute} is not specified!")
425
-
426
- object_type = self.__class__.__annotations__.get(attribute)
427
-
428
- if not isinstance(
429
- value,
430
- typing.get_origin(object_type) if typing.get_origin(object_type) else object_type,
431
- ):
432
- raise AttributeError(
433
- f"{attribute} has incorrect type! (must be {object_type.__name__})",
434
- )
435
-
436
- setattr(self, attribute, value)
437
-
438
- os.makedirs(
439
- name=self._PATH,
440
- exist_ok=True,
441
- )
442
-
443
- with open(self._PATH + self._FILENAME.format(attribute), "w", encoding="utf-8") as file:
444
- json.dump(
445
- [model.model_dump() for model in value] if typing.get_origin(
446
- object_type,
447
- ) == list else value.model_dump(),
448
- fp=file,
449
- ensure_ascii=False,
450
- indent=2,
451
- )
452
-
453
-
454
- class DatabaseManager(utils._HasRequiredAttributes):
455
- """
456
- Class for managing database connections
457
-
458
- **Required attributes**::
459
-
460
- # Predefined
461
-
462
- _PATH = pyquoks.utils.get_path("db/")
463
-
464
- Attributes:
465
- _PATH: Path to the directory with databases
466
- """
467
-
468
- class Database(sqlite3.Connection, utils._HasRequiredAttributes):
469
- """
470
- Class that represents a database connection
471
-
472
- **Required attributes**::
473
-
474
- _NAME = "users"
475
-
476
- _SQL = f\"""CREATE TABLE IF NOT EXISTS {_NAME} (user_id INTEGER PRIMARY KEY NOT NULL)\"""
477
-
478
- # Predefined
479
-
480
- _FILENAME = "{0}.db"
481
-
482
- Attributes:
483
- _NAME: Name of the database
484
- _SQL: SQL expression for creating a table
485
- _FILENAME: Filename of the database
486
- _parent: Parent object
487
- """
488
-
489
- _REQUIRED_ATTRIBUTES = {
490
- "_NAME",
491
- "_SQL",
492
- "_FILENAME",
493
- }
494
-
495
- _NAME: str
496
-
497
- _SQL: str
498
-
499
- _FILENAME: str = "{0}.db"
500
-
501
- _parent: DatabaseManager
502
-
503
- def __init__(self, parent: DatabaseManager = None) -> None:
504
- self._check_attributes()
505
-
506
- if parent:
507
- self._parent = parent
508
- elif not hasattr(self, "_parent") or not self._parent:
509
- raise AttributeError("This class cannot be initialized without a parent object!")
510
-
511
- self._FILENAME = self._FILENAME.format(self._NAME)
512
-
513
- super().__init__(
514
- database=self._parent._PATH + self._FILENAME,
515
- check_same_thread=False,
516
- )
517
- self.row_factory = sqlite3.Row
518
-
519
- cursor = self.cursor()
520
-
521
- cursor.execute(
522
- self._SQL,
523
- )
524
-
525
- self.commit()
526
-
527
- _REQUIRED_ATTRIBUTES = {
528
- "_PATH",
529
- }
530
-
531
- _PATH: str = utils.get_path("db/")
532
-
533
- def __init__(self) -> None:
534
- self._check_attributes()
535
-
536
- os.makedirs(
537
- name=self._PATH,
538
- exist_ok=True,
539
- )
540
-
541
- for attribute, child_class in self.__class__.__annotations__.items():
542
- if issubclass(child_class, DatabaseManager.Database):
543
- setattr(self, attribute, child_class(self))
544
- else:
545
- raise AttributeError(
546
- f"{attribute} has incorrect type! (must be subclass of {DatabaseManager.Database.__name__})",
547
- )
548
-
549
- def close_all(self) -> None:
550
- """
551
- Closes all database connections
552
- """
553
-
554
- for attribute in self.__class__.__annotations__.keys():
555
- getattr(self, attribute).close()
556
-
557
-
558
- # endregion
559
-
560
- # region Services
561
-
562
- class LoggerService(logging.Logger):
563
- """
564
- Class that provides methods for parallel logging
565
-
566
- Attributes:
567
- _LOG_PATH: Path to the logs file
568
- """
569
-
570
- _LOG_PATH: str | None
571
-
572
- def __init__(
573
- self,
574
- filename: str,
575
- level: int = logging.NOTSET,
576
- file_handling: bool = True,
577
- path: str = utils.get_path("logs/"),
578
- ) -> None:
579
- super().__init__(filename, level)
580
-
581
- self.stream_handler = logging.StreamHandler(sys.stdout)
582
- self.stream_handler.setFormatter(
583
- logging.Formatter(
584
- fmt="$levelname $asctime $name - $message",
585
- datefmt="%d-%m-%y %H:%M:%S",
586
- style="$",
587
- )
588
- )
589
- self.addHandler(self.stream_handler)
590
-
591
- if file_handling:
592
- os.makedirs(
593
- name=path,
594
- exist_ok=True
595
- )
596
- self._LOG_PATH = path + f"{int(datetime.datetime.now().timestamp())}.{filename}.log"
597
-
598
- self.file_handler = logging.FileHandler(
599
- filename=self._LOG_PATH,
600
- encoding="utf-8",
601
- )
602
- self.file_handler.setFormatter(
603
- logging.Formatter(
604
- fmt="$levelname $asctime - $message",
605
- datefmt="%d-%m-%y %H:%M:%S",
606
- style="$",
607
- ),
608
- )
609
- self.addHandler(self.file_handler)
610
- else:
611
- self._LOG_PATH = None
612
-
613
- @property
614
- def file(self) -> typing.IO | None:
615
- """
616
- :return: Opened file-like object of current logs
617
- """
618
-
619
- if self._LOG_PATH:
620
- return open(self._LOG_PATH, "rb")
621
- else:
622
- return None
623
-
624
- def log_error(self, exception: Exception, raise_again: bool = False) -> None:
625
- """
626
- Logs an exception with detailed traceback
627
-
628
- :param exception: Exception to be logged
629
- :param raise_again: Whether or not exception should be raised again
630
- """
631
-
632
- self.error(
633
- msg=exception,
634
- exc_info=True,
635
- )
636
-
637
- if raise_again:
638
- raise exception
639
-
640
- # endregion