zrb 1.15.21__py3-none-any.whl → 1.15.22__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.
zrb/input/text_input.py CHANGED
@@ -6,6 +6,7 @@ from collections.abc import Callable
6
6
  from zrb.config.config import CFG
7
7
  from zrb.context.any_shared_context import AnySharedContext
8
8
  from zrb.input.base_input import BaseInput
9
+ from zrb.util.cli.text import edit_text
9
10
  from zrb.util.file import read_file
10
11
 
11
12
 
@@ -85,24 +86,10 @@ class TextInput(BaseInput):
85
86
  comment_prompt_message = (
86
87
  f"{self.comment_start}{prompt_message}{self.comment_end}"
87
88
  )
88
- comment_prompt_message_eol = f"{comment_prompt_message}\n"
89
89
  default_value = self.get_default_str(shared_ctx)
90
- with tempfile.NamedTemporaryFile(
91
- delete=False, suffix=self._extension
92
- ) as temp_file:
93
- temp_file_name = temp_file.name
94
- temp_file.write(comment_prompt_message_eol.encode())
95
- # Pre-fill with default content
96
- if default_value:
97
- temp_file.write(default_value.encode())
98
- temp_file.flush()
99
- subprocess.call([self.editor_cmd, temp_file_name])
100
- # Read the edited content
101
- edited_content = read_file(temp_file_name)
102
- parts = [
103
- text.strip() for text in edited_content.split(comment_prompt_message, 1)
104
- ]
105
- edited_content = "\n".join(parts).lstrip()
106
- os.remove(temp_file_name)
107
- print(f"{prompt_message}: {edited_content}")
108
- return edited_content
90
+ return edit_text(
91
+ prompt_message=comment_prompt_message,
92
+ value=default_value,
93
+ editor=self.editor_cmd,
94
+ extension=self._extension,
95
+ )
@@ -5,6 +5,7 @@ import typing
5
5
  from collections.abc import Callable
6
6
  from typing import TYPE_CHECKING, Any
7
7
 
8
+ from zrb.config.config import CFG
8
9
  from zrb.context.any_context import AnyContext
9
10
  from zrb.task.llm.error import ToolExecutionError
10
11
  from zrb.util.callable import get_callable_name
@@ -15,6 +16,7 @@ from zrb.util.cli.style import (
15
16
  stylize_green,
16
17
  stylize_yellow,
17
18
  )
19
+ from zrb.util.cli.text import edit_text
18
20
  from zrb.util.run import run_async
19
21
  from zrb.util.string.conversion import to_boolean
20
22
 
@@ -39,7 +41,6 @@ def wrap_tool(func: Callable, ctx: AnyContext, yolo_mode: bool | list[str]) -> "
39
41
  def wrap_func(func: Callable, ctx: AnyContext, yolo_mode: bool | list[str]) -> Callable:
40
42
  original_sig = inspect.signature(func)
41
43
  needs_any_context_for_injection = _has_context_parameter(original_sig, AnyContext)
42
- takes_no_args = len(original_sig.parameters) == 0
43
44
  # Pass individual flags to the wrapper creator
44
45
  wrapper = _create_wrapper(
45
46
  func=func,
@@ -48,7 +49,7 @@ def wrap_func(func: Callable, ctx: AnyContext, yolo_mode: bool | list[str]) -> C
48
49
  needs_any_context_for_injection=needs_any_context_for_injection,
49
50
  yolo_mode=yolo_mode,
50
51
  )
51
- _adjust_signature(wrapper, original_sig, takes_no_args)
52
+ _adjust_signature(wrapper, original_sig)
52
53
  return wrapper
53
54
 
54
55
 
