zrb 1.15.21__py3-none-any.whl → 1.15.23__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
+ )
zrb/task/llm/agent.py CHANGED
@@ -199,7 +199,6 @@ async def _run_single_agent_iteration(
199
199
  ) as agent_run:
200
200
  async for node in agent_run:
201
201
  # Each node represents a step in the agent's execution
202
- # Reference: https://ai.pydantic.dev/agents/#streaming
203
202
  try:
204
203
  await print_node(
205
204
  _get_plain_printer(ctx), agent_run, node, log_indent_level
@@ -217,7 +216,7 @@ async def _run_single_agent_iteration(
217
216
 
218
217
 
219
218
  def _create_print_throttle_notif(ctx: AnyContext) -> Callable[[], None]:
220
- def _print_throttle_notif(ctx: AnyContext):
219
+ def _print_throttle_notif():
221
220
  ctx.print(stylize_faint(" ⌛>> Request Throttled"), plain=True)
222
221
 
223
222
  return _print_throttle_notif
@@ -28,7 +28,7 @@ async def print_node(
28
28
  elif Agent.is_model_request_node(node):
29
29
  # A model request node => We can stream tokens from the model's request
30
30
  print_func(_format_header("🧠 Processing...", log_indent_level))
31
- # Reference: https://ai.pydantic.dev/agents/#streaming
31
+ # Reference: https://ai.pydantic.dev/agents/#streaming-all-events-and-output
32
32
  try:
33
33
  async with node.stream(agent_run.ctx) as request_stream:
34
34
  is_streaming = False
@@ -40,9 +40,7 @@ async def print_node(
40
40
  print_func(_format_content(content, log_indent_level), end="")
41
41
  is_streaming = True
42
42
  elif isinstance(event, PartDeltaEvent):
43
- if isinstance(event.delta, TextPartDelta) or isinstance(
44
- event.delta, ThinkingPartDelta
45
- ):
43
+ if isinstance(event.delta, TextPartDelta):
46
44
  content_delta = event.delta.content_delta
47
45
  print_func(
48
46
  _format_stream_content(content_delta, log_indent_level),
@@ -78,7 +76,7 @@ async def print_node(
78
76
  print_func(
79
77
  _format_content(
80
78
  (
81
- f"⚠️ Unexpected Model Behavior: {e}. "
79
+ f"🟡 Unexpected Model Behavior: {e}. "
82
80
  f"Cause: {e.__cause__}. Node.Id: {meta}"
83
81
  ),
84
82
  log_indent_level,
@@ -112,7 +110,7 @@ async def print_node(
112
110
  print_func(
113
111
  _format_content(
114
112
  (
115
- f"⚠️ Unexpected Model Behavior: {e}. "
113
+ f"🟡 Unexpected Model Behavior: {e}. "
116
114
  f"Cause: {e.__cause__}. Node.Id: {meta}"
117
115
  ),
118
116
  log_indent_level,
@@ -123,7 +121,7 @@ async def print_node(
123
121
  print_func(_format_header("✅ Completed...", log_indent_level))
124
122
 
125
123
 
126
- def _format_header(text: str, log_indent_level: int = 0) -> str:
124
+ def _format_header(text: str | None, log_indent_level: int = 0) -> str:
127
125
  return _format(
128
126
  text,
129
127
  base_indent=2,
@@ -133,7 +131,7 @@ def _format_header(text: str, log_indent_level: int = 0) -> str:
133
131
  )
134
132
 
135
133
 
136
- def _format_content(text: str, log_indent_level: int = 0) -> str:
134
+ def _format_content(text: str | None, log_indent_level: int = 0) -> str:
137
135
  return _format(
138
136
  text,
139
137
  base_indent=2,
@@ -143,7 +141,7 @@ def _format_content(text: str, log_indent_level: int = 0) -> str:
143
141
  )
144
142
 
145
143
 
146
- def _format_stream_content(text: str, log_indent_level: int = 0) -> str:
144
+ def _format_stream_content(text: str | None, log_indent_level: int = 0) -> str:
147
145
  return _format(
148
146
  text,
149
147
  base_indent=2,
@@ -154,13 +152,15 @@ def _format_stream_content(text: str, log_indent_level: int = 0) -> str:
154
152
 
155
153
 
156
154
  def _format(
157
- text: str,
155
+ text: str | None,
158
156
  base_indent: int = 0,
159
157
  first_indent: int = 0,
160
158
  indent: int = 0,
161
159
  log_indent_level: int = 0,
162
160
  is_stream: bool = False,
163
161
  ) -> str:
162
+ if text is None:
163
+ text = ""
164
164
  line_prefix = (base_indent * (log_indent_level + 1) + indent) * " "
165
165
  processed_text = text.replace("\n", f"\n{line_prefix}")
166
166
  if is_stream:
@@ -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.23
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
@@ -29,7 +29,7 @@ Requires-Dist: pdfplumber (>=0.11.7,<0.12.0)
29
29
  Requires-Dist: playwright (>=1.54.0,<2.0.0) ; extra == "playwright" or extra == "all"
30
30
  Requires-Dist: prompt-toolkit (>=3)
31
31
  Requires-Dist: psutil (>=7.0.0,<8.0.0)
32
- Requires-Dist: pydantic-ai-slim[anthropic,bedrock,cohere,google,groq,huggingface,mistral,openai,vertexai] (>=0.8.1,<0.9.0)
32
+ Requires-Dist: pydantic-ai-slim[anthropic,bedrock,cohere,google,groq,huggingface,mistral,openai,vertexai] (>=1.0.1,<1.1.0)
33
33
  Requires-Dist: pyjwt (>=2.10.1,<3.0.0)
34
34
  Requires-Dist: python-dotenv (>=1.1.1,<2.0.0)
35
35
  Requires-Dist: python-jose[cryptography] (>=3.5.0,<4.0.0)
@@ -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
@@ -346,7 +346,7 @@ zrb/task/base_trigger.py,sha256=WSGcmBcGAZw8EzUXfmCjqJQkz8GEmi1RzogpF6A1V4s,6902
346
346
  zrb/task/cmd_task.py,sha256=myM8WZm6NrUD-Wv0Vb5sTOrutrAVZLt5LVsSBKwX6SM,10860
347
347
  zrb/task/http_check.py,sha256=Gf5rOB2Se2EdizuN9rp65HpGmfZkGc-clIAlHmPVehs,2565
348
348
  zrb/task/llm/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
349
- zrb/task/llm/agent.py,sha256=uI0X_injDrxq-IhYuZsAMdoq9AXy-gnipozGuZ72QIs,8990
349
+ zrb/task/llm/agent.py,sha256=lN0kSpPmTweWAEptt4Pq7JYX9gne0hP5Jtw3iuUlrU8,8904
350
350
  zrb/task/llm/config.py,sha256=zOPf4NpdWPuBc_R8d-kljYcOKfUAypDxiSjRDrxV66M,4059
351
351
  zrb/task/llm/conversation_history.py,sha256=oMdKUV2__mBZ4znnA-prl-gfyoleKC8Nj5KNpmLQJ4o,6764
352
352
  zrb/task/llm/conversation_history_model.py,sha256=kk-7niTl29Rm2EUIhTHzPXgZ5tp4IThMnIB3dS-1OdU,3062
@@ -356,9 +356,9 @@ zrb/task/llm/default_workflow/researching.md,sha256=KD-aYHFHir6Ti-4FsBBtGwiI0seS
356
356
  zrb/task/llm/error.py,sha256=QR-nIohS6pBpC_16cWR-fw7Mevo1sNYAiXMBsh_CJDE,4157
357
357
  zrb/task/llm/history_summarization.py,sha256=UIT8bpdT3hy1xn559waDLFWZlNtIqdIpIvRGcZEpHm0,8057
358
358
  zrb/task/llm/history_summarization_tool.py,sha256=Wazi4WMr3k1WJ1v7QgjAPbuY1JdBpHUsTWGt3DSTsLc,1706
359
- zrb/task/llm/print_node.py,sha256=TG8i3MrAkIj3cLkU9_fSX-u49jlTdU8t9FpHGI_VtoM,8077
359
+ zrb/task/llm/print_node.py,sha256=Nnf4F6eDJR4PFcOqQ1jLWBTFnzNGl1Stux2DZ3SMhsY,8062
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=ifOP-smxHcDNpZvZMZm0XkKH49BicbyGTtpHNdcIA6Q,10241
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.23.dist-info/METADATA,sha256=FHiMrHWdQFZ1pP6odLEBBlNkbYhvWe1NwXBuj668FRc,9892
414
+ zrb-1.15.23.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
415
+ zrb-1.15.23.dist-info/entry_points.txt,sha256=-Pg3ElWPfnaSM-XvXqCxEAa-wfVI6BEgcs386s8C8v8,46
416
+ zrb-1.15.23.dist-info/RECORD,,
File without changes