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.
Files changed (107) hide show
  1. bear_utils/__init__.py +51 -0
  2. bear_utils/__main__.py +14 -0
  3. bear_utils/_internal/__init__.py +0 -0
  4. bear_utils/_internal/_version.py +1 -0
  5. bear_utils/_internal/cli.py +119 -0
  6. bear_utils/_internal/debug.py +174 -0
  7. bear_utils/ai/__init__.py +30 -0
  8. bear_utils/ai/ai_helpers/__init__.py +136 -0
  9. bear_utils/ai/ai_helpers/_common.py +19 -0
  10. bear_utils/ai/ai_helpers/_config.py +24 -0
  11. bear_utils/ai/ai_helpers/_parsers.py +194 -0
  12. bear_utils/ai/ai_helpers/_types.py +15 -0
  13. bear_utils/cache/__init__.py +131 -0
  14. bear_utils/cli/__init__.py +22 -0
  15. bear_utils/cli/_args.py +12 -0
  16. bear_utils/cli/_get_version.py +207 -0
  17. bear_utils/cli/commands.py +105 -0
  18. bear_utils/cli/prompt_helpers.py +186 -0
  19. bear_utils/cli/shell/__init__.py +1 -0
  20. bear_utils/cli/shell/_base_command.py +81 -0
  21. bear_utils/cli/shell/_base_shell.py +430 -0
  22. bear_utils/cli/shell/_common.py +19 -0
  23. bear_utils/cli/typer_bridge.py +90 -0
  24. bear_utils/config/__init__.py +13 -0
  25. bear_utils/config/config_manager.py +229 -0
  26. bear_utils/config/dir_manager.py +69 -0
  27. bear_utils/config/settings_manager.py +179 -0
  28. bear_utils/constants/__init__.py +90 -0
  29. bear_utils/constants/_exceptions.py +8 -0
  30. bear_utils/constants/_exit_code.py +60 -0
  31. bear_utils/constants/_http_status_code.py +37 -0
  32. bear_utils/constants/_lazy_typing.py +15 -0
  33. bear_utils/constants/_meta.py +196 -0
  34. bear_utils/constants/date_related.py +25 -0
  35. bear_utils/constants/time_related.py +24 -0
  36. bear_utils/database/__init__.py +8 -0
  37. bear_utils/database/_db_manager.py +98 -0
  38. bear_utils/events/__init__.py +18 -0
  39. bear_utils/events/events_class.py +52 -0
  40. bear_utils/events/events_module.py +74 -0
  41. bear_utils/extras/__init__.py +28 -0
  42. bear_utils/extras/_async_helpers.py +67 -0
  43. bear_utils/extras/_tools.py +185 -0
  44. bear_utils/extras/_zapper.py +399 -0
  45. bear_utils/extras/platform_utils.py +57 -0
  46. bear_utils/extras/responses/__init__.py +5 -0
  47. bear_utils/extras/responses/function_response.py +451 -0
  48. bear_utils/extras/wrappers/__init__.py +1 -0
  49. bear_utils/extras/wrappers/add_methods.py +100 -0
  50. bear_utils/extras/wrappers/string_io.py +46 -0
  51. bear_utils/files/__init__.py +6 -0
  52. bear_utils/files/file_handlers/__init__.py +5 -0
  53. bear_utils/files/file_handlers/_base_file_handler.py +107 -0
  54. bear_utils/files/file_handlers/file_handler_factory.py +280 -0
  55. bear_utils/files/file_handlers/json_file_handler.py +71 -0
  56. bear_utils/files/file_handlers/log_file_handler.py +40 -0
  57. bear_utils/files/file_handlers/toml_file_handler.py +76 -0
  58. bear_utils/files/file_handlers/txt_file_handler.py +76 -0
  59. bear_utils/files/file_handlers/yaml_file_handler.py +64 -0
  60. bear_utils/files/ignore_parser.py +293 -0
  61. bear_utils/graphics/__init__.py +6 -0
  62. bear_utils/graphics/bear_gradient.py +145 -0
  63. bear_utils/graphics/font/__init__.py +13 -0
  64. bear_utils/graphics/font/_raw_block_letters.py +463 -0
  65. bear_utils/graphics/font/_theme.py +31 -0
  66. bear_utils/graphics/font/_utils.py +220 -0
  67. bear_utils/graphics/font/block_font.py +192 -0
  68. bear_utils/graphics/font/glitch_font.py +63 -0
  69. bear_utils/graphics/image_helpers.py +45 -0
  70. bear_utils/gui/__init__.py +8 -0
  71. bear_utils/gui/gui_tools/__init__.py +10 -0
  72. bear_utils/gui/gui_tools/_settings.py +36 -0
  73. bear_utils/gui/gui_tools/_types.py +12 -0
  74. bear_utils/gui/gui_tools/qt_app.py +150 -0
  75. bear_utils/gui/gui_tools/qt_color_picker.py +130 -0
  76. bear_utils/gui/gui_tools/qt_file_handler.py +130 -0
  77. bear_utils/gui/gui_tools/qt_input_dialog.py +303 -0
  78. bear_utils/logger_manager/__init__.py +109 -0
  79. bear_utils/logger_manager/_common.py +63 -0
  80. bear_utils/logger_manager/_console_junk.py +135 -0
  81. bear_utils/logger_manager/_log_level.py +50 -0
  82. bear_utils/logger_manager/_styles.py +95 -0
  83. bear_utils/logger_manager/logger_protocol.py +42 -0
  84. bear_utils/logger_manager/loggers/__init__.py +1 -0
  85. bear_utils/logger_manager/loggers/_console.py +223 -0
  86. bear_utils/logger_manager/loggers/_level_sin.py +61 -0
  87. bear_utils/logger_manager/loggers/_logger.py +19 -0
  88. bear_utils/logger_manager/loggers/base_logger.py +244 -0
  89. bear_utils/logger_manager/loggers/base_logger.pyi +51 -0
  90. bear_utils/logger_manager/loggers/basic_logger/__init__.py +5 -0
  91. bear_utils/logger_manager/loggers/basic_logger/logger.py +80 -0
  92. bear_utils/logger_manager/loggers/basic_logger/logger.pyi +19 -0
  93. bear_utils/logger_manager/loggers/buffer_logger.py +57 -0
  94. bear_utils/logger_manager/loggers/console_logger.py +278 -0
  95. bear_utils/logger_manager/loggers/console_logger.pyi +50 -0
  96. bear_utils/logger_manager/loggers/fastapi_logger.py +333 -0
  97. bear_utils/logger_manager/loggers/file_logger.py +151 -0
  98. bear_utils/logger_manager/loggers/simple_logger.py +98 -0
  99. bear_utils/logger_manager/loggers/sub_logger.py +105 -0
  100. bear_utils/logger_manager/loggers/sub_logger.pyi +23 -0
  101. bear_utils/monitoring/__init__.py +13 -0
  102. bear_utils/monitoring/_common.py +28 -0
  103. bear_utils/monitoring/host_monitor.py +346 -0
  104. bear_utils/time/__init__.py +59 -0
  105. bear_utils-0.0.1.dist-info/METADATA +305 -0
  106. bear_utils-0.0.1.dist-info/RECORD +107 -0
  107. 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)