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 +6 -4
- pyquoks/managers/__init__.py +9 -0
- pyquoks/managers/config.py +154 -0
- pyquoks/managers/data.py +94 -0
- pyquoks/managers/database.py +103 -0
- pyquoks/providers/__init__.py +9 -0
- pyquoks/providers/assets.py +141 -0
- pyquoks/providers/environment.py +18 -0
- pyquoks/providers/strings.py +23 -0
- pyquoks/services/__init__.py +5 -0
- pyquoks/services/logger.py +86 -0
- pyquoks/utils.py +4 -1
- {pyquoks-2.4.0.dist-info → pyquoks-2.5.0.dist-info}/METADATA +3 -3
- pyquoks-2.5.0.dist-info/RECORD +16 -0
- {pyquoks-2.4.0.dist-info → pyquoks-2.5.0.dist-info}/WHEEL +1 -1
- pyquoks/data.py +0 -640
- pyquoks/test.py +0 -129
- pyquoks-2.4.0.dist-info/RECORD +0 -8
- {pyquoks-2.4.0.dist-info → pyquoks-2.5.0.dist-info}/licenses/LICENSE +0 -0
pyquoks/__init__.py
CHANGED
|
@@ -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)
|
pyquoks/managers/data.py
ADDED
|
@@ -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,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,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 =
|
|
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.
|
|
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.
|
|
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
|