bear-utils 0.0.1__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.
- bear_utils/__init__.py +51 -0
- bear_utils/__main__.py +14 -0
- bear_utils/_internal/__init__.py +0 -0
- bear_utils/_internal/_version.py +1 -0
- bear_utils/_internal/cli.py +119 -0
- bear_utils/_internal/debug.py +174 -0
- bear_utils/ai/__init__.py +30 -0
- bear_utils/ai/ai_helpers/__init__.py +136 -0
- bear_utils/ai/ai_helpers/_common.py +19 -0
- bear_utils/ai/ai_helpers/_config.py +24 -0
- bear_utils/ai/ai_helpers/_parsers.py +194 -0
- bear_utils/ai/ai_helpers/_types.py +15 -0
- bear_utils/cache/__init__.py +131 -0
- bear_utils/cli/__init__.py +22 -0
- bear_utils/cli/_args.py +12 -0
- bear_utils/cli/_get_version.py +207 -0
- bear_utils/cli/commands.py +105 -0
- bear_utils/cli/prompt_helpers.py +186 -0
- bear_utils/cli/shell/__init__.py +1 -0
- bear_utils/cli/shell/_base_command.py +81 -0
- bear_utils/cli/shell/_base_shell.py +430 -0
- bear_utils/cli/shell/_common.py +19 -0
- bear_utils/cli/typer_bridge.py +90 -0
- bear_utils/config/__init__.py +13 -0
- bear_utils/config/config_manager.py +229 -0
- bear_utils/config/dir_manager.py +69 -0
- bear_utils/config/settings_manager.py +179 -0
- bear_utils/constants/__init__.py +90 -0
- bear_utils/constants/_exceptions.py +8 -0
- bear_utils/constants/_exit_code.py +60 -0
- bear_utils/constants/_http_status_code.py +37 -0
- bear_utils/constants/_lazy_typing.py +15 -0
- bear_utils/constants/_meta.py +196 -0
- bear_utils/constants/date_related.py +25 -0
- bear_utils/constants/time_related.py +24 -0
- bear_utils/database/__init__.py +8 -0
- bear_utils/database/_db_manager.py +98 -0
- bear_utils/events/__init__.py +18 -0
- bear_utils/events/events_class.py +52 -0
- bear_utils/events/events_module.py +74 -0
- bear_utils/extras/__init__.py +28 -0
- bear_utils/extras/_async_helpers.py +67 -0
- bear_utils/extras/_tools.py +185 -0
- bear_utils/extras/_zapper.py +399 -0
- bear_utils/extras/platform_utils.py +57 -0
- bear_utils/extras/responses/__init__.py +5 -0
- bear_utils/extras/responses/function_response.py +451 -0
- bear_utils/extras/wrappers/__init__.py +1 -0
- bear_utils/extras/wrappers/add_methods.py +100 -0
- bear_utils/extras/wrappers/string_io.py +46 -0
- bear_utils/files/__init__.py +6 -0
- bear_utils/files/file_handlers/__init__.py +5 -0
- bear_utils/files/file_handlers/_base_file_handler.py +107 -0
- bear_utils/files/file_handlers/file_handler_factory.py +280 -0
- bear_utils/files/file_handlers/json_file_handler.py +71 -0
- bear_utils/files/file_handlers/log_file_handler.py +40 -0
- bear_utils/files/file_handlers/toml_file_handler.py +76 -0
- bear_utils/files/file_handlers/txt_file_handler.py +76 -0
- bear_utils/files/file_handlers/yaml_file_handler.py +64 -0
- bear_utils/files/ignore_parser.py +293 -0
- bear_utils/graphics/__init__.py +6 -0
- bear_utils/graphics/bear_gradient.py +145 -0
- bear_utils/graphics/font/__init__.py +13 -0
- bear_utils/graphics/font/_raw_block_letters.py +463 -0
- bear_utils/graphics/font/_theme.py +31 -0
- bear_utils/graphics/font/_utils.py +220 -0
- bear_utils/graphics/font/block_font.py +192 -0
- bear_utils/graphics/font/glitch_font.py +63 -0
- bear_utils/graphics/image_helpers.py +45 -0
- bear_utils/gui/__init__.py +8 -0
- bear_utils/gui/gui_tools/__init__.py +10 -0
- bear_utils/gui/gui_tools/_settings.py +36 -0
- bear_utils/gui/gui_tools/_types.py +12 -0
- bear_utils/gui/gui_tools/qt_app.py +150 -0
- bear_utils/gui/gui_tools/qt_color_picker.py +130 -0
- bear_utils/gui/gui_tools/qt_file_handler.py +130 -0
- bear_utils/gui/gui_tools/qt_input_dialog.py +303 -0
- bear_utils/logger_manager/__init__.py +109 -0
- bear_utils/logger_manager/_common.py +63 -0
- bear_utils/logger_manager/_console_junk.py +135 -0
- bear_utils/logger_manager/_log_level.py +50 -0
- bear_utils/logger_manager/_styles.py +95 -0
- bear_utils/logger_manager/logger_protocol.py +42 -0
- bear_utils/logger_manager/loggers/__init__.py +1 -0
- bear_utils/logger_manager/loggers/_console.py +223 -0
- bear_utils/logger_manager/loggers/_level_sin.py +61 -0
- bear_utils/logger_manager/loggers/_logger.py +19 -0
- bear_utils/logger_manager/loggers/base_logger.py +244 -0
- bear_utils/logger_manager/loggers/base_logger.pyi +51 -0
- bear_utils/logger_manager/loggers/basic_logger/__init__.py +5 -0
- bear_utils/logger_manager/loggers/basic_logger/logger.py +80 -0
- bear_utils/logger_manager/loggers/basic_logger/logger.pyi +19 -0
- bear_utils/logger_manager/loggers/buffer_logger.py +57 -0
- bear_utils/logger_manager/loggers/console_logger.py +278 -0
- bear_utils/logger_manager/loggers/console_logger.pyi +50 -0
- bear_utils/logger_manager/loggers/fastapi_logger.py +333 -0
- bear_utils/logger_manager/loggers/file_logger.py +151 -0
- bear_utils/logger_manager/loggers/simple_logger.py +98 -0
- bear_utils/logger_manager/loggers/sub_logger.py +105 -0
- bear_utils/logger_manager/loggers/sub_logger.pyi +23 -0
- bear_utils/monitoring/__init__.py +13 -0
- bear_utils/monitoring/_common.py +28 -0
- bear_utils/monitoring/host_monitor.py +346 -0
- bear_utils/time/__init__.py +59 -0
- bear_utils-0.0.1.dist-info/METADATA +305 -0
- bear_utils-0.0.1.dist-info/RECORD +107 -0
- bear_utils-0.0.1.dist-info/WHEEL +4 -0
@@ -0,0 +1,107 @@
|
|
1
|
+
from abc import ABC, abstractmethod
|
2
|
+
from collections.abc import Callable
|
3
|
+
from functools import wraps
|
4
|
+
from pathlib import Path
|
5
|
+
from typing import Any, ClassVar, ParamSpec, TypeVar, cast
|
6
|
+
|
7
|
+
P = ParamSpec("P")
|
8
|
+
R = TypeVar("R")
|
9
|
+
|
10
|
+
|
11
|
+
class FileHandler(ABC):
|
12
|
+
"""Abstract class for file handling with read, write, and present methods
|
13
|
+
|
14
|
+
:attr ext str: File extension to check for.
|
15
|
+
:method file_checker: Class method to check if file is of correct type.
|
16
|
+
:method read_file: Read file method.
|
17
|
+
:method write_file: Write file method.
|
18
|
+
:method present_file: Present file method.
|
19
|
+
"""
|
20
|
+
|
21
|
+
valid_extensions: ClassVar[list[str]] = []
|
22
|
+
valid_types: ClassVar[tuple[type, ...]] = ()
|
23
|
+
|
24
|
+
@classmethod
|
25
|
+
def file_checker(cls, file_path: Path) -> bool:
|
26
|
+
"""Check if the file is of the correct type.
|
27
|
+
|
28
|
+
Args:
|
29
|
+
file_path: Path to the file
|
30
|
+
|
31
|
+
Returns:
|
32
|
+
bool: True if the file is of the correct type, False otherwise
|
33
|
+
"""
|
34
|
+
return file_path.suffix.lstrip(".") in cls.valid_extensions
|
35
|
+
|
36
|
+
@classmethod
|
37
|
+
def check_data_type(cls, data: dict[str, Any] | str, valid_types: tuple[type, ...]) -> None:
|
38
|
+
"""Check if the data is of the correct type.
|
39
|
+
|
40
|
+
Args:
|
41
|
+
data: Data to check the type of
|
42
|
+
|
43
|
+
Returns:
|
44
|
+
bool: True if the data is of the correct type, False otherwise
|
45
|
+
"""
|
46
|
+
if not isinstance(data, valid_types):
|
47
|
+
raise TypeError(f"Data must be one of {valid_types}, got {type(data)}")
|
48
|
+
|
49
|
+
@classmethod
|
50
|
+
def ValidateFileType(cls, method: Callable[P, R]) -> Callable[P, R]: # noqa: N802 disable=invalid-name
|
51
|
+
"""Decorator to validate file type before executing a method.
|
52
|
+
|
53
|
+
This decorator checks if the file is of the correct type before
|
54
|
+
executing the method. If not, it raises a ValueError.
|
55
|
+
|
56
|
+
Args:
|
57
|
+
method: Method to decorate
|
58
|
+
|
59
|
+
Returns:
|
60
|
+
Decorated method
|
61
|
+
"""
|
62
|
+
|
63
|
+
@wraps(method)
|
64
|
+
def wrapper(self: "FileHandler", file_path: Path, *args: Any, **kwargs: Any) -> R:
|
65
|
+
if not self.file_checker(file_path):
|
66
|
+
raise ValueError(f"Invalid file type. Expected {self.valid_extensions}")
|
67
|
+
return method(self, file_path, *args, **kwargs) # type: ignore[return-value]
|
68
|
+
|
69
|
+
return cast("Callable[P, R]", wrapper)
|
70
|
+
|
71
|
+
@abstractmethod
|
72
|
+
def read_file(self, file_path: Path) -> dict[str, Any] | str:
|
73
|
+
if not file_path.exists():
|
74
|
+
raise ValueError(f"File does not exist: {file_path}")
|
75
|
+
|
76
|
+
@abstractmethod
|
77
|
+
def write_file(self, file_path: Path, data: dict[str, Any] | str, **kwargs) -> None:
|
78
|
+
if not file_path.parent.exists():
|
79
|
+
if kwargs.get("mkdir", False):
|
80
|
+
file_path.parent.mkdir(parents=True, exist_ok=True)
|
81
|
+
else:
|
82
|
+
raise ValueError(f"Directory does not exist: {file_path.parent}. Set mkdir=True to create it.")
|
83
|
+
|
84
|
+
@abstractmethod
|
85
|
+
def present_file(self, data: dict[str, Any] | str) -> str: ...
|
86
|
+
|
87
|
+
@staticmethod
|
88
|
+
def get_file_info(file_path: Path) -> dict[str, Any]:
|
89
|
+
"""Get information about a file.
|
90
|
+
|
91
|
+
Args:
|
92
|
+
file_path: Path to the file
|
93
|
+
|
94
|
+
Returns:
|
95
|
+
Dictionary with file information
|
96
|
+
"""
|
97
|
+
if not file_path.exists():
|
98
|
+
raise ValueError(f"File does not exist: {file_path}")
|
99
|
+
|
100
|
+
return {
|
101
|
+
"path": file_path,
|
102
|
+
"name": file_path.name,
|
103
|
+
"extension": file_path.suffix,
|
104
|
+
"size": file_path.stat().st_size if file_path.exists() else 0,
|
105
|
+
"is_file": file_path.is_file() if file_path.exists() else False,
|
106
|
+
"modified": file_path.stat().st_mtime if file_path.exists() else None,
|
107
|
+
}
|
@@ -0,0 +1,280 @@
|
|
1
|
+
"""Factory for creating and managing file handlers based on file types."""
|
2
|
+
|
3
|
+
from pathlib import Path
|
4
|
+
from typing import Any, cast
|
5
|
+
import warnings
|
6
|
+
|
7
|
+
from ._base_file_handler import FileHandler
|
8
|
+
from .json_file_handler import JsonFileHandler
|
9
|
+
from .log_file_handler import LogFileHandler
|
10
|
+
from .toml_file_handler import TomlFileHandler
|
11
|
+
from .txt_file_handler import TextFileHandler
|
12
|
+
from .yaml_file_handler import YamlFileHandler
|
13
|
+
|
14
|
+
|
15
|
+
class FileHandlerFactory:
|
16
|
+
"""Factory class to create and manage FileHandler instances based on file types.
|
17
|
+
|
18
|
+
This factory maintains state about the current file path and can create appropriate
|
19
|
+
handlers for different file types.
|
20
|
+
"""
|
21
|
+
|
22
|
+
def __init__(self, file_path: Path | None = None) -> None:
|
23
|
+
"""Initialize the factory with an optional file path.
|
24
|
+
|
25
|
+
Args:
|
26
|
+
file_path: Optional Path to the file to handle
|
27
|
+
"""
|
28
|
+
self._file_path: Path | None = file_path
|
29
|
+
self._handler_registry: dict[str, type[FileHandler]] = {}
|
30
|
+
self._handler_instance: FileHandler | None = None
|
31
|
+
self._register_default_handlers()
|
32
|
+
|
33
|
+
@property
|
34
|
+
def file_path(self) -> Path | None:
|
35
|
+
"""Get the current file path."""
|
36
|
+
return self._file_path
|
37
|
+
|
38
|
+
@file_path.setter
|
39
|
+
def file_path(self, path: Path) -> None:
|
40
|
+
"""Set the current file path and reset the handler instance.
|
41
|
+
|
42
|
+
Args:
|
43
|
+
path: New file path to set
|
44
|
+
"""
|
45
|
+
self._file_path = path
|
46
|
+
self._handler_instance = None
|
47
|
+
|
48
|
+
def _register_default_handlers(self) -> None:
|
49
|
+
"""Register all default handlers from the package.
|
50
|
+
|
51
|
+
This method imports and registers all standard FileHandler implementations.
|
52
|
+
"""
|
53
|
+
try:
|
54
|
+
self.register_handler(handler_class=JsonFileHandler)
|
55
|
+
self.register_handler(handler_class=TextFileHandler)
|
56
|
+
self.register_handler(handler_class=YamlFileHandler)
|
57
|
+
self.register_handler(handler_class=LogFileHandler)
|
58
|
+
self.register_handler(handler_class=TomlFileHandler)
|
59
|
+
|
60
|
+
except ImportError as e:
|
61
|
+
warnings.warn(f"Could not import all default handlers: {e}", UserWarning, stacklevel=2)
|
62
|
+
|
63
|
+
def register_handler(self, handler_class: type[FileHandler]) -> None:
|
64
|
+
"""Register a handler class for its supported extensions.
|
65
|
+
|
66
|
+
Args:
|
67
|
+
handler_class: The FileHandler class to register
|
68
|
+
"""
|
69
|
+
for ext in handler_class.valid_extensions:
|
70
|
+
self._handler_registry[ext] = handler_class
|
71
|
+
|
72
|
+
def get_handler_class(self, extension: str) -> type[FileHandler] | None:
|
73
|
+
"""Get the handler class for a specific extension.
|
74
|
+
|
75
|
+
Args:
|
76
|
+
extension: File extension (without the dot)
|
77
|
+
|
78
|
+
Returns:
|
79
|
+
FileHandler class for the extension or None if not found
|
80
|
+
"""
|
81
|
+
return self._handler_registry.get(extension)
|
82
|
+
|
83
|
+
def get_handler_for_path(self, file_path: Path | None = None) -> FileHandler:
|
84
|
+
"""Get or create a handler instance for the given path or the current path.
|
85
|
+
|
86
|
+
Args:
|
87
|
+
file_path: Path to get handler for (uses stored path if None)
|
88
|
+
|
89
|
+
Returns:
|
90
|
+
Appropriate FileHandler instance
|
91
|
+
|
92
|
+
Raises:
|
93
|
+
ValueError: If no file path is provided or stored, or if no handler exists for the file extension
|
94
|
+
"""
|
95
|
+
path: Path | None = file_path or self._file_path
|
96
|
+
if path is None:
|
97
|
+
raise ValueError("No file path provided or stored in factory")
|
98
|
+
|
99
|
+
if file_path is not None:
|
100
|
+
self._file_path = file_path
|
101
|
+
self._handler_instance = None
|
102
|
+
|
103
|
+
if self._handler_instance and file_path is None:
|
104
|
+
return self._handler_instance
|
105
|
+
|
106
|
+
extension: str = path.suffix.lstrip(".")
|
107
|
+
handler_class: type[FileHandler] | None = self.get_handler_class(extension)
|
108
|
+
|
109
|
+
if handler_class is None:
|
110
|
+
raise ValueError(f"No handler registered for extension: {extension}")
|
111
|
+
|
112
|
+
handler = handler_class()
|
113
|
+
if file_path is None:
|
114
|
+
self._handler_instance = handler
|
115
|
+
|
116
|
+
return handler
|
117
|
+
|
118
|
+
def handle_file(self, operation: str, file_path: Path | None = None, data: Any = None, **kwargs) -> Any:
|
119
|
+
"""Handle file operations using the appropriate handler.
|
120
|
+
|
121
|
+
Args:
|
122
|
+
operation: Operation to perform ('read', 'write', 'present', 'info')
|
123
|
+
file_path: Path to the file (uses stored path if None)
|
124
|
+
data: Data for write operations
|
125
|
+
**kwargs: Additional arguments for the operation
|
126
|
+
|
127
|
+
Returns:
|
128
|
+
Result of the operation
|
129
|
+
|
130
|
+
Raises:
|
131
|
+
ValueError: If an invalid operation is specified
|
132
|
+
"""
|
133
|
+
path: Path | None = file_path or self._file_path
|
134
|
+
if not path and operation != "present":
|
135
|
+
raise ValueError("File path required for this operation")
|
136
|
+
|
137
|
+
match operation:
|
138
|
+
case "read":
|
139
|
+
handler = self.get_handler_for_path(path)
|
140
|
+
return handler.read_file(cast("Path", path), **kwargs)
|
141
|
+
case "write":
|
142
|
+
if data is None:
|
143
|
+
raise ValueError("Data required for write operation")
|
144
|
+
handler = self.get_handler_for_path(path)
|
145
|
+
return handler.write_file(cast("Path", path), data, **kwargs)
|
146
|
+
case "present":
|
147
|
+
if data is None:
|
148
|
+
raise ValueError("Data required for present operation")
|
149
|
+
if path:
|
150
|
+
handler: FileHandler = self.get_handler_for_path(path)
|
151
|
+
elif isinstance(data, dict):
|
152
|
+
yaml_class = self.get_handler_class("yaml")
|
153
|
+
if yaml_class:
|
154
|
+
handler = yaml_class()
|
155
|
+
else:
|
156
|
+
raise ValueError("No handler available for dict data")
|
157
|
+
elif isinstance(data, str):
|
158
|
+
text_class = self.get_handler_class("txt")
|
159
|
+
if text_class:
|
160
|
+
handler = text_class()
|
161
|
+
else:
|
162
|
+
raise ValueError("No handler available for string data")
|
163
|
+
else:
|
164
|
+
raise ValueError(f"Cannot determine handler for data type: {type(data)}")
|
165
|
+
return handler.present_file(data)
|
166
|
+
|
167
|
+
case "info":
|
168
|
+
handler = self.get_handler_for_path(path)
|
169
|
+
return handler.get_file_info(cast("Path", path))
|
170
|
+
case _:
|
171
|
+
raise ValueError(f"Invalid operation: {operation}")
|
172
|
+
|
173
|
+
def read(self, file_path: Path | None = None, **kwargs) -> Any:
|
174
|
+
"""Read a file using the appropriate handler.
|
175
|
+
|
176
|
+
Args:
|
177
|
+
file_path: Path to read (uses stored path if None)
|
178
|
+
**kwargs: Additional arguments for read_file
|
179
|
+
|
180
|
+
Returns:
|
181
|
+
File contents
|
182
|
+
"""
|
183
|
+
return self.handle_file("read", file_path, **kwargs)
|
184
|
+
|
185
|
+
def write(self, data: dict | str, file_path: Path | None = None, **kwargs) -> None:
|
186
|
+
"""Write to a file using the appropriate handler.
|
187
|
+
|
188
|
+
Args:
|
189
|
+
data: Data to write
|
190
|
+
file_path: Path to write to (uses stored path if None)
|
191
|
+
**kwargs: Additional arguments for write_file
|
192
|
+
"""
|
193
|
+
return self.handle_file("write", file_path, data, **kwargs)
|
194
|
+
|
195
|
+
def present(self, data: dict | str, file_path: Path | None = None) -> str:
|
196
|
+
"""Present data using the appropriate handler.
|
197
|
+
|
198
|
+
Args:
|
199
|
+
data: Data to present
|
200
|
+
file_path: Optional path to determine format (uses stored path if None)
|
201
|
+
|
202
|
+
Returns:
|
203
|
+
String representation of the data
|
204
|
+
"""
|
205
|
+
return self.handle_file("present", file_path, data)
|
206
|
+
|
207
|
+
def get_info(self, file_path: Path | None = None) -> dict[str, Any]:
|
208
|
+
"""Get information about a file.
|
209
|
+
|
210
|
+
Args:
|
211
|
+
file_path: Path to get info for (uses stored path if None)
|
212
|
+
|
213
|
+
Returns:
|
214
|
+
Dictionary with file information
|
215
|
+
"""
|
216
|
+
return self.handle_file("info", file_path)
|
217
|
+
|
218
|
+
def convert(self, source_path: Path, target_path: Path, **kwargs) -> None:
|
219
|
+
"""Convert a file from one format to another.
|
220
|
+
|
221
|
+
Args:
|
222
|
+
source_path: Path to the source file
|
223
|
+
target_path: Path to the target file
|
224
|
+
**kwargs: Additional arguments for read_file and write_file
|
225
|
+
"""
|
226
|
+
source_handler = self.get_handler_for_path(source_path)
|
227
|
+
data = source_handler.read_file(source_path, **kwargs)
|
228
|
+
|
229
|
+
target_handler = self.get_handler_for_path(target_path)
|
230
|
+
target_handler.write_file(target_path, data, **kwargs)
|
231
|
+
|
232
|
+
|
233
|
+
_default_factory = FileHandlerFactory()
|
234
|
+
|
235
|
+
|
236
|
+
def get_handler_for_file(file_path: Path) -> FileHandler:
|
237
|
+
"""Get a handler for the given file path.
|
238
|
+
|
239
|
+
Args:
|
240
|
+
file_path: Path to the file
|
241
|
+
|
242
|
+
Returns:
|
243
|
+
Appropriate FileHandler instance
|
244
|
+
"""
|
245
|
+
return _default_factory.get_handler_for_path(file_path)
|
246
|
+
|
247
|
+
|
248
|
+
def read_file(file_path: Path, **kwargs) -> Any:
|
249
|
+
"""Read a file using the appropriate handler.
|
250
|
+
|
251
|
+
Args:
|
252
|
+
file_path: Path to the file
|
253
|
+
**kwargs: Additional arguments for read_file
|
254
|
+
|
255
|
+
Returns:
|
256
|
+
File contents
|
257
|
+
"""
|
258
|
+
return _default_factory.read(file_path, **kwargs)
|
259
|
+
|
260
|
+
|
261
|
+
def write_file(file_path: Path, data: Any, **kwargs) -> None:
|
262
|
+
"""Write to a file using the appropriate handler.
|
263
|
+
|
264
|
+
Args:
|
265
|
+
file_path: Path to the file
|
266
|
+
data: Data to write
|
267
|
+
**kwargs: Additional arguments for write_file
|
268
|
+
"""
|
269
|
+
return _default_factory.write(data, file_path, **kwargs)
|
270
|
+
|
271
|
+
|
272
|
+
def convert_file(source_path: Path, target_path: Path, **kwargs) -> None:
|
273
|
+
"""Convert a file from one format to another.
|
274
|
+
|
275
|
+
Args:
|
276
|
+
source_path: Path to the source file
|
277
|
+
target_path: Path to the target file
|
278
|
+
**kwargs: Additional arguments for read_file and write_file
|
279
|
+
"""
|
280
|
+
return _default_factory.convert(source_path, target_path, **kwargs)
|
@@ -0,0 +1,71 @@
|
|
1
|
+
"""JsonFileHandler: A class for handling JSON files in Bear Utils."""
|
2
|
+
|
3
|
+
import json
|
4
|
+
from pathlib import Path
|
5
|
+
from typing import Any, ClassVar, overload
|
6
|
+
|
7
|
+
from ._base_file_handler import FileHandler
|
8
|
+
|
9
|
+
|
10
|
+
class JsonFileHandler(FileHandler):
|
11
|
+
"""Class for handling JSON files with read, write, and present methods"""
|
12
|
+
|
13
|
+
valid_extensions: ClassVar[list[str]] = ["json"]
|
14
|
+
valid_types: ClassVar[tuple[type, ...]] = (dict, list, str)
|
15
|
+
|
16
|
+
@FileHandler.ValidateFileType
|
17
|
+
def read_file(self, file_path: Path, **kwargs) -> dict[str, Any]:
|
18
|
+
"""Read a JSON file and return its content as a dictionary."""
|
19
|
+
try:
|
20
|
+
super().read_file(file_path=file_path)
|
21
|
+
with open(file_path, encoding="utf-8") as file:
|
22
|
+
return json.load(file, **kwargs)
|
23
|
+
except Exception as e:
|
24
|
+
raise ValueError(f"Error reading file: {e}") from e
|
25
|
+
|
26
|
+
@overload
|
27
|
+
def write_file(self, file_path: Path, data: str) -> None: ...
|
28
|
+
|
29
|
+
@overload
|
30
|
+
def write_file(self, file_path: Path, data: list[str]) -> None: ...
|
31
|
+
|
32
|
+
@overload
|
33
|
+
def write_file(self, file_path: Path, data: dict[str, Any], indent: int = 2, sort_keys: bool = False) -> None: ...
|
34
|
+
|
35
|
+
@FileHandler.ValidateFileType
|
36
|
+
def write_file(
|
37
|
+
self,
|
38
|
+
file_path: Path,
|
39
|
+
data: dict[str, Any] | str,
|
40
|
+
indent: int = 2,
|
41
|
+
sort_keys: bool = False,
|
42
|
+
**_,
|
43
|
+
) -> None:
|
44
|
+
"""Write data to a JSON file."""
|
45
|
+
try:
|
46
|
+
super().write_file(file_path=file_path, data=data)
|
47
|
+
self.check_data_type(data=data, valid_types=self.valid_types)
|
48
|
+
data = self.present_file(data, indent=indent, sort_keys=sort_keys)
|
49
|
+
file_path.write_text(data=data, encoding="utf-8")
|
50
|
+
except Exception as e:
|
51
|
+
raise ValueError(f"Error writing file: {e}") from e
|
52
|
+
|
53
|
+
@overload
|
54
|
+
def present_file(self, data: dict[str, Any], indent: int = 2, sort_keys: bool = False) -> str: ...
|
55
|
+
|
56
|
+
@overload
|
57
|
+
def present_file(self, data: str, **kwargs) -> str: ...
|
58
|
+
|
59
|
+
def present_file(self, data: dict[str, Any] | str, indent: int = 2, sort_keys: bool = False, **_) -> str:
|
60
|
+
"""Present data as a JSON string."""
|
61
|
+
try:
|
62
|
+
if isinstance(data, list):
|
63
|
+
data = {"data": data}
|
64
|
+
elif isinstance(data, str):
|
65
|
+
try:
|
66
|
+
data = json.loads(data)
|
67
|
+
except json.JSONDecodeError as e:
|
68
|
+
raise ValueError(f"Invalid JSON string: {e}") from e
|
69
|
+
return json.dumps(data, indent=indent, sort_keys=sort_keys)
|
70
|
+
except Exception as e:
|
71
|
+
raise ValueError(f"Error presenting file: {e}") from e
|
@@ -0,0 +1,40 @@
|
|
1
|
+
"""Log File Handler Module"""
|
2
|
+
|
3
|
+
from pathlib import Path
|
4
|
+
from typing import Any, ClassVar, cast
|
5
|
+
|
6
|
+
from ._base_file_handler import FileHandler
|
7
|
+
|
8
|
+
|
9
|
+
class LogFileHandler(FileHandler):
|
10
|
+
"""Class for handling .log files with read, write, and present methods"""
|
11
|
+
|
12
|
+
valid_extensions: ClassVar[list[str]] = ["log"]
|
13
|
+
valid_types: ClassVar[tuple[type, ...]] = (str,)
|
14
|
+
|
15
|
+
@FileHandler.ValidateFileType
|
16
|
+
def read_file(self, file_path: Path) -> str:
|
17
|
+
"""Read a log file and return its content as a string."""
|
18
|
+
try:
|
19
|
+
super().read_file(file_path)
|
20
|
+
with open(file_path, encoding="utf-8") as file:
|
21
|
+
return file.read()
|
22
|
+
except Exception as e:
|
23
|
+
raise ValueError(f"Error reading file: {e}") from e
|
24
|
+
|
25
|
+
@FileHandler.ValidateFileType
|
26
|
+
def write_file(self, file_path: Path, data: dict[str, Any] | str, **_) -> None:
|
27
|
+
"""Write data to a log file."""
|
28
|
+
try:
|
29
|
+
super().write_file(file_path=file_path, data=data)
|
30
|
+
self.check_data_type(data=data, valid_types=self.valid_types)
|
31
|
+
with open(file_path, "w", encoding="utf-8") as file:
|
32
|
+
file.write(cast("str", data))
|
33
|
+
except Exception as e:
|
34
|
+
raise ValueError(f"Error writing file: {e}") from e
|
35
|
+
|
36
|
+
def present_file(self, data: dict[str, Any] | list[str] | str, **_) -> str:
|
37
|
+
"""Present data as a string."""
|
38
|
+
if isinstance(data, dict):
|
39
|
+
data = "\n".join(f"{key}: {value}" for key, value in data.items())
|
40
|
+
return str(data)
|
@@ -0,0 +1,76 @@
|
|
1
|
+
"""TOML File Handler Module"""
|
2
|
+
|
3
|
+
from dataclasses import dataclass
|
4
|
+
from pathlib import Path
|
5
|
+
from typing import Any, ClassVar
|
6
|
+
|
7
|
+
import toml
|
8
|
+
|
9
|
+
from ._base_file_handler import FileHandler
|
10
|
+
|
11
|
+
|
12
|
+
class TomlFileHandler(FileHandler):
|
13
|
+
"""Class for handling .toml files with read, write, and present methods"""
|
14
|
+
|
15
|
+
valid_extensions: ClassVar[list[str]] = ["toml"]
|
16
|
+
valid_types: ClassVar[tuple[type, ...]] = (dict, str)
|
17
|
+
|
18
|
+
@FileHandler.ValidateFileType
|
19
|
+
def read_file(self, file_path: Path) -> dict:
|
20
|
+
"""Read a TOML file and return its content as a dictionary."""
|
21
|
+
try:
|
22
|
+
super().read_file(file_path)
|
23
|
+
|
24
|
+
return toml.load(file_path)
|
25
|
+
except Exception as e:
|
26
|
+
raise ValueError(f"Error reading file: {e}") from e
|
27
|
+
|
28
|
+
@FileHandler.ValidateFileType
|
29
|
+
def write_file(self, file_path: Path, data: dict[str, Any] | str, **kwargs) -> None:
|
30
|
+
"""Write data to a TOML file."""
|
31
|
+
try:
|
32
|
+
super().write_file(file_path=file_path, data=data)
|
33
|
+
self.check_data_type(data=data, valid_types=self.valid_types)
|
34
|
+
with open(file_path, "w", encoding="utf-8") as file:
|
35
|
+
if isinstance(data, dict):
|
36
|
+
toml.dump(data, file, **kwargs)
|
37
|
+
else:
|
38
|
+
file.write(data)
|
39
|
+
except Exception as e:
|
40
|
+
raise ValueError(f"Error writing file: {e}") from e
|
41
|
+
|
42
|
+
def present_file(self, data: dict[str, Any] | str, **_) -> str:
|
43
|
+
"""Present data as a string."""
|
44
|
+
# TODO: Actually implement this method to format TOML data nicely
|
45
|
+
return str(data)
|
46
|
+
|
47
|
+
|
48
|
+
@dataclass
|
49
|
+
class PyProjectToml:
|
50
|
+
"""Dataclass for handling pyproject.toml files"""
|
51
|
+
|
52
|
+
name: str
|
53
|
+
version: str
|
54
|
+
description: str | None = None
|
55
|
+
author_name: str | None = None
|
56
|
+
author_email: str | None = None
|
57
|
+
dependencies: list[str] | None = None
|
58
|
+
|
59
|
+
def __post_init__(self):
|
60
|
+
if self.dependencies:
|
61
|
+
self.dependencies = [dep.split(" ")[0] for dep in self.dependencies if isinstance(dep, str)]
|
62
|
+
self.dependencies = [dep.split(">=")[0] for dep in self.dependencies]
|
63
|
+
|
64
|
+
@classmethod
|
65
|
+
def from_dict(cls, data: dict[str, Any]) -> "PyProjectToml":
|
66
|
+
"""Create a PyProjectToml instance from a dictionary."""
|
67
|
+
data = data.get("project", {})
|
68
|
+
authors: dict = data.get("authors", {})[0]
|
69
|
+
return cls(
|
70
|
+
name=data.get("name", ""),
|
71
|
+
version=data.get("version", ""),
|
72
|
+
description=data.get("description"),
|
73
|
+
author_name=authors.get("name") if authors else None,
|
74
|
+
author_email=authors.get("email") if authors else None,
|
75
|
+
dependencies=data.get("dependencies", []),
|
76
|
+
)
|
@@ -0,0 +1,76 @@
|
|
1
|
+
"""Text File Handler Module for .txt files"""
|
2
|
+
|
3
|
+
import json
|
4
|
+
from pathlib import Path
|
5
|
+
from typing import Any, ClassVar, overload
|
6
|
+
|
7
|
+
from ._base_file_handler import FileHandler
|
8
|
+
|
9
|
+
|
10
|
+
class TextFileHandler(FileHandler):
|
11
|
+
"""Class for handling .txt files with read, write, and present methods"""
|
12
|
+
|
13
|
+
valid_extensions: ClassVar[list[str]] = ["txt"]
|
14
|
+
valid_types: ClassVar[tuple[type, ...]] = (str, dict, list)
|
15
|
+
|
16
|
+
@FileHandler.ValidateFileType
|
17
|
+
def read_file(self, file_path: Path) -> str:
|
18
|
+
"""Read a text file and return its content as a string."""
|
19
|
+
try:
|
20
|
+
super().read_file(file_path)
|
21
|
+
with open(file_path, encoding="utf-8") as file:
|
22
|
+
return file.read()
|
23
|
+
except Exception as e:
|
24
|
+
raise ValueError(f"Error reading file: {e}") from e
|
25
|
+
|
26
|
+
@overload
|
27
|
+
def write_file(self, file_path: Path, data: str) -> None: ...
|
28
|
+
|
29
|
+
@overload
|
30
|
+
def write_file(self, file_path: Path, data: list[str]) -> None: ...
|
31
|
+
|
32
|
+
@overload
|
33
|
+
def write_file(self, file_path: Path, data: dict[str, Any], indent: int = 2, sort_keys: bool = False) -> None: ...
|
34
|
+
|
35
|
+
@FileHandler.ValidateFileType
|
36
|
+
def write_file(
|
37
|
+
self,
|
38
|
+
file_path: Path,
|
39
|
+
data: dict[str, Any] | str,
|
40
|
+
indent: int = 2,
|
41
|
+
sort_keys: bool = False,
|
42
|
+
**_,
|
43
|
+
) -> None:
|
44
|
+
"""Write data to a text file."""
|
45
|
+
try:
|
46
|
+
super().write_file(file_path=file_path, data=data)
|
47
|
+
self.check_data_type(data=data, valid_types=self.valid_types)
|
48
|
+
if isinstance(data, dict):
|
49
|
+
data = json.dumps(data, indent=indent, sort_keys=sort_keys)
|
50
|
+
elif isinstance(data, list):
|
51
|
+
data = "\n".join(data)
|
52
|
+
file_path.write_text(data=data, encoding="utf-8")
|
53
|
+
except Exception as e:
|
54
|
+
raise ValueError(f"Error writing file: {e}") from e
|
55
|
+
|
56
|
+
def present_file(self, data: dict[str, Any] | list[str] | Any, **kwargs) -> str:
|
57
|
+
"""Present data as a string.
|
58
|
+
|
59
|
+
This method converts the data to a string representation if it is a dictionary,
|
60
|
+
|
61
|
+
Args:
|
62
|
+
data (dict[str, Any] | str): Data to present
|
63
|
+
Returns:
|
64
|
+
str: String representation of the data
|
65
|
+
"""
|
66
|
+
converted_data: str | None = None
|
67
|
+
if data is None:
|
68
|
+
raise ValueError("No data to present")
|
69
|
+
if isinstance(data, dict):
|
70
|
+
converted_data = json.dumps(data, indent=kwargs.get("indent", 2), sort_keys=kwargs.get("sort", False))
|
71
|
+
elif isinstance(data, list):
|
72
|
+
converted_data = "\n".join(data)
|
73
|
+
elif not isinstance(data, str):
|
74
|
+
raise TypeError("Data must be a string for text files")
|
75
|
+
|
76
|
+
return str(converted_data if isinstance(data, dict | list) else data)
|