fabricatio 0.2.6__cp39-cp39-win_amd64.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.
- fabricatio/__init__.py +43 -0
- fabricatio/_rust.cp39-win_amd64.pyd +0 -0
- fabricatio/_rust.pyi +115 -0
- fabricatio/_rust_instances.py +10 -0
- fabricatio/actions/article.py +128 -0
- fabricatio/actions/output.py +19 -0
- fabricatio/actions/rag.py +71 -0
- fabricatio/capabilities/correct.py +115 -0
- fabricatio/capabilities/propose.py +49 -0
- fabricatio/capabilities/rag.py +384 -0
- fabricatio/capabilities/rating.py +339 -0
- fabricatio/capabilities/review.py +278 -0
- fabricatio/capabilities/task.py +113 -0
- fabricatio/config.py +405 -0
- fabricatio/core.py +181 -0
- fabricatio/decorators.py +179 -0
- fabricatio/fs/__init__.py +29 -0
- fabricatio/fs/curd.py +149 -0
- fabricatio/fs/readers.py +46 -0
- fabricatio/journal.py +21 -0
- fabricatio/models/action.py +230 -0
- fabricatio/models/events.py +120 -0
- fabricatio/models/extra.py +655 -0
- fabricatio/models/generic.py +406 -0
- fabricatio/models/kwargs_types.py +169 -0
- fabricatio/models/role.py +72 -0
- fabricatio/models/task.py +299 -0
- fabricatio/models/tool.py +189 -0
- fabricatio/models/usages.py +718 -0
- fabricatio/models/utils.py +192 -0
- fabricatio/parser.py +151 -0
- fabricatio/py.typed +0 -0
- fabricatio/toolboxes/__init__.py +15 -0
- fabricatio/toolboxes/arithmetic.py +62 -0
- fabricatio/toolboxes/fs.py +31 -0
- fabricatio/workflows/articles.py +26 -0
- fabricatio/workflows/rag.py +11 -0
- fabricatio-0.2.6.data/scripts/tdown.exe +0 -0
- fabricatio-0.2.6.dist-info/METADATA +432 -0
- fabricatio-0.2.6.dist-info/RECORD +42 -0
- fabricatio-0.2.6.dist-info/WHEEL +4 -0
- fabricatio-0.2.6.dist-info/licenses/LICENSE +21 -0
fabricatio/core.py
ADDED
@@ -0,0 +1,181 @@
|
|
1
|
+
"""Core module that contains the Env class for managing event handling."""
|
2
|
+
|
3
|
+
from typing import Callable, Optional, Self, overload
|
4
|
+
|
5
|
+
from pydantic import BaseModel, ConfigDict, PrivateAttr
|
6
|
+
from pymitter import EventEmitter
|
7
|
+
|
8
|
+
from fabricatio.config import configs
|
9
|
+
from fabricatio.models.events import Event
|
10
|
+
|
11
|
+
|
12
|
+
class Env(BaseModel):
|
13
|
+
"""Environment class that manages event handling using EventEmitter."""
|
14
|
+
|
15
|
+
model_config = ConfigDict(use_attribute_docstrings=True)
|
16
|
+
_ee: EventEmitter = PrivateAttr(
|
17
|
+
default_factory=lambda: EventEmitter(
|
18
|
+
delimiter=configs.pymitter.delimiter,
|
19
|
+
new_listener=configs.pymitter.new_listener_event,
|
20
|
+
max_listeners=configs.pymitter.max_listeners,
|
21
|
+
wildcard=True,
|
22
|
+
)
|
23
|
+
)
|
24
|
+
|
25
|
+
@overload
|
26
|
+
def on(self, event: str | Event, /, ttl: int = -1) -> Self:
|
27
|
+
"""
|
28
|
+
Registers an event listener that listens indefinitely or for a specified number of times.
|
29
|
+
|
30
|
+
Args:
|
31
|
+
event (str | Event): The event to listen for.
|
32
|
+
ttl (int): Time-to-live for the listener. If -1, the listener will listen indefinitely.
|
33
|
+
|
34
|
+
Returns:
|
35
|
+
Self: The current instance of Env.
|
36
|
+
"""
|
37
|
+
...
|
38
|
+
|
39
|
+
@overload
|
40
|
+
def on[**P, R](
|
41
|
+
self,
|
42
|
+
event: str | Event,
|
43
|
+
func: Optional[Callable[P, R]] = None,
|
44
|
+
/,
|
45
|
+
ttl: int = -1,
|
46
|
+
) -> Callable[[Callable[P, R]], Callable[P, R]]:
|
47
|
+
"""
|
48
|
+
Registers an event listener with a specific function that listens indefinitely or for a specified number of times.
|
49
|
+
|
50
|
+
Args:
|
51
|
+
event (str | Event): The event to listen for.
|
52
|
+
func (Callable[P, R]): The function to be called when the event is emitted.
|
53
|
+
ttl (int): Time-to-live for the listener. If -1, the listener will listen indefinitely.
|
54
|
+
|
55
|
+
Returns:
|
56
|
+
Callable[[Callable[P, R]], Callable[P, R]]: A decorator that registers the function as an event listener.
|
57
|
+
"""
|
58
|
+
...
|
59
|
+
|
60
|
+
def on[**P, R](
|
61
|
+
self,
|
62
|
+
event: str | Event,
|
63
|
+
func: Optional[Callable[P, R]] = None,
|
64
|
+
/,
|
65
|
+
ttl=-1,
|
66
|
+
) -> Callable[[Callable[P, R]], Callable[P, R]] | Self:
|
67
|
+
"""Registers an event listener with a specific function that listens indefinitely or for a specified number of times.
|
68
|
+
|
69
|
+
Args:
|
70
|
+
event (str | Event): The event to listen for.
|
71
|
+
func (Callable[P, R]): The function to be called when the event is emitted.
|
72
|
+
ttl (int): Time-to-live for the listener. If -1, the listener will listen indefinitely.
|
73
|
+
|
74
|
+
Returns:
|
75
|
+
Callable[[Callable[P, R]], Callable[P, R]] | Self: A decorator that registers the function as an event listener or the current instance of Env.
|
76
|
+
"""
|
77
|
+
if isinstance(event, Event):
|
78
|
+
event = event.collapse()
|
79
|
+
if func is None:
|
80
|
+
return self._ee.on(event, ttl=ttl)
|
81
|
+
self._ee.on(event, func, ttl=ttl)
|
82
|
+
return self
|
83
|
+
|
84
|
+
@overload
|
85
|
+
def once[**P, R](
|
86
|
+
self,
|
87
|
+
event: str | Event,
|
88
|
+
) -> Callable[[Callable[P, R]], Callable[P, R]]:
|
89
|
+
"""
|
90
|
+
Registers an event listener that listens only once.
|
91
|
+
|
92
|
+
Args:
|
93
|
+
event (str | Event): The event to listen for.
|
94
|
+
|
95
|
+
Returns:
|
96
|
+
Callable[[Callable[P, R]], Callable[P, R]]: A decorator that registers the function as an event listener.
|
97
|
+
"""
|
98
|
+
...
|
99
|
+
|
100
|
+
@overload
|
101
|
+
def once[**P, R](
|
102
|
+
self,
|
103
|
+
event: str | Event,
|
104
|
+
func: Callable[[Callable[P, R]], Callable[P, R]],
|
105
|
+
) -> Self:
|
106
|
+
"""
|
107
|
+
Registers an event listener with a specific function that listens only once.
|
108
|
+
|
109
|
+
Args:
|
110
|
+
event (str | Event): The event to listen for.
|
111
|
+
func (Callable[P, R]): The function to be called when the event is emitted.
|
112
|
+
|
113
|
+
Returns:
|
114
|
+
Self: The current instance of Env.
|
115
|
+
"""
|
116
|
+
...
|
117
|
+
|
118
|
+
def once[**P, R](
|
119
|
+
self,
|
120
|
+
event: str | Event,
|
121
|
+
func: Optional[Callable[P, R]] = None,
|
122
|
+
) -> Callable[[Callable[P, R]], Callable[P, R]] | Self:
|
123
|
+
"""Registers an event listener with a specific function that listens only once.
|
124
|
+
|
125
|
+
Args:
|
126
|
+
event (str | Event): The event to listen for.
|
127
|
+
func (Callable[P, R]): The function to be called when the event is emitted.
|
128
|
+
|
129
|
+
Returns:
|
130
|
+
Callable[[Callable[P, R]], Callable[P, R]] | Self: A decorator that registers the function as an event listener or the current instance
|
131
|
+
"""
|
132
|
+
if isinstance(event, Event):
|
133
|
+
event = event.collapse()
|
134
|
+
if func is None:
|
135
|
+
return self._ee.once(event)
|
136
|
+
|
137
|
+
self._ee.once(event, func)
|
138
|
+
return self
|
139
|
+
|
140
|
+
def emit[**P](self, event: str | Event, *args: P.args, **kwargs: P.kwargs) -> None:
|
141
|
+
"""Emits an event to all registered listeners.
|
142
|
+
|
143
|
+
Args:
|
144
|
+
event (str | Event): The event to emit.
|
145
|
+
*args: Positional arguments to pass to the listeners.
|
146
|
+
**kwargs: Keyword arguments to pass to the listeners.
|
147
|
+
"""
|
148
|
+
if isinstance(event, Event):
|
149
|
+
event = event.collapse()
|
150
|
+
|
151
|
+
self._ee.emit(event, *args, **kwargs)
|
152
|
+
|
153
|
+
async def emit_async[**P](self, event: str | Event, *args: P.args, **kwargs: P.kwargs) -> None:
|
154
|
+
"""Asynchronously emits an event to all registered listeners.
|
155
|
+
|
156
|
+
Args:
|
157
|
+
event (str | Event): The event to emit.
|
158
|
+
*args: Positional arguments to pass to the listeners.
|
159
|
+
**kwargs: Keyword arguments to pass to the listeners.
|
160
|
+
"""
|
161
|
+
if isinstance(event, Event):
|
162
|
+
event = event.collapse()
|
163
|
+
return await self._ee.emit_async(event, *args, **kwargs)
|
164
|
+
|
165
|
+
def emit_future[**P](self, event: str | Event, *args: P.args, **kwargs: P.kwargs) -> None:
|
166
|
+
"""Emits an event to all registered listeners and returns a future object.
|
167
|
+
|
168
|
+
Args:
|
169
|
+
event (str | Event): The event to emit.
|
170
|
+
*args: Positional arguments to pass to the listeners.
|
171
|
+
**kwargs: Keyword arguments to pass to the listeners.
|
172
|
+
|
173
|
+
Returns:
|
174
|
+
None: The future object.
|
175
|
+
"""
|
176
|
+
if isinstance(event, Event):
|
177
|
+
event = event.collapse()
|
178
|
+
return self._ee.emit_future(event, *args, **kwargs)
|
179
|
+
|
180
|
+
|
181
|
+
env = Env()
|
fabricatio/decorators.py
ADDED
@@ -0,0 +1,179 @@
|
|
1
|
+
"""Decorators for Fabricatio."""
|
2
|
+
|
3
|
+
from asyncio import iscoroutinefunction
|
4
|
+
from functools import wraps
|
5
|
+
from inspect import signature
|
6
|
+
from shutil import which
|
7
|
+
from types import ModuleType
|
8
|
+
from typing import Callable, List, Optional
|
9
|
+
|
10
|
+
from questionary import confirm
|
11
|
+
|
12
|
+
from fabricatio.config import configs
|
13
|
+
from fabricatio.journal import logger
|
14
|
+
|
15
|
+
|
16
|
+
def depend_on_external_cmd[**P, R](
|
17
|
+
bin_name: str, install_tip: Optional[str], homepage: Optional[str] = None
|
18
|
+
) -> Callable[[Callable[P, R]], Callable[P, R]]:
|
19
|
+
"""Decorator to check for the presence of an external command.
|
20
|
+
|
21
|
+
Args:
|
22
|
+
bin_name (str): The name of the required binary.
|
23
|
+
install_tip (Optional[str]): Installation instructions for the required binary.
|
24
|
+
homepage (Optional[str]): The homepage of the required binary.
|
25
|
+
|
26
|
+
Returns:
|
27
|
+
Callable[[Callable[P, R]], Callable[P, R]]: A decorator that wraps the function to check for the binary.
|
28
|
+
|
29
|
+
Raises:
|
30
|
+
RuntimeError: If the required binary is not found.
|
31
|
+
"""
|
32
|
+
|
33
|
+
def _decorator(func: Callable[P, R]) -> Callable[P, R]:
|
34
|
+
@wraps(func)
|
35
|
+
def _wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
|
36
|
+
if which(bin_name) is None:
|
37
|
+
err = f"`{bin_name}` is required to run {func.__name__}{signature(func)}, please install it the to `PATH` first."
|
38
|
+
if install_tip is not None:
|
39
|
+
err += f"\nInstall tip: {install_tip}"
|
40
|
+
if homepage is not None:
|
41
|
+
err += f"\nHomepage: {homepage}"
|
42
|
+
logger.error(err)
|
43
|
+
raise RuntimeError(err)
|
44
|
+
return func(*args, **kwargs)
|
45
|
+
|
46
|
+
return _wrapper
|
47
|
+
|
48
|
+
return _decorator
|
49
|
+
|
50
|
+
|
51
|
+
def logging_execution_info[**P, R](func: Callable[P, R]) -> Callable[P, R]:
|
52
|
+
"""Decorator to log the execution of a function.
|
53
|
+
|
54
|
+
Args:
|
55
|
+
func (Callable): The function to be executed
|
56
|
+
|
57
|
+
Returns:
|
58
|
+
Callable: A decorator that wraps the function to log the execution.
|
59
|
+
"""
|
60
|
+
|
61
|
+
@wraps(func)
|
62
|
+
def _wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
|
63
|
+
logger.info(f"Executing function: {func.__name__}{signature(func)}")
|
64
|
+
logger.debug(f"{func.__name__}{signature(func)}\nArgs: {args}\nKwargs: {kwargs}")
|
65
|
+
return func(*args, **kwargs)
|
66
|
+
|
67
|
+
return _wrapper
|
68
|
+
|
69
|
+
|
70
|
+
def confirm_to_execute[**P, R](func: Callable[P, R]) -> Callable[P, Optional[R]] | Callable[P, R]:
|
71
|
+
"""Decorator to confirm before executing a function.
|
72
|
+
|
73
|
+
Args:
|
74
|
+
func (Callable): The function to be executed
|
75
|
+
|
76
|
+
Returns:
|
77
|
+
Callable: A decorator that wraps the function to confirm before execution.
|
78
|
+
"""
|
79
|
+
if not configs.general.confirm_on_ops:
|
80
|
+
# Skip confirmation if the configuration is set to False
|
81
|
+
return func
|
82
|
+
|
83
|
+
if iscoroutinefunction(func):
|
84
|
+
|
85
|
+
@wraps(func)
|
86
|
+
async def _wrapper(*args: P.args, **kwargs: P.kwargs) -> Optional[R]:
|
87
|
+
if await confirm(
|
88
|
+
f"Are you sure to execute function: {func.__name__}{signature(func)} \n📦 Args:{args}\n🔑 Kwargs:{kwargs}\n",
|
89
|
+
instruction="Please input [Yes/No] to proceed (default: Yes):",
|
90
|
+
).ask_async():
|
91
|
+
return await func(*args, **kwargs)
|
92
|
+
logger.warning(f"Function: {func.__name__}{signature(func)} canceled by user.")
|
93
|
+
return None
|
94
|
+
|
95
|
+
else:
|
96
|
+
|
97
|
+
@wraps(func)
|
98
|
+
def _wrapper(*args: P.args, **kwargs: P.kwargs) -> Optional[R]:
|
99
|
+
if confirm(
|
100
|
+
f"Are you sure to execute function: {func.__name__}{signature(func)} \n📦 Args:{args}\n��� Kwargs:{kwargs}\n",
|
101
|
+
instruction="Please input [Yes/No] to proceed (default: Yes):",
|
102
|
+
).ask():
|
103
|
+
return func(*args, **kwargs)
|
104
|
+
logger.warning(f"Function: {func.__name__}{signature(func)} canceled by user.")
|
105
|
+
return None
|
106
|
+
|
107
|
+
return _wrapper
|
108
|
+
|
109
|
+
|
110
|
+
def use_temp_module[**P, R](modules: ModuleType | List[ModuleType]) -> Callable[[Callable[P, R]], Callable[P, R]]:
|
111
|
+
"""Temporarily inject modules into sys.modules during function execution.
|
112
|
+
|
113
|
+
This decorator allows you to temporarily inject one or more modules into sys.modules
|
114
|
+
while the decorated function executes. After execution, it restores the original
|
115
|
+
state of sys.modules.
|
116
|
+
|
117
|
+
Args:
|
118
|
+
modules (ModuleType | List[ModuleType]): A single module or list of modules to
|
119
|
+
temporarily inject into sys.modules.
|
120
|
+
|
121
|
+
Returns:
|
122
|
+
Callable[[Callable[P, R]], Callable[P, R]]: A decorator that handles temporary
|
123
|
+
module injection.
|
124
|
+
|
125
|
+
Examples:
|
126
|
+
```python
|
127
|
+
from types import ModuleSpec, ModuleType, module_from_spec
|
128
|
+
|
129
|
+
# Create a temporary module
|
130
|
+
temp_module = module_from_spec(ModuleSpec("temp_math", None))
|
131
|
+
temp_module.pi = 3.14
|
132
|
+
|
133
|
+
# Use the decorator to temporarily inject the module
|
134
|
+
@use_temp_module(temp_module)
|
135
|
+
def calculate_area(radius: float) -> float:
|
136
|
+
from temp_math import pi
|
137
|
+
return pi * radius ** 2
|
138
|
+
|
139
|
+
# The temp_module is only available inside the function
|
140
|
+
result = calculate_area(5.0) # Uses temp_module.pi
|
141
|
+
```
|
142
|
+
|
143
|
+
Multiple modules can also be injected:
|
144
|
+
```python
|
145
|
+
module1 = module_from_spec(ModuleSpec("mod1", None))
|
146
|
+
module2 = module_from_spec(ModuleSpec("mod2", None))
|
147
|
+
|
148
|
+
@use_temp_module([module1, module2])
|
149
|
+
def process_data():
|
150
|
+
import mod1, mod2
|
151
|
+
# Work with temporary modules
|
152
|
+
...
|
153
|
+
```
|
154
|
+
"""
|
155
|
+
module_list = [modules] if isinstance(modules, ModuleType) else modules
|
156
|
+
|
157
|
+
def _decorator(func: Callable[P, R]) -> Callable[P, R]:
|
158
|
+
@wraps(func)
|
159
|
+
def _wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
|
160
|
+
import sys
|
161
|
+
|
162
|
+
# Store original modules if they exist
|
163
|
+
for module in module_list:
|
164
|
+
if module.__name__ in sys.modules:
|
165
|
+
raise RuntimeError(
|
166
|
+
f"Module '{module.__name__}' is already present in sys.modules and cannot be overridden."
|
167
|
+
)
|
168
|
+
sys.modules[module.__name__] = module
|
169
|
+
|
170
|
+
try:
|
171
|
+
return func(*args, **kwargs)
|
172
|
+
finally:
|
173
|
+
# Restore original state
|
174
|
+
for module in module_list:
|
175
|
+
del sys.modules[module.__name__]
|
176
|
+
|
177
|
+
return _wrapper
|
178
|
+
|
179
|
+
return _decorator
|
@@ -0,0 +1,29 @@
|
|
1
|
+
"""FileSystem manipulation module for Fabricatio."""
|
2
|
+
|
3
|
+
from fabricatio.fs.curd import (
|
4
|
+
absolute_path,
|
5
|
+
copy_file,
|
6
|
+
create_directory,
|
7
|
+
delete_directory,
|
8
|
+
delete_file,
|
9
|
+
dump_text,
|
10
|
+
gather_files,
|
11
|
+
move_file,
|
12
|
+
tree,
|
13
|
+
)
|
14
|
+
from fabricatio.fs.readers import MAGIKA, safe_json_read, safe_text_read
|
15
|
+
|
16
|
+
__all__ = [
|
17
|
+
"MAGIKA",
|
18
|
+
"absolute_path",
|
19
|
+
"copy_file",
|
20
|
+
"create_directory",
|
21
|
+
"delete_directory",
|
22
|
+
"delete_file",
|
23
|
+
"dump_text",
|
24
|
+
"gather_files",
|
25
|
+
"move_file",
|
26
|
+
"safe_json_read",
|
27
|
+
"safe_text_read",
|
28
|
+
"tree",
|
29
|
+
]
|
fabricatio/fs/curd.py
ADDED
@@ -0,0 +1,149 @@
|
|
1
|
+
"""File system create, update, read, delete operations."""
|
2
|
+
|
3
|
+
import shutil
|
4
|
+
import subprocess
|
5
|
+
from os import PathLike
|
6
|
+
from pathlib import Path
|
7
|
+
from typing import Union
|
8
|
+
|
9
|
+
from fabricatio.decorators import depend_on_external_cmd
|
10
|
+
from fabricatio.journal import logger
|
11
|
+
|
12
|
+
|
13
|
+
def dump_text(path: Union[str, Path], text: str) -> None:
|
14
|
+
"""Dump text to a file. you need to make sure the file's parent directory exists.
|
15
|
+
|
16
|
+
Args:
|
17
|
+
path(str, Path): Path to the file
|
18
|
+
text(str): Text to write to the file
|
19
|
+
|
20
|
+
Returns:
|
21
|
+
None
|
22
|
+
"""
|
23
|
+
Path(path).write_text(text, encoding="utf-8", errors="ignore")
|
24
|
+
|
25
|
+
|
26
|
+
def copy_file(src: Union[str, Path], dst: Union[str, Path]) -> None:
|
27
|
+
"""Copy a file from source to destination.
|
28
|
+
|
29
|
+
Args:
|
30
|
+
src: Source file path
|
31
|
+
dst: Destination file path
|
32
|
+
|
33
|
+
Raises:
|
34
|
+
FileNotFoundError: If source file doesn't exist
|
35
|
+
shutil.SameFileError: If source and destination are the same
|
36
|
+
"""
|
37
|
+
try:
|
38
|
+
shutil.copy(src, dst)
|
39
|
+
logger.info(f"Copied file from {src} to {dst}")
|
40
|
+
except OSError as e:
|
41
|
+
logger.error(f"Failed to copy file from {src} to {dst}: {e!s}")
|
42
|
+
raise
|
43
|
+
|
44
|
+
|
45
|
+
def move_file(src: Union[str, Path], dst: Union[str, Path]) -> None:
|
46
|
+
"""Move a file from source to destination.
|
47
|
+
|
48
|
+
Args:
|
49
|
+
src: Source file path
|
50
|
+
dst: Destination file path
|
51
|
+
|
52
|
+
Raises:
|
53
|
+
FileNotFoundError: If source file doesn't exist
|
54
|
+
shutil.SameFileError: If source and destination are the same
|
55
|
+
"""
|
56
|
+
try:
|
57
|
+
shutil.move(src, dst)
|
58
|
+
logger.info(f"Moved file from {src} to {dst}")
|
59
|
+
except OSError as e:
|
60
|
+
logger.error(f"Failed to move file from {src} to {dst}: {e!s}")
|
61
|
+
raise
|
62
|
+
|
63
|
+
|
64
|
+
def delete_file(file_path: Union[str, Path]) -> None:
|
65
|
+
"""Delete a file.
|
66
|
+
|
67
|
+
Args:
|
68
|
+
file_path: Path to the file to be deleted
|
69
|
+
|
70
|
+
Raises:
|
71
|
+
FileNotFoundError: If file doesn't exist
|
72
|
+
PermissionError: If no permission to delete the file
|
73
|
+
"""
|
74
|
+
try:
|
75
|
+
Path(file_path).unlink()
|
76
|
+
logger.info(f"Deleted file: {file_path}")
|
77
|
+
except OSError as e:
|
78
|
+
logger.error(f"Failed to delete file {file_path}: {e!s}")
|
79
|
+
raise
|
80
|
+
|
81
|
+
|
82
|
+
def create_directory(dir_path: Union[str, Path], parents: bool = True, exist_ok: bool = True) -> None:
|
83
|
+
"""Create a directory.
|
84
|
+
|
85
|
+
Args:
|
86
|
+
dir_path: Path to the directory to create
|
87
|
+
parents: Create parent directories if they don't exist
|
88
|
+
exist_ok: Don't raise error if directory already exists
|
89
|
+
"""
|
90
|
+
try:
|
91
|
+
Path(dir_path).mkdir(parents=parents, exist_ok=exist_ok)
|
92
|
+
logger.info(f"Created directory: {dir_path}")
|
93
|
+
except OSError as e:
|
94
|
+
logger.error(f"Failed to create directory {dir_path}: {e!s}")
|
95
|
+
raise
|
96
|
+
|
97
|
+
|
98
|
+
@depend_on_external_cmd(
|
99
|
+
"erd",
|
100
|
+
"Please install `erd` using `cargo install erdtree` or `scoop install erdtree`.",
|
101
|
+
"https://github.com/solidiquis/erdtree",
|
102
|
+
)
|
103
|
+
def tree(dir_path: Union[str, Path]) -> str:
|
104
|
+
"""Generate a tree representation of the directory structure. Requires `erd` to be installed."""
|
105
|
+
dir_path = Path(dir_path)
|
106
|
+
return subprocess.check_output(("erd", dir_path.as_posix()), encoding="utf-8") # noqa: S603
|
107
|
+
|
108
|
+
|
109
|
+
def delete_directory(dir_path: Union[str, Path]) -> None:
|
110
|
+
"""Delete a directory and its contents.
|
111
|
+
|
112
|
+
Args:
|
113
|
+
dir_path: Path to the directory to delete
|
114
|
+
|
115
|
+
Raises:
|
116
|
+
FileNotFoundError: If directory doesn't exist
|
117
|
+
OSError: If directory is not empty and can't be removed
|
118
|
+
"""
|
119
|
+
try:
|
120
|
+
shutil.rmtree(dir_path)
|
121
|
+
logger.info(f"Deleted directory: {dir_path}")
|
122
|
+
except OSError as e:
|
123
|
+
logger.error(f"Failed to delete directory {dir_path}: {e!s}")
|
124
|
+
raise
|
125
|
+
|
126
|
+
|
127
|
+
def absolute_path(path: str | Path | PathLike) -> str:
|
128
|
+
"""Get the absolute path of a file or directory.
|
129
|
+
|
130
|
+
Args:
|
131
|
+
path (str, Path, PathLike): The path to the file or directory.
|
132
|
+
|
133
|
+
Returns:
|
134
|
+
str: The absolute path of the file or directory.
|
135
|
+
"""
|
136
|
+
return Path(path).expanduser().resolve().as_posix()
|
137
|
+
|
138
|
+
|
139
|
+
def gather_files(directory: str | Path | PathLike, extension: str) -> list[str]:
|
140
|
+
"""Gather all files with a specific extension in a directory.
|
141
|
+
|
142
|
+
Args:
|
143
|
+
directory (str, Path, PathLike): The directory to search in.
|
144
|
+
extension (str): The file extension to look for.
|
145
|
+
|
146
|
+
Returns:
|
147
|
+
list[str]: A list of file paths with the specified extension.
|
148
|
+
"""
|
149
|
+
return [file.as_posix() for file in Path(directory).rglob(f"*.{extension}")]
|
fabricatio/fs/readers.py
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
"""Filesystem readers for Fabricatio."""
|
2
|
+
|
3
|
+
from pathlib import Path
|
4
|
+
from typing import Dict
|
5
|
+
|
6
|
+
import orjson
|
7
|
+
from magika import Magika
|
8
|
+
|
9
|
+
from fabricatio.config import configs
|
10
|
+
from fabricatio.journal import logger
|
11
|
+
|
12
|
+
MAGIKA = Magika(model_dir=configs.magika.model_dir)
|
13
|
+
|
14
|
+
|
15
|
+
def safe_text_read(path: Path | str) -> str:
|
16
|
+
"""Safely read the text from a file.
|
17
|
+
|
18
|
+
Args:
|
19
|
+
path (Path|str): The path to the file.
|
20
|
+
|
21
|
+
Returns:
|
22
|
+
str: The text from the file.
|
23
|
+
"""
|
24
|
+
path = Path(path)
|
25
|
+
try:
|
26
|
+
return path.read_text(encoding="utf-8")
|
27
|
+
except (UnicodeDecodeError, IsADirectoryError, FileNotFoundError) as e:
|
28
|
+
logger.error(f"Failed to read file {path}: {e!s}")
|
29
|
+
return ""
|
30
|
+
|
31
|
+
|
32
|
+
def safe_json_read(path: Path | str) -> Dict:
|
33
|
+
"""Safely read the JSON from a file.
|
34
|
+
|
35
|
+
Args:
|
36
|
+
path (Path|str): The path to the file.
|
37
|
+
|
38
|
+
Returns:
|
39
|
+
dict: The JSON from the file.
|
40
|
+
"""
|
41
|
+
path = Path(path)
|
42
|
+
try:
|
43
|
+
return orjson.loads(path.read_text(encoding="utf-8"))
|
44
|
+
except (orjson.JSONDecodeError, IsADirectoryError, FileNotFoundError) as e:
|
45
|
+
logger.error(f"Failed to read file {path}: {e!s}")
|
46
|
+
return {}
|
fabricatio/journal.py
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
"""Logging setup for the project."""
|
2
|
+
|
3
|
+
import sys
|
4
|
+
|
5
|
+
from loguru import logger
|
6
|
+
from rich import pretty, traceback
|
7
|
+
|
8
|
+
from fabricatio.config import configs
|
9
|
+
|
10
|
+
pretty.install()
|
11
|
+
traceback.install()
|
12
|
+
logger.remove()
|
13
|
+
logger.add(
|
14
|
+
configs.debug.log_file,
|
15
|
+
level=configs.debug.log_level,
|
16
|
+
rotation=f"{configs.debug.rotation} weeks",
|
17
|
+
retention=f"{configs.debug.retention} weeks",
|
18
|
+
)
|
19
|
+
logger.add(sys.stderr, level=configs.debug.log_level)
|
20
|
+
|
21
|
+
__all__ = ["logger"]
|