fabricatio 0.2.1.dev0__cp313-cp313-win_amd64.whl → 0.3.14.dev5__cp313-cp313-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.
Files changed (75) hide show
  1. fabricatio/__init__.py +12 -20
  2. fabricatio/actions/__init__.py +1 -5
  3. fabricatio/actions/article.py +319 -0
  4. fabricatio/actions/article_rag.py +416 -0
  5. fabricatio/actions/fs.py +25 -0
  6. fabricatio/actions/output.py +248 -0
  7. fabricatio/actions/rag.py +96 -0
  8. fabricatio/actions/rules.py +83 -0
  9. fabricatio/capabilities/__init__.py +1 -0
  10. fabricatio/capabilities/advanced_judge.py +20 -0
  11. fabricatio/capabilities/advanced_rag.py +61 -0
  12. fabricatio/capabilities/censor.py +105 -0
  13. fabricatio/capabilities/check.py +212 -0
  14. fabricatio/capabilities/correct.py +228 -0
  15. fabricatio/capabilities/extract.py +74 -0
  16. fabricatio/capabilities/persist.py +103 -0
  17. fabricatio/capabilities/propose.py +65 -0
  18. fabricatio/capabilities/rag.py +263 -0
  19. fabricatio/capabilities/rating.py +404 -0
  20. fabricatio/capabilities/review.py +114 -0
  21. fabricatio/capabilities/task.py +113 -0
  22. fabricatio/decorators.py +251 -179
  23. fabricatio/{core.py → emitter.py} +31 -21
  24. fabricatio/fs/__init__.py +32 -2
  25. fabricatio/fs/curd.py +32 -9
  26. fabricatio/fs/readers.py +44 -7
  27. fabricatio/journal.py +3 -19
  28. fabricatio/models/action.py +185 -61
  29. fabricatio/models/adv_kwargs_types.py +63 -0
  30. fabricatio/models/extra/__init__.py +1 -0
  31. fabricatio/models/extra/advanced_judge.py +32 -0
  32. fabricatio/models/extra/aricle_rag.py +284 -0
  33. fabricatio/models/extra/article_base.py +422 -0
  34. fabricatio/models/extra/article_essence.py +101 -0
  35. fabricatio/models/extra/article_main.py +284 -0
  36. fabricatio/models/extra/article_outline.py +46 -0
  37. fabricatio/models/extra/article_proposal.py +52 -0
  38. fabricatio/models/extra/patches.py +20 -0
  39. fabricatio/models/extra/problem.py +165 -0
  40. fabricatio/models/extra/rag.py +98 -0
  41. fabricatio/models/extra/rule.py +52 -0
  42. fabricatio/models/generic.py +704 -36
  43. fabricatio/models/kwargs_types.py +112 -17
  44. fabricatio/models/role.py +74 -27
  45. fabricatio/models/task.py +94 -60
  46. fabricatio/models/tool.py +328 -188
  47. fabricatio/models/usages.py +791 -515
  48. fabricatio/parser.py +81 -60
  49. fabricatio/rust.cp313-win_amd64.pyd +0 -0
  50. fabricatio/rust.pyi +886 -0
  51. fabricatio/toolboxes/__init__.py +1 -3
  52. fabricatio/toolboxes/fs.py +17 -1
  53. fabricatio/utils.py +156 -0
  54. fabricatio/workflows/__init__.py +1 -0
  55. fabricatio/workflows/articles.py +24 -0
  56. fabricatio/workflows/rag.py +11 -0
  57. fabricatio-0.3.14.dev5.data/scripts/tdown.exe +0 -0
  58. fabricatio-0.3.14.dev5.data/scripts/ttm.exe +0 -0
  59. fabricatio-0.3.14.dev5.dist-info/METADATA +188 -0
  60. fabricatio-0.3.14.dev5.dist-info/RECORD +64 -0
  61. {fabricatio-0.2.1.dev0.dist-info → fabricatio-0.3.14.dev5.dist-info}/WHEEL +1 -1
  62. fabricatio/_rust.cp313-win_amd64.pyd +0 -0
  63. fabricatio/_rust.pyi +0 -53
  64. fabricatio/_rust_instances.py +0 -8
  65. fabricatio/actions/communication.py +0 -15
  66. fabricatio/actions/transmission.py +0 -23
  67. fabricatio/config.py +0 -263
  68. fabricatio/models/advanced.py +0 -128
  69. fabricatio/models/events.py +0 -82
  70. fabricatio/models/utils.py +0 -78
  71. fabricatio/toolboxes/task.py +0 -6
  72. fabricatio-0.2.1.dev0.data/scripts/tdown.exe +0 -0
  73. fabricatio-0.2.1.dev0.dist-info/METADATA +0 -420
  74. fabricatio-0.2.1.dev0.dist-info/RECORD +0 -35
  75. {fabricatio-0.2.1.dev0.dist-info → fabricatio-0.3.14.dev5.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,114 @@
1
+ """A module that provides functionality to rate tasks based on a rating manual and score range."""
2
+
3
+ from abc import ABC
4
+ from typing import Dict, Optional, Set, Unpack
5
+
6
+ from fabricatio.capabilities.propose import Propose
7
+ from fabricatio.capabilities.rating import Rating
8
+ from fabricatio.models.extra.problem import Improvement
9
+ from fabricatio.models.generic import Display, WithBriefing
10
+ from fabricatio.models.kwargs_types import ReviewKwargs, ValidateKwargs
11
+ from fabricatio.models.task import Task
12
+ from fabricatio.rust import CONFIG, TEMPLATE_MANAGER
13
+ from fabricatio.utils import ok
14
+
15
+
16
+ class Review(Rating, Propose, ABC):
17
+ """Class that provides functionality to review tasks and strings using a language model.
18
+
19
+ This class extends GiveRating and Propose capabilities to analyze content,
20
+ identify problems, and suggest solutions based on specified criteria.
21
+
22
+ The review process can be applied to Task objects or plain strings with
23
+ appropriate topic and criteria.
24
+ """
25
+
26
+ async def review_task[T](self, task: Task[T], **kwargs: Unpack[ReviewKwargs]) -> Optional[Improvement]:
27
+ """Review a task using specified review criteria.
28
+
29
+ This method analyzes a task object to identify problems and propose solutions
30
+ based on the criteria provided in kwargs.
31
+
32
+ Args:
33
+ task (Task[T]): The task object to be reviewed.
34
+ **kwargs (Unpack[ReviewKwargs]): Additional keyword arguments for the review process,
35
+ including topic and optional criteria.
36
+
37
+ Returns:
38
+ Improvement[Task[T]]: A review result containing identified problems and proposed solutions,
39
+ with a reference to the original task.
40
+ """
41
+ return await self.review_obj(task, **kwargs)
42
+
43
+ async def review_string(
44
+ self,
45
+ input_text: str,
46
+ topic: str,
47
+ criteria: Optional[Set[str]] = None,
48
+ rating_manual: Optional[Dict[str, str]] = None,
49
+ **kwargs: Unpack[ValidateKwargs[Improvement]],
50
+ ) -> Optional[Improvement]:
51
+ """Review a string based on specified topic and criteria.
52
+
53
+ This method analyzes a text string to identify problems and propose solutions
54
+ based on the given topic and criteria.
55
+
56
+ Args:
57
+ input_text (str): The text content to be reviewed.
58
+ topic (str): The subject topic for the review criteria.
59
+ criteria (Optional[Set[str]], optional): A set of criteria for the review.
60
+ If not provided, criteria will be drafted automatically. Defaults to None.
61
+ rating_manual (Optional[Dict[str,str]], optional): A dictionary of rating criteria and their corresponding scores.
62
+ **kwargs (Unpack[ValidateKwargs]): Additional keyword arguments for the LLM usage.
63
+
64
+ Returns:
65
+ Improvement: A review result containing identified problems and proposed solutions,
66
+ with a reference to the original text.
67
+ """
68
+ default = None
69
+ if "default" in kwargs:
70
+ # this `default` is the default for the `propose` method
71
+ default = kwargs.pop("default")
72
+
73
+ criteria = ok(criteria or (await self.draft_rating_criteria(topic, **kwargs)), " No criteria could be use.")
74
+ manual = rating_manual or await self.draft_rating_manual(topic, criteria, **kwargs)
75
+
76
+ if default is not None:
77
+ kwargs["default"] = default
78
+ return await self.propose(
79
+ Improvement,
80
+ TEMPLATE_MANAGER.render_template(
81
+ CONFIG.templates.review_string_template,
82
+ {"text": input_text, "topic": topic, "criteria_manual": manual},
83
+ ),
84
+ **kwargs,
85
+ )
86
+
87
+ async def review_obj[M: (Display, WithBriefing)](
88
+ self, obj: M, **kwargs: Unpack[ReviewKwargs[Improvement]]
89
+ ) -> Optional[Improvement]:
90
+ """Review an object that implements Display or WithBriefing interface.
91
+
92
+ This method extracts displayable text from the object and performs a review
93
+ based on the criteria provided in kwargs.
94
+
95
+ Args:
96
+ obj (M): The object to be reviewed, which must implement either Display or WithBriefing.
97
+ **kwargs (Unpack[ReviewKwargs]): Additional keyword arguments for the review process,
98
+ including topic and optional criteria.
99
+
100
+ Raises:
101
+ TypeError: If the object does not implement Display or WithBriefing.
102
+
103
+ Returns:
104
+ Improvement: A review result containing identified problems and proposed solutions,
105
+ with a reference to the original object.
106
+ """
107
+ if isinstance(obj, Display):
108
+ text_to_review = obj.display()
109
+ elif isinstance(obj, WithBriefing):
110
+ text_to_review = obj.briefing
111
+ else:
112
+ raise TypeError(f"Unsupported type for review: {type(obj)}")
113
+
114
+ return await self.review_string(text_to_review, **kwargs)
@@ -0,0 +1,113 @@
1
+ """A module for the task capabilities of the Fabricatio library."""
2
+
3
+ from abc import ABC
4
+ from types import CodeType
5
+ from typing import Any, Dict, List, Optional, Tuple, Unpack
6
+
7
+ import ujson
8
+
9
+ from fabricatio.capabilities.propose import Propose
10
+ from fabricatio.journal import logger
11
+ from fabricatio.models.kwargs_types import ChooseKwargs, ValidateKwargs
12
+ from fabricatio.models.task import Task
13
+ from fabricatio.models.tool import Tool, ToolExecutor
14
+ from fabricatio.models.usages import ToolBoxUsage
15
+ from fabricatio.parser import JsonCapture, PythonCapture
16
+ from fabricatio.rust import CONFIG, TEMPLATE_MANAGER
17
+
18
+
19
+ class ProposeTask(Propose, ABC):
20
+ """A class that proposes a task based on a prompt."""
21
+
22
+ async def propose_task[T](
23
+ self,
24
+ prompt: str,
25
+ **kwargs: Unpack[ValidateKwargs[Task[T]]],
26
+ ) -> Optional[Task[T]]:
27
+ """Asynchronously proposes a task based on a given prompt and parameters.
28
+
29
+ Parameters:
30
+ prompt: The prompt text for proposing a task, which is a string that must be provided.
31
+ **kwargs: The keyword arguments for the LLM (Large Language Model) usage.
32
+
33
+ Returns:
34
+ A Task object based on the proposal result.
35
+ """
36
+ if not prompt:
37
+ logger.error(err := "Prompt must be provided.")
38
+ raise ValueError(err)
39
+
40
+ return await self.propose(Task, prompt, **kwargs)
41
+
42
+
43
+ class HandleTask(ToolBoxUsage,ABC):
44
+ """A class that handles a task based on a task object."""
45
+
46
+ async def draft_tool_usage_code(
47
+ self,
48
+ task: Task,
49
+ tools: List[Tool],
50
+ data: Dict[str, Any],
51
+ **kwargs: Unpack[ValidateKwargs],
52
+ ) -> Optional[Tuple[CodeType, List[str]]]:
53
+ """Asynchronously drafts the tool usage code for a task based on a given task object and tools."""
54
+ logger.info(f"Drafting tool usage code for task: {task.briefing}")
55
+
56
+ if not tools:
57
+ err = "Tools must be provided to draft the tool usage code."
58
+ logger.error(err)
59
+ raise ValueError(err)
60
+
61
+ def _validator(response: str) -> Tuple[CodeType, List[str]] | None:
62
+ if (source := PythonCapture.convert_with(response, lambda resp: compile(resp, "<string>", "exec"))) and (
63
+ to_extract := JsonCapture.convert_with(response, ujson.loads)
64
+ ):
65
+ return source, to_extract
66
+
67
+ return None
68
+
69
+ q = TEMPLATE_MANAGER.render_template(
70
+ CONFIG.templates.draft_tool_usage_code_template,
71
+ {
72
+ "data_module_name": CONFIG.toolbox.data_module_name,
73
+ "tool_module_name": CONFIG.toolbox.tool_module_name,
74
+ "task": task.briefing,
75
+ "deps": task.dependencies_prompt,
76
+ "tools": [{"name": t.name, "briefing": t.briefing} for t in tools],
77
+ "data": data,
78
+ },
79
+ )
80
+ logger.debug(f"Code Drafting Question: \n{q}")
81
+ return await self.aask_validate(
82
+ question=q,
83
+ validator=_validator,
84
+ **kwargs,
85
+ )
86
+
87
+ async def handle_fine_grind(
88
+ self,
89
+ task: Task,
90
+ data: Dict[str, Any],
91
+ box_choose_kwargs: Optional[ChooseKwargs] = None,
92
+ tool_choose_kwargs: Optional[ChooseKwargs] = None,
93
+ **kwargs: Unpack[ValidateKwargs],
94
+ ) -> Optional[Tuple]:
95
+ """Asynchronously handles a task based on a given task object and parameters."""
96
+ logger.info(f"Handling task: \n{task.briefing}")
97
+
98
+ tools = await self.gather_tools_fine_grind(task, box_choose_kwargs, tool_choose_kwargs)
99
+ logger.info(f"Gathered {[t.name for t in tools]}")
100
+
101
+ if tools and (pack := await self.draft_tool_usage_code(task, tools, data, **kwargs)):
102
+ executor = ToolExecutor(candidates=tools, data=data)
103
+
104
+ code, to_extract = pack
105
+ cxt = executor.execute(code)
106
+ if to_extract:
107
+ return tuple(cxt.get(k) for k in to_extract)
108
+
109
+ return None
110
+
111
+ async def handle(self, task: Task, data: Dict[str, Any], **kwargs: Unpack[ValidateKwargs]) -> Optional[Tuple]:
112
+ """Asynchronously handles a task based on a given task object and parameters."""
113
+ return await self.handle_fine_grind(task, data, **kwargs)
fabricatio/decorators.py CHANGED
@@ -1,179 +1,251 @@
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
1
+ """Decorators for Fabricatio."""
2
+
3
+ from asyncio import iscoroutinefunction
4
+ from functools import wraps
5
+ from importlib.util import find_spec
6
+ from inspect import signature
7
+ from shutil import which
8
+ from types import ModuleType
9
+ from typing import Callable, Coroutine, List, Optional
10
+
11
+ from fabricatio.journal import logger
12
+ from fabricatio.rust import CONFIG
13
+
14
+
15
+ def precheck_package[**P, R](package_name: str, msg: str) -> Callable[[Callable[P, R]], Callable[P, R]]:
16
+ """Check if a package exists in the current environment.
17
+
18
+ Args:
19
+ package_name (str): The name of the package to check.
20
+ msg (str): The message to display if the package is not found.
21
+
22
+ Returns:
23
+ bool: True if the package exists, False otherwise.
24
+ """
25
+
26
+ def _wrapper(
27
+ func: Callable[P, R] | Callable[P, Coroutine[None, None, R]],
28
+ ) -> Callable[P, R] | Callable[P, Coroutine[None, None, R]]:
29
+ if iscoroutinefunction(func):
30
+
31
+ @wraps(func)
32
+ async def _async_inner(*args: P.args, **kwargs: P.kwargs) -> R:
33
+ if find_spec(package_name):
34
+ return await func(*args, **kwargs)
35
+ raise RuntimeError(msg)
36
+
37
+ return _async_inner
38
+
39
+ @wraps(func)
40
+ def _inner(*args: P.args, **kwargs: P.kwargs) -> R:
41
+ if find_spec(package_name):
42
+ return func(*args, **kwargs)
43
+ raise RuntimeError(msg)
44
+
45
+ return _inner
46
+
47
+ return _wrapper
48
+
49
+
50
+ def depend_on_external_cmd[**P, R](
51
+ bin_name: str, install_tip: Optional[str], homepage: Optional[str] = None
52
+ ) -> Callable[[Callable[P, R]], Callable[P, R]]:
53
+ """Decorator to check for the presence of an external command.
54
+
55
+ Args:
56
+ bin_name (str): The name of the required binary.
57
+ install_tip (Optional[str]): Installation instructions for the required binary.
58
+ homepage (Optional[str]): The homepage of the required binary.
59
+
60
+ Returns:
61
+ Callable[[Callable[P, R]], Callable[P, R]]: A decorator that wraps the function to check for the binary.
62
+
63
+ Raises:
64
+ RuntimeError: If the required binary is not found.
65
+ """
66
+
67
+ def _decorator(func: Callable[P, R]) -> Callable[P, R]:
68
+ @wraps(func)
69
+ def _wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
70
+ if which(bin_name) is None:
71
+ err = f"`{bin_name}` is required to run {func.__name__}{signature(func)}, please install it the to `PATH` first."
72
+ if install_tip is not None:
73
+ err += f"\nInstall tip: {install_tip}"
74
+ if homepage is not None:
75
+ err += f"\nHomepage: {homepage}"
76
+ logger.error(err)
77
+ raise RuntimeError(err)
78
+ return func(*args, **kwargs)
79
+
80
+ return _wrapper
81
+
82
+ return _decorator
83
+
84
+
85
+ def logging_execution_info[**P, R](func: Callable[P, R]) -> Callable[P, R]:
86
+ """Decorator to log the execution of a function.
87
+
88
+ Args:
89
+ func (Callable): The function to be executed
90
+
91
+ Returns:
92
+ Callable: A decorator that wraps the function to log the execution.
93
+ """
94
+
95
+ @wraps(func)
96
+ def _wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
97
+ logger.info(f"Executing function: {func.__name__}{signature(func)}")
98
+ logger.debug(f"{func.__name__}{signature(func)}\nArgs: {args}\nKwargs: {kwargs}")
99
+ return func(*args, **kwargs)
100
+
101
+ return _wrapper
102
+
103
+
104
+ @precheck_package(
105
+ "questionary", "'questionary' is required to run this function. Have you installed `fabricatio[qa]`?."
106
+ )
107
+ def confirm_to_execute[**P, R](func: Callable[P, R]) -> Callable[P, Optional[R]] | Callable[P, R]:
108
+ """Decorator to confirm before executing a function.
109
+
110
+ Args:
111
+ func (Callable): The function to be executed
112
+
113
+ Returns:
114
+ Callable: A decorator that wraps the function to confirm before execution.
115
+ """
116
+ if not CONFIG.general.confirm_on_ops:
117
+ # Skip confirmation if the configuration is set to False
118
+ return func
119
+ from questionary import confirm
120
+
121
+ if iscoroutinefunction(func):
122
+
123
+ @wraps(func)
124
+ async def _wrapper(*args: P.args, **kwargs: P.kwargs) -> Optional[R]:
125
+ if await confirm(
126
+ f"Are you sure to execute function: {func.__name__}{signature(func)} \n📦 Args:{args}\n🔑 Kwargs:{kwargs}\n",
127
+ instruction="Please input [Yes/No] to proceed (default: Yes):",
128
+ ).ask_async():
129
+ return await func(*args, **kwargs)
130
+ logger.warning(f"Function: {func.__name__}{signature(func)} canceled by user.")
131
+ return None
132
+
133
+ else:
134
+
135
+ @wraps(func)
136
+ def _wrapper(*args: P.args, **kwargs: P.kwargs) -> Optional[R]:
137
+ if confirm(
138
+ f"Are you sure to execute function: {func.__name__}{signature(func)} \n📦 Args:{args}\n��� Kwargs:{kwargs}\n",
139
+ instruction="Please input [Yes/No] to proceed (default: Yes):",
140
+ ).ask():
141
+ return func(*args, **kwargs)
142
+ logger.warning(f"Function: {func.__name__}{signature(func)} canceled by user.")
143
+ return None
144
+
145
+ return _wrapper
146
+
147
+
148
+ def use_temp_module[**P, R](modules: ModuleType | List[ModuleType]) -> Callable[[Callable[P, R]], Callable[P, R]]:
149
+ """Temporarily inject modules into sys.modules during function execution.
150
+
151
+ This decorator allows you to temporarily inject one or more modules into sys.modules
152
+ while the decorated function executes. After execution, it restores the original
153
+ state of sys.modules.
154
+
155
+ Args:
156
+ modules (ModuleType | List[ModuleType]): A single module or list of modules to
157
+ temporarily inject into sys.modules.
158
+
159
+ Returns:
160
+ Callable[[Callable[P, R]], Callable[P, R]]: A decorator that handles temporary
161
+ module injection.
162
+
163
+ Examples:
164
+ ```python
165
+ from types import ModuleSpec, ModuleType, module_from_spec
166
+
167
+ # Create a temporary module
168
+ temp_module = module_from_spec(ModuleSpec("temp_math", None))
169
+ temp_module.pi = 3.14
170
+
171
+ # Use the decorator to temporarily inject the module
172
+ @use_temp_module(temp_module)
173
+ def calculate_area(radius: float) -> float:
174
+ from temp_math import pi
175
+ return pi * radius ** 2
176
+
177
+ # The temp_module is only available inside the function
178
+ result = calculate_area(5.0) # Uses temp_module.pi
179
+ ```
180
+
181
+ Multiple modules can also be injected:
182
+ ```python
183
+ module1 = module_from_spec(ModuleSpec("mod1", None))
184
+ module2 = module_from_spec(ModuleSpec("mod2", None))
185
+
186
+ @use_temp_module([module1, module2])
187
+ def process_data():
188
+ import mod1, mod2
189
+ # Work with temporary modules
190
+ ...
191
+ ```
192
+ """
193
+ module_list = [modules] if isinstance(modules, ModuleType) else modules
194
+
195
+ def _decorator(func: Callable[P, R]) -> Callable[P, R]:
196
+ @wraps(func)
197
+ def _wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
198
+ import sys
199
+
200
+ # Store original modules if they exist
201
+ for module in module_list:
202
+ if module.__name__ in sys.modules:
203
+ raise RuntimeError(
204
+ f"Module '{module.__name__}' is already present in sys.modules and cannot be overridden."
205
+ )
206
+ sys.modules[module.__name__] = module
207
+
208
+ try:
209
+ return func(*args, **kwargs)
210
+ finally:
211
+ # Restore original state
212
+ for module in module_list:
213
+ del sys.modules[module.__name__]
214
+
215
+ return _wrapper
216
+
217
+ return _decorator
218
+
219
+
220
+ def logging_exec_time[**P, R](
221
+ func: Callable[P, R] | Callable[P, Coroutine[None, None, R]],
222
+ ) -> Callable[P, R] | Callable[P, Coroutine[None, None, R]]:
223
+ """Decorator to log the execution time of a function.
224
+
225
+ Args:
226
+ func (Callable): The function to be executed
227
+
228
+ Returns:
229
+ Callable: A decorator that wraps the function to log the execution time.
230
+ """
231
+ from time import time
232
+
233
+ if iscoroutinefunction(func):
234
+
235
+ @wraps(func)
236
+ async def _async_wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
237
+ start_time = time()
238
+ result = await func(*args, **kwargs)
239
+ logger.debug(f"Execution time of `{func.__name__}`: {time() - start_time:.2f} s")
240
+ return result
241
+
242
+ return _async_wrapper
243
+
244
+ @wraps(func)
245
+ def _wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
246
+ start_time = time()
247
+ result = func(*args, **kwargs)
248
+ logger.debug(f"Execution time of {func.__name__}: {(time() - start_time) * 1000:.2f} ms")
249
+ return result
250
+
251
+ return _wrapper