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.
pyquoks/__init__.py CHANGED
@@ -1,9 +1,11 @@
1
1
  __all__ = [
2
- "data",
2
+ "managers",
3
+ "providers",
4
+ "services",
3
5
  "utils",
4
- "test",
5
6
  ]
6
7
 
7
- from . import data
8
+ from . import managers
9
+ from . import providers
10
+ from . import services
8
11
  from . import utils
9
- from . import test
@@ -0,0 +1,9 @@
1
+ __all__ = [
2
+ "config",
3
+ "data",
4
+ "database",
5
+ ]
6
+
7
+ from . import config
8
+ from . import data
9
+ from . import database
@@ -0,0 +1,154 @@
1
+ import configparser
2
+ import json
3
+ import typing
4
+
5
+ import pyquoks.utils
6
+
7
+
8
+ class ConfigManager(pyquoks.utils._HasRequiredAttributes):
9
+ """
10
+ Class for managing data in configuration file
11
+
12
+ **Required attributes**::
13
+
14
+ # Predefined
15
+
16
+ _PATH = pyquoks.utils.get_path("config.ini")
17
+
18
+ Attributes:
19
+ _PATH: Path to the configuration file
20
+ """
21
+
22
+ _REQUIRED_ATTRIBUTES = {
23
+ "_PATH",
24
+ }
25
+
26
+ _PATH: str = pyquoks.utils.get_path("config.ini")
27
+
28
+ def __init__(self) -> None:
29
+ self._check_attributes()
30
+
31
+ for attribute, object_type in self.__class__.__annotations__.items():
32
+ if issubclass(object_type, Config):
33
+ setattr(self, attribute, object_type(self))
34
+
35
+
36
+ class Config(pyquoks.utils._HasRequiredAttributes):
37
+ """
38
+ Class that represents a section in configuration file
39
+
40
+ **Required attributes**::
41
+
42
+ _SECTION = "Settings"
43
+
44
+ _VALUES = {"version": str, "beta": bool}
45
+
46
+ Attributes:
47
+ _SECTION: Name of the section in configuration file
48
+ _VALUES: Dictionary with settings and their types
49
+ _parent: Parent object
50
+ """
51
+
52
+ _REQUIRED_ATTRIBUTES = {
53
+ "_SECTION",
54
+ "_VALUES",
55
+ }
56
+
57
+ _SECTION: str
58
+
59
+ _VALUES: dict[str, type]
60
+
61
+ _incorrect_content_exception = configparser.ParsingError(
62
+ source="configuration file is filled incorrectly",
63
+ )
64
+
65
+ _parent: ConfigManager
66
+
67
+ def __init__(self, parent: ConfigManager) -> None:
68
+ self._check_attributes()
69
+
70
+ self._parent = parent
71
+
72
+ self._config = configparser.ConfigParser()
73
+ self._config.read(self._parent._PATH)
74
+
75
+ if not self._config.has_section(self._SECTION):
76
+ self._config.add_section(self._SECTION)
77
+
78
+ for attribute, object_type in self._VALUES.items():
79
+ try:
80
+ setattr(self, attribute, self._config.get(self._SECTION, attribute))
81
+ except Exception:
82
+ self._config.set(self._SECTION, attribute, object_type.__name__)
83
+ with open(self._parent._PATH, "w", encoding="utf-8") as file:
84
+ self._config.write(file)
85
+
86
+ for attribute, object_type in self._VALUES.items():
87
+ try:
88
+ match object_type():
89
+ case bool():
90
+ if getattr(self, attribute) not in [str(True), str(False)]:
91
+ setattr(self, attribute, None)
92
+ raise self._incorrect_content_exception
93
+ else:
94
+ setattr(self, attribute, getattr(self, attribute) == str(True))
95
+ case int():
96
+ setattr(self, attribute, int(getattr(self, attribute)))
97
+ case float():
98
+ setattr(self, attribute, float(getattr(self, attribute)))
99
+ case str():
100
+ pass
101
+ case dict() | list():
102
+ setattr(self, attribute, json.loads(getattr(self, attribute)))
103
+ case _:
104
+ raise ValueError(f"{object_type.__name__} type is not supported!")
105
+ except Exception:
106
+ setattr(self, attribute, None)
107
+
108
+ raise self._incorrect_content_exception
109
+
110
+ @property
111
+ def _values(self) -> dict | None:
112
+ """
113
+ :return: Values stored in this section
114
+ """
115
+
116
+ try:
117
+ return {
118
+ attribute: getattr(self, attribute) for attribute in self._VALUES.keys()
119
+ }
120
+ except Exception:
121
+ return None
122
+
123
+ def update(self, **kwargs) -> None:
124
+ """
125
+ Updates provided attributes in object
126
+ """
127
+
128
+ for attribute, value in kwargs.items():
129
+
130
+ if attribute not in self._VALUES.keys():
131
+ raise AttributeError(f"{attribute} is not specified!")
132
+
133
+ object_type = self._VALUES.get(attribute)
134
+
135
+ if not isinstance(
136
+ value,
137
+ typing.get_origin(object_type) if typing.get_origin(object_type) else object_type,
138
+ ):
139
+ raise AttributeError(
140
+ f"{attribute} has incorrect type! (must be {object_type.__name__})",
141
+ )
142
+
143
+ setattr(self, attribute, value)
144
+
145
+ match object_type():
146
+ case bool() | int() | float() | str():
147
+ self._config.set(self._SECTION, attribute, str(value))
148
+ case dict() | list():
149
+ self._config.set(self._SECTION, attribute, json.dumps(value))
150
+ case _:
151
+ raise ValueError(f"{object_type.__name__} type is not supported!")
152
+
153
+ with open(self._parent._PATH, "w", encoding="utf-8") as file:
154
+ self._config.write(file)
@@ -0,0 +1,94 @@
1
+ import json
2
+ import os
3
+ import typing
4
+
5
+ import pydantic
6
+
7
+ import pyquoks.utils
8
+
9
+
10
+ class DataManager(pyquoks.utils._HasRequiredAttributes):
11
+ """
12
+ Class for managing data from JSON-like files
13
+
14
+ **Required attributes**::
15
+
16
+ # Predefined:
17
+
18
+ _PATH = pyquoks.utils.get_path("data/")
19
+
20
+ _FILENAME = "{0}.json"
21
+
22
+ Attributes:
23
+ _PATH: Path to the directory with JSON-like files
24
+ _FILENAME: Filename of JSON-like files
25
+ """
26
+
27
+ _REQUIRED_ATTRIBUTES = {
28
+ "_PATH",
29
+ "_FILENAME",
30
+ }
31
+
32
+ _PATH: str = pyquoks.utils.get_path("data/")
33
+
34
+ _FILENAME: str = "{0}.json"
35
+
36
+ def __init__(self) -> None:
37
+ self._check_attributes()
38
+
39
+ for attribute, object_type in self.__class__.__annotations__.items():
40
+ if issubclass(
41
+ typing.get_args(object_type)[0],
42
+ pydantic.BaseModel,
43
+ ) if typing.get_origin(object_type) else issubclass(
44
+ object_type,
45
+ pydantic.BaseModel,
46
+ ):
47
+ try:
48
+ with open(self._PATH + self._FILENAME.format(attribute), "rb") as file:
49
+ data = json.loads(file.read())
50
+
51
+ if typing.get_origin(object_type) == list:
52
+ setattr(self, attribute, [typing.get_args(object_type)[0](**model) for model in data])
53
+ else:
54
+ setattr(self, attribute, object_type(**data))
55
+ except Exception:
56
+ setattr(self, attribute, None)
57
+
58
+ def update(self, **kwargs) -> None:
59
+ """
60
+ Updates provided attributes in object
61
+ """
62
+
63
+ for attribute, value in kwargs.items():
64
+ value: pydantic.BaseModel | list[pydantic.BaseModel]
65
+
66
+ if attribute not in self.__class__.__annotations__.keys():
67
+ raise AttributeError(f"{attribute} is not specified!")
68
+
69
+ object_type = self.__class__.__annotations__.get(attribute)
70
+
71
+ if not isinstance(
72
+ value,
73
+ typing.get_origin(object_type) if typing.get_origin(object_type) else object_type,
74
+ ):
75
+ raise AttributeError(
76
+ f"{attribute} has incorrect type! (must be {object_type.__name__})",
77
+ )
78
+
79
+ setattr(self, attribute, value)
80
+
81
+ os.makedirs(
82
+ name=self._PATH,
83
+ exist_ok=True,
84
+ )
85
+
86
+ with open(self._PATH + self._FILENAME.format(attribute), "w", encoding="utf-8") as file:
87
+ json.dump(
88
+ [model.model_dump() for model in value] if typing.get_origin(
89
+ object_type,
90
+ ) == list else value.model_dump(),
91
+ fp=file,
92
+ ensure_ascii=False,
93
+ indent=2,
94
+ )
@@ -0,0 +1,103 @@
1
+ import os
2
+ import sqlite3
3
+
4
+ import pyquoks.utils
5
+
6
+
7
+ class DatabaseManager(pyquoks.utils._HasRequiredAttributes):
8
+ """
9
+ Class for managing database connections
10
+
11
+ **Required attributes**::
12
+
13
+ # Predefined
14
+
15
+ _PATH = pyquoks.utils.get_path("db/")
16
+
17
+ Attributes:
18
+ _PATH: Path to the directory with databases
19
+ """
20
+
21
+ _REQUIRED_ATTRIBUTES = {
22
+ "_PATH",
23
+ }
24
+
25
+ _PATH: str = pyquoks.utils.get_path("db/")
26
+
27
+ def __init__(self) -> None:
28
+ self._check_attributes()
29
+
30
+ os.makedirs(
31
+ name=self._PATH,
32
+ exist_ok=True,
33
+ )
34
+
35
+ for attribute, object_type in self.__class__.__annotations__.items():
36
+ if issubclass(object_type, Database):
37
+ setattr(self, attribute, object_type(self))
38
+
39
+ def close_all(self) -> None:
40
+ """
41
+ Closes all database connections
42
+ """
43
+
44
+ for attribute, object_type in self.__class__.__annotations__.items():
45
+ if issubclass(object_type, Database):
46
+ getattr(self, attribute).close()
47
+
48
+
49
+ class Database(sqlite3.Connection, pyquoks.utils._HasRequiredAttributes):
50
+ """
51
+ Class that represents a database connection
52
+
53
+ **Required attributes**::
54
+
55
+ _NAME = "users"
56
+
57
+ _SQL = f\"""CREATE TABLE IF NOT EXISTS {_NAME} (user_id INTEGER PRIMARY KEY NOT NULL)\"""
58
+
59
+ # Predefined
60
+
61
+ _FILENAME = "{0}.db"
62
+
63
+ Attributes:
64
+ _NAME: Name of the database
65
+ _SQL: SQL expression for creating a table
66
+ _FILENAME: Filename of the database
67
+ _parent: Parent object
68
+ """
69
+
70
+ _REQUIRED_ATTRIBUTES = {
71
+ "_NAME",
72
+ "_SQL",
73
+ "_FILENAME",
74
+ }
75
+
76
+ _NAME: str
77
+
78
+ _SQL: str
79
+
80
+ _FILENAME: str = "{0}.db"
81
+
82
+ _parent: DatabaseManager
83
+
84
+ def __init__(self, parent: DatabaseManager) -> None:
85
+ self._check_attributes()
86
+
87
+ self._parent = parent
88
+
89
+ self._FILENAME = self._FILENAME.format(self._NAME)
90
+
91
+ super().__init__(
92
+ database=self._parent._PATH + self._FILENAME,
93
+ check_same_thread=False,
94
+ )
95
+ self.row_factory = sqlite3.Row
96
+
97
+ cursor = self.cursor()
98
+
99
+ cursor.execute(
100
+ self._SQL,
101
+ )
102
+
103
+ self.commit()
@@ -0,0 +1,9 @@
1
+ __all__ = [
2
+ "assets",
3
+ "environment",
4
+ "strings",
5
+ ]
6
+
7
+ from . import assets
8
+ from . import environment
9
+ from . import strings
@@ -0,0 +1,141 @@
1
+ import io
2
+
3
+ import PIL.Image
4
+ import requests
5
+
6
+ import pyquoks.utils
7
+
8
+
9
+ class AssetsProvider(pyquoks.utils._HasRequiredAttributes):
10
+ """
11
+ Class for providing various assets data
12
+
13
+ **Required attributes**::
14
+
15
+ # Predefined:
16
+
17
+ _PATH = pyquoks.utils.get_path("assets/")
18
+
19
+ Attributes:
20
+ _PATH: Path to the directory with assets folders
21
+ """
22
+
23
+ _REQUIRED_ATTRIBUTES = {
24
+ "_PATH",
25
+ }
26
+
27
+ _PATH: str = pyquoks.utils.get_path("assets/")
28
+
29
+ def __init__(self) -> None:
30
+ self._check_attributes()
31
+
32
+ for attribute, object_type in self.__class__.__annotations__.items():
33
+ if issubclass(object_type, Directory | Network):
34
+ setattr(self, attribute, object_type(self))
35
+
36
+
37
+ class Directory(pyquoks.utils._HasRequiredAttributes):
38
+ """
39
+ Class that represents a directory with various assets
40
+
41
+ **Required attributes**::
42
+
43
+ _ATTRIBUTES = {"picture1", "picture2"}
44
+
45
+ _PATH = "images/"
46
+
47
+ _FILENAME = "{0}.png"
48
+
49
+ Attributes:
50
+ _ATTRIBUTES: Names of files in the directory
51
+ _PATH: Path to the directory with assets files
52
+ _FILENAME: Filename of assets files
53
+ _parent: Parent object
54
+ """
55
+
56
+ _REQUIRED_ATTRIBUTES = {
57
+ "_ATTRIBUTES",
58
+ "_PATH",
59
+ "_FILENAME",
60
+ }
61
+
62
+ _ATTRIBUTES: set[str]
63
+
64
+ _PATH: str
65
+
66
+ _FILENAME: str
67
+
68
+ _parent: AssetsProvider
69
+
70
+ def __init__(self, parent: AssetsProvider) -> None:
71
+ self._check_attributes()
72
+
73
+ self._parent = parent
74
+
75
+ self._PATH = self._parent._PATH + self._PATH
76
+
77
+ for attribute in self._ATTRIBUTES:
78
+ try:
79
+ setattr(self, attribute, self.file_image(
80
+ path=self._PATH + self._FILENAME.format(attribute),
81
+ ))
82
+ except Exception:
83
+ setattr(self, attribute, None)
84
+
85
+ @staticmethod
86
+ def file_image(path: str) -> PIL.Image.Image:
87
+ """
88
+ :param path: Absolute path of the image file
89
+ :return: Image object from a file
90
+ """
91
+
92
+ with open(path, "rb") as file:
93
+ return PIL.Image.open(
94
+ fp=io.BytesIO(file.read()),
95
+ )
96
+
97
+
98
+ class Network(pyquoks.utils._HasRequiredAttributes):
99
+ """
100
+ Class that represents a set of images obtained from a network
101
+
102
+ **Required attributes**::
103
+
104
+ _URLS = {"example": "https://example.com/image.png"}
105
+
106
+ Attributes:
107
+ _URLS: Dictionary with names of attributes and URLs
108
+ _parent: Parent object
109
+ """
110
+
111
+ _REQUIRED_ATTRIBUTES = {
112
+ "_URLS",
113
+ }
114
+
115
+ _URLS: dict[str, str]
116
+
117
+ _parent: AssetsProvider
118
+
119
+ def __init__(self, parent: AssetsProvider) -> None:
120
+ self._check_attributes()
121
+
122
+ self._parent = parent
123
+
124
+ for attribute, url in self._URLS.items():
125
+ try:
126
+ setattr(self, attribute, self.network_image(
127
+ url=url,
128
+ ))
129
+ except Exception:
130
+ setattr(self, attribute, None)
131
+
132
+ @staticmethod
133
+ def network_image(url: str) -> PIL.Image.Image:
134
+ """
135
+ :param url: URL of the image file
136
+ :return: Image object from a URL
137
+ """
138
+
139
+ return PIL.Image.open(
140
+ fp=io.BytesIO(requests.get(url).content),
141
+ )
@@ -0,0 +1,18 @@
1
+ import os
2
+
3
+
4
+ class EnvironmentProvider:
5
+ """
6
+ Class for providing environment variables
7
+ """
8
+
9
+ def __init__(self) -> None:
10
+ self.load_variables()
11
+
12
+ def load_variables(self) -> None:
13
+ """
14
+ Loads specified environment variables
15
+ """
16
+
17
+ for attribute in self.__class__.__annotations__.keys():
18
+ setattr(self, attribute, os.getenv(attribute, None))
@@ -0,0 +1,23 @@
1
+ class StringsProvider:
2
+ """
3
+ Class for providing various strings data
4
+ """
5
+
6
+ def __init__(self) -> None:
7
+ for attribute, child_class in self.__class__.__annotations__.items():
8
+ if issubclass(child_class, Strings):
9
+ setattr(self, attribute, child_class(self))
10
+ else:
11
+ raise AttributeError(
12
+ f"{attribute} has incorrect type! (must be subclass of {Strings.__name__})",
13
+ )
14
+
15
+
16
+ class Strings:
17
+ """
18
+ Class that represents a container for strings
19
+ """
20
+
21
+ # noinspection PyUnusedLocal
22
+ def __init__(self, parent: StringsProvider) -> None:
23
+ ... # TODO
@@ -0,0 +1,5 @@
1
+ __all__ = [
2
+ "logger",
3
+ ]
4
+
5
+ from . import logger
@@ -0,0 +1,86 @@
1
+ import datetime
2
+ import logging
3
+ import os
4
+ import sys
5
+ import typing
6
+
7
+ import pyquoks.utils
8
+
9
+
10
+ class LoggerService(logging.Logger):
11
+ """
12
+ Class that provides methods for parallel logging
13
+
14
+ Attributes:
15
+ _LOG_PATH: Path to the logs file
16
+ """
17
+
18
+ _LOG_PATH: str | None
19
+
20
+ def __init__(
21
+ self,
22
+ filename: str,
23
+ level: int = logging.NOTSET,
24
+ file_handling: bool = True,
25
+ path: str = pyquoks.utils.get_path("logs/"),
26
+ ) -> None:
27
+ super().__init__(filename, level)
28
+
29
+ self.stream_handler = logging.StreamHandler(sys.stdout)
30
+ self.stream_handler.setFormatter(
31
+ logging.Formatter(
32
+ fmt="$levelname $asctime $name - $message",
33
+ datefmt="%d-%m-%y %H:%M:%S",
34
+ style="$",
35
+ )
36
+ )
37
+ self.addHandler(self.stream_handler)
38
+
39
+ if file_handling:
40
+ os.makedirs(
41
+ name=path,
42
+ exist_ok=True
43
+ )
44
+ self._LOG_PATH = path + f"{int(datetime.datetime.now().timestamp())}.{filename}.log"
45
+
46
+ self.file_handler = logging.FileHandler(
47
+ filename=self._LOG_PATH,
48
+ encoding="utf-8",
49
+ )
50
+ self.file_handler.setFormatter(
51
+ logging.Formatter(
52
+ fmt="$levelname $asctime - $message",
53
+ datefmt="%d-%m-%y %H:%M:%S",
54
+ style="$",
55
+ ),
56
+ )
57
+ self.addHandler(self.file_handler)
58
+ else:
59
+ self._LOG_PATH = None
60
+
61
+ @property
62
+ def file(self) -> typing.IO | None:
63
+ """
64
+ :return: Opened file-like object of current logs
65
+ """
66
+
67
+ if self._LOG_PATH:
68
+ return open(self._LOG_PATH, "rb")
69
+ else:
70
+ return None
71
+
72
+ def log_error(self, exception: Exception, raise_again: bool = False) -> None:
73
+ """
74
+ Logs an exception with detailed traceback
75
+
76
+ :param exception: Exception to be logged
77
+ :param raise_again: Whether or not exception should be raised again
78
+ """
79
+
80
+ self.error(
81
+ msg=exception,
82
+ exc_info=True,
83
+ )
84
+
85
+ if raise_again:
86
+ raise exception
pyquoks/utils.py CHANGED
@@ -38,12 +38,15 @@ def get_path(relative_path: str, use_meipass: bool = False) -> str:
38
38
  return os.path.join(base_path, relative_path)