@@ -113,7 +114,9 @@ def _create_wrapper(
113
114
  if (
114
115
  isinstance(yolo_mode, list) and func.__name__ not in yolo_mode
115
116
  ) or not yolo_mode:
116
- approval, reason = await _ask_for_approval(ctx, func, args, kwargs)
117
+ approval, reason = await _handle_user_response(
118
+ ctx, func, args, kwargs
119
+ )
117
120
  if not approval:
118
121
  raise ToolExecutionCancelled(f"User disapproving: {reason}")
119
122
  return await run_async(func(*args, **kwargs))
@@ -131,54 +134,97 @@ def _create_wrapper(
131
134
  return wrapper
132
135
 
133
136
 
134
- async def _ask_for_approval(
135
- ctx: AnyContext, func: Callable, args: list[Any], kwargs: dict[str, Any]
137
+ async def _handle_user_response(
138
+ ctx: AnyContext,
139
+ func: Callable,
140
+ args: list[Any] | tuple[Any],
141
+ kwargs: dict[str, Any],
136
142
  ) -> tuple[bool, str]:
137
- func_call_str = _get_func_call_str(func, args, kwargs)
138
- complete_confirmation_message = "\n".join(
139
- [
140
- f"\n🎰 >> {func_call_str}",
141
- _get_detail_func_param(args, kwargs),
142
- f"🎰 >> {_get_run_func_confirmation(func)}",
143
- ]
144
- )
145
143
  while True:
144
+ func_call_str = _get_func_call_str(func, args, kwargs)
145
+ complete_confirmation_message = "\n".join(
146
+ [
147
+ f"\n🎰 >> {func_call_str}",
148
+ _get_detail_func_param(args, kwargs),
149
+ f"🎰 >> {_get_run_func_confirmation(func)}",
150
+ ]
151
+ )
146
152
  ctx.print(complete_confirmation_message, plain=True)
147
- user_input = await _read_line()
153
+ user_response = await _read_line()
148
154
  ctx.print("", plain=True)
149
- user_responses = [val.strip() for val in user_input.split(",", maxsplit=1)]
150
- while len(user_responses) < 2:
151
- user_responses.append("")
152
- approval_str, reason = user_responses
153
- try:
154
- approved = True if approval_str.strip() == "" else to_boolean(approval_str)
155
- if not approved and reason == "":
156
- ctx.print(
157
- stylize_error(
158
- f"You must specify rejection reason (i.e., No, <why>) for {func_call_str}" # noqa
159
- ),
160
- plain=True,
161
- )
162
- continue
163
- return approved, reason
164
- except Exception:
155
+ new_kwargs, is_edited = _get_edited_kwargs(ctx, user_response, kwargs)
156
+ if is_edited:
157
+ kwargs = new_kwargs
158
+ continue
159
+ approval_and_reason = _get_user_approval_and_reason(
160
+ ctx, user_response, func_call_str
161
+ )
162
+ if approval_and_reason is None:
163
+ continue
164
+ return approval_and_reason
165
+
166
+
167
+ def _get_edited_kwargs(
168
+ cx: AnyContext, user_response: str, kwargs: dict[str, Any]
169
+ ) -> tuple[dict[str, Any], bool]:
170
+ user_edit_responses = [val for val in user_response.split(" ", maxsplit=2)]
171
+ if len(user_edit_responses) >= 1 and user_edit_responses[0].lower() != "edit":
172
+ return kwargs, False
173
+ while len(user_edit_responses) < 3:
174
+ user_edit_responses.append("")
175
+ key, val = user_edit_responses[1:]
176
+ if key not in kwargs:
177
+ return kwargs, True
178
+ if val != "":
179
+ kwargs[key] = val
180
+ return kwargs, True
181
+ val = edit_text(
182
+ prompt_message=f"// {key}",
183
+ value=kwargs.get(key, ""),
184
+ editor=CFG.DEFAULT_EDITOR,
185
+ )
186
+ kwargs[key] = val
187
+ return kwargs, True
188
+
189
+
190
+ def _get_user_approval_and_reason(
191
+ ctx: AnyContext, user_response: str, func_call_str: str
192
+ ) -> tuple[bool, str] | None:
193
+ user_approval_responses = [
194
+ val.strip() for val in user_response.split(",", maxsplit=1)
195
+ ]
196
+ while len(user_approval_responses) < 2:
197
+ user_approval_responses.append("")
198
+ approval_str, reason = user_approval_responses
199
+ try:
200
+ approved = True if approval_str.strip() == "" else to_boolean(approval_str)
201
+ if not approved and reason == "":
165
202
  ctx.print(
166
203
  stylize_error(
167
- f"Invalid approval value for {func_call_str}: {approval_str}"
204
+ f"You must specify rejection reason (i.e., No, <why>) for {func_call_str}" # noqa
168
205
  ),
169
206
  plain=True,
170
207
  )
171
- continue
208
+ return None
209
+ return approved, reason
210
+ except Exception:
211
+ ctx.print(
212
+ stylize_error(
213
+ f"Invalid approval value for {func_call_str}: {approval_str}"
214
+ ),
215
+ plain=True,
216
+ )
217
+ return None
172
218
 
173
219
 
174
220
  def _get_run_func_confirmation(func: Callable) -> str:
175
221
  func_name = get_callable_name(func)
176
222
  return render_markdown(
177
- f"Allow to run `{func_name}`? (✅ `Yes` | ⛔ `No, <reason>`)"
223
+ f"Allow to run `{func_name}`? (✅ `Yes` | ⛔ `No, <reason>` | ✏️ `Edit <param> <value>`)"
178
224
  ).strip()
179
225
 
180
226
 
181
- def _get_detail_func_param(args: list[Any], kwargs: dict[str, Any]) -> str:
227
+ def _get_detail_func_param(args: list[Any] | tuple[Any], kwargs: dict[str, Any]) -> str:
182
228
  markdown = "\n".join(
183
229
  [_get_func_param_item(key, val) for key, val in kwargs.items()]
184
230
  )
@@ -198,7 +244,9 @@ def _get_func_param_item(key: str, val: Any) -> str:
198
244
  return "\n".join(lines)
199
245
 
200
246
 
201
- def _get_func_call_str(func: Callable, args: list[Any], kwargs: dict[str, Any]) -> str:
247
+ def _get_func_call_str(
248
+ func: Callable, args: list[Any] | tuple[Any], kwargs: dict[str, Any]
249
+ ) -> str:
202
250
  func_name = get_callable_name(func)
203
251
  normalized_args = [stylize_green(_truncate_arg(arg)) for arg in args]
204
252
  normalized_kwargs = []
@@ -225,9 +273,7 @@ async def _read_line():
225
273
  return await reader.prompt_async()
226
274
 
227
275
 
228
- def _adjust_signature(
229
- wrapper: Callable, original_sig: inspect.Signature, takes_no_args: bool
230
- ):
276
+ def _adjust_signature(wrapper: Callable, original_sig: inspect.Signature):
231
277
  """Adjusts the wrapper function's signature for schema generation."""
232
278
  # The wrapper's signature should represent the arguments the *LLM* needs to provide.
233
279
  # The LLM does not provide RunContext (pydantic-ai injects it) or AnyContext
zrb/util/cli/text.py ADDED
@@ -0,0 +1,28 @@
1
+ import os
2
+ import subprocess
3
+ import tempfile
4
+
5
+ from zrb.util.file import read_file
6
+
7
+
8
+ def edit_text(
9
+ prompt_message: str,
10
+ value: str,
11
+ editor: str = "vi",
12
+ extension: str = ".txt",
13
+ ) -> str:
14
+ prompt_message_eol = f"{prompt_message}\n"
15
+ with tempfile.NamedTemporaryFile(delete=False, suffix=extension) as temp_file:
16
+ temp_file_name = temp_file.name
17
+ temp_file.write(prompt_message_eol.encode())
18
+ # Pre-fill with default content
19
+ if value:
20
+ temp_file.write(value.encode())
21
+ temp_file.flush()
22
+ subprocess.call([editor, temp_file_name])
23
+ # Read the edited content
24
+ edited_content = read_file(temp_file_name)
25
+ parts = [text.strip() for text in edited_content.split(prompt_message, 1)]
26
+ edited_content = "\n".join(parts).lstrip()
27
+ os.remove(temp_file_name)
28
+ return edited_content
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: zrb
3
- Version: 1.15.21
3
+ Version: 1.15.22
4
4
  Summary: Your Automation Powerhouse
5
5
  License: AGPL-3.0-or-later
6
6
  Keywords: Automation,Task Runner,Code Generator,Monorepo,Low Code
@@ -257,7 +257,7 @@ zrb/input/int_input.py,sha256=UhxCFYlZdJcgUSGGEkz301zOgRVpK0KDG_IxxWpQfMU,1457
257
257
  zrb/input/option_input.py,sha256=TQB82ko5odgzkULEizBZi0e9TIHEbIgvdP0AR3RhA74,2135
258
258
  zrb/input/password_input.py,sha256=szBojWxSP9QJecgsgA87OIYwQrY2AQ3USIKdDZY6snU,1465
259
259
  zrb/input/str_input.py,sha256=NevZHX9rf1g8eMatPyy-kUX3DglrVAQpzvVpKAzf7bA,81
260
- zrb/input/text_input.py,sha256=UCkC497V6L12cPjupOgIZ5XW2eBbBDydQi5IIYtknek,3702
260
+ zrb/input/text_input.py,sha256=NRM9FSS2pUFs7_R0KsBlu_CD8WLxbfbwxRkpaRoeCSY,3049
261
261
  zrb/runner/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
262
262
  zrb/runner/cli.py,sha256=E5GGNJjCOBpFbhgnMM_iE1TVYhxMNDJKA6WxCRETTmA,6951
263
263
  zrb/runner/common_util.py,sha256=yIJm9ivM7hvJ4Kb4Nt5RRE7oqAlt9EN89w6JDGyLkFE,1570
@@ -358,7 +358,7 @@ zrb/task/llm/history_summarization.py,sha256=UIT8bpdT3hy1xn559waDLFWZlNtIqdIpIvR
358
358
  zrb/task/llm/history_summarization_tool.py,sha256=Wazi4WMr3k1WJ1v7QgjAPbuY1JdBpHUsTWGt3DSTsLc,1706
359
359
  zrb/task/llm/print_node.py,sha256=TG8i3MrAkIj3cLkU9_fSX-u49jlTdU8t9FpHGI_VtoM,8077
360
360
  zrb/task/llm/prompt.py,sha256=FGXWYHecWtrNNkPnjg-uhnkqp7fYt8V91-AjFM_5fpA,11550
361
- zrb/task/llm/tool_wrapper.py,sha256=o_CccStYSiEI28nlvgcBEu9VcPefRE1JyPb16_cVFIg,8887
361
+ zrb/task/llm/tool_wrapper.py,sha256=v3y4FO14xStpq9K0lA3GIVv6-3dbq85I7xZqdtG-j9U,10243
362
362
  zrb/task/llm/typing.py,sha256=c8VAuPBw_4A3DxfYdydkgedaP-LU61W9_wj3m3CAX1E,58
363
363
  zrb/task/llm_task.py,sha256=OxJ9QpqjEyeOI1_zqzNZHtQlRHi0ANOvL9FYaWLzO3Y,14913
364
364
  zrb/task/make_task.py,sha256=PD3b_aYazthS8LHeJsLAhwKDEgdurQZpymJDKeN60u0,2265
@@ -376,6 +376,7 @@ zrb/util/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
376
376
  zrb/util/cli/markdown.py,sha256=Uhuw8XR-jAG9AG3oNK8VHJpYOdU40Q_8yVN74uu0RJ8,384
377
377
  zrb/util/cli/style.py,sha256=D_548KG1gXEirQGdkAVTc81vBdCeInXtnG1gV1yabBA,6655
378
378
  zrb/util/cli/subcommand.py,sha256=umTZIlrL-9g-qc_eRRgdaQgK-whvXK1roFfvnbuY7NQ,1753
379
+ zrb/util/cli/text.py,sha256=6r1NqvtjKXt-XVVURyBqYE9tZA2Bnr6u8h9Lopr-Gag,870
379
380
  zrb/util/cmd/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
380
381
  zrb/util/cmd/command.py,sha256=WpEMWVL9hBsxptvDHmRR93_cJ2zP05BJ2h9-tP93M1Y,7473
381
382
  zrb/util/cmd/remote.py,sha256=NGQq2_IrUMDoZz3qmcgtnNYVGjMHaBKQpZxImf0yfXA,1296
@@ -409,7 +410,7 @@ zrb/util/todo_model.py,sha256=hhzAX-uFl5rsg7iVX1ULlJOfBtblwQ_ieNUxBWfc-Os,1670
409
410
  zrb/util/truncate.py,sha256=eSzmjBpc1Qod3lM3M73snNbDOcARHukW_tq36dWdPvc,921
410
411
  zrb/xcom/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
411
412
  zrb/xcom/xcom.py,sha256=o79rxR9wphnShrcIushA0Qt71d_p3ZTxjNf7x9hJB78,1571
412
- zrb-1.15.21.dist-info/METADATA,sha256=RPXozcJ63UcRxsaQWJlBGjuhGUuAPwXVgJ1o3VKMS7E,9892
413
- zrb-1.15.21.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
414
- zrb-1.15.21.dist-info/entry_points.txt,sha256=-Pg3ElWPfnaSM-XvXqCxEAa-wfVI6BEgcs386s8C8v8,46
415
- zrb-1.15.21.dist-info/RECORD,,
413
+ zrb-1.15.22.dist-info/METADATA,sha256=OHahYYqF0_2Z_Ht40qggq5Z538ITZNVtaC-UagYam6o,9892
414
+ zrb-1.15.22.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
415
+ zrb-1.15.22.dist-info/entry_points.txt,sha256=-Pg3ElWPfnaSM-XvXqCxEAa-wfVI6BEgcs386s8C8v8,46
416
+ zrb-1.15.22.dist-info/RECORD,,
File without changes