39
39
 
40
40
 
41
- def get_process_created_datetime(pid: int = os.getpid()) -> datetime.datetime:
41
+ def get_process_created_datetime(pid: int = None) -> datetime.datetime:
42
42
  """
43
43
  :param pid: ID of the process
44
44
  :return: Datetime when the process was created
45
45
  """
46
46
 
47
+ if pid is None:
48
+ pid = os.getpid()
49
+
47
50
  process = psutil.Process(pid)
48
51
 
49
52
  return datetime.datetime.fromtimestamp(
@@ -1,17 +1,17 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyquoks
3
- Version: 2.4.0
3
+ Version: 2.5.0
4
4
  Summary: Пакет PyPI для часто используемых модулей в проектах diquoks
5
5
  License: MIT
6
6
  License-File: LICENSE
7
7
  Author: Denis Titovets
8
8
  Author-email: den232titovets@yandex.ru
9
- Requires-Python: >=3.14
9
+ Requires-Python: >=3.14,<4.0
10
10
  Classifier: License :: OSI Approved :: MIT License
11
11
  Classifier: Programming Language :: Python :: 3
12
12
  Classifier: Programming Language :: Python :: 3.14
13
13
  Requires-Dist: pillow (>=12.1.0)
14
- Requires-Dist: psutil (>=7.2.1)
14
+ Requires-Dist: psutil (>=7.2.2)
15
15
  Requires-Dist: pydantic (>=2.12.5)
16
16
  Requires-Dist: requests (>=2.32.5)
17
17
  Description-Content-Type: text/markdown