falyx 0.1.47__tar.gz → 0.1.49__tar.gz

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 (61) hide show
  1. {falyx-0.1.47 → falyx-0.1.49}/PKG-INFO +1 -1
  2. {falyx-0.1.47 → falyx-0.1.49}/falyx/action/base.py +1 -1
  3. {falyx-0.1.47 → falyx-0.1.49}/falyx/action/menu_action.py +4 -1
  4. {falyx-0.1.47 → falyx-0.1.49}/falyx/action/prompt_menu_action.py +4 -1
  5. {falyx-0.1.47 → falyx-0.1.49}/falyx/action/select_file_action.py +4 -1
  6. {falyx-0.1.47 → falyx-0.1.49}/falyx/action/selection_action.py +4 -1
  7. {falyx-0.1.47 → falyx-0.1.49}/falyx/action/user_input_action.py +4 -1
  8. {falyx-0.1.47 → falyx-0.1.49}/falyx/bottom_bar.py +1 -1
  9. {falyx-0.1.47 → falyx-0.1.49}/falyx/command.py +1 -1
  10. {falyx-0.1.47 → falyx-0.1.49}/falyx/config.py +1 -2
  11. {falyx-0.1.47 → falyx-0.1.49}/falyx/context.py +3 -1
  12. {falyx-0.1.47 → falyx-0.1.49}/falyx/execution_registry.py +93 -18
  13. {falyx-0.1.47 → falyx-0.1.49}/falyx/falyx.py +39 -5
  14. {falyx-0.1.47 → falyx-0.1.49}/falyx/init.py +1 -1
  15. {falyx-0.1.47 → falyx-0.1.49}/falyx/parsers/argparse.py +25 -25
  16. {falyx-0.1.47 → falyx-0.1.49}/falyx/parsers/signature.py +10 -5
  17. {falyx-0.1.47 → falyx-0.1.49}/falyx/selection.py +5 -5
  18. falyx-0.1.49/falyx/version.py +1 -0
  19. {falyx-0.1.47 → falyx-0.1.49}/pyproject.toml +1 -1
  20. falyx-0.1.47/falyx/version.py +0 -1
  21. {falyx-0.1.47 → falyx-0.1.49}/LICENSE +0 -0
  22. {falyx-0.1.47 → falyx-0.1.49}/README.md +0 -0
  23. {falyx-0.1.47 → falyx-0.1.49}/falyx/.pytyped +0 -0
  24. {falyx-0.1.47 → falyx-0.1.49}/falyx/__init__.py +0 -0
  25. {falyx-0.1.47 → falyx-0.1.49}/falyx/__main__.py +0 -0
  26. {falyx-0.1.47 → falyx-0.1.49}/falyx/action/.pytyped +0 -0
  27. {falyx-0.1.47 → falyx-0.1.49}/falyx/action/__init__.py +0 -0
  28. {falyx-0.1.47 → falyx-0.1.49}/falyx/action/action.py +0 -0
  29. {falyx-0.1.47 → falyx-0.1.49}/falyx/action/action_factory.py +0 -0
  30. {falyx-0.1.47 → falyx-0.1.49}/falyx/action/action_group.py +0 -0
  31. {falyx-0.1.47 → falyx-0.1.49}/falyx/action/chained_action.py +0 -0
  32. {falyx-0.1.47 → falyx-0.1.49}/falyx/action/fallback_action.py +0 -0
  33. {falyx-0.1.47 → falyx-0.1.49}/falyx/action/http_action.py +0 -0
  34. {falyx-0.1.47 → falyx-0.1.49}/falyx/action/io_action.py +0 -0
  35. {falyx-0.1.47 → falyx-0.1.49}/falyx/action/literal_input_action.py +0 -0
  36. {falyx-0.1.47 → falyx-0.1.49}/falyx/action/mixins.py +0 -0
  37. {falyx-0.1.47 → falyx-0.1.49}/falyx/action/process_action.py +0 -0
  38. {falyx-0.1.47 → falyx-0.1.49}/falyx/action/process_pool_action.py +0 -0
  39. {falyx-0.1.47 → falyx-0.1.49}/falyx/action/signal_action.py +0 -0
  40. {falyx-0.1.47 → falyx-0.1.49}/falyx/action/types.py +0 -0
  41. {falyx-0.1.47 → falyx-0.1.49}/falyx/debug.py +0 -0
  42. {falyx-0.1.47 → falyx-0.1.49}/falyx/exceptions.py +0 -0
  43. {falyx-0.1.47 → falyx-0.1.49}/falyx/hook_manager.py +0 -0
  44. {falyx-0.1.47 → falyx-0.1.49}/falyx/hooks.py +0 -0
  45. {falyx-0.1.47 → falyx-0.1.49}/falyx/logger.py +0 -0
  46. {falyx-0.1.47 → falyx-0.1.49}/falyx/menu.py +0 -0
  47. {falyx-0.1.47 → falyx-0.1.49}/falyx/options_manager.py +0 -0
  48. {falyx-0.1.47 → falyx-0.1.49}/falyx/parsers/.pytyped +0 -0
  49. {falyx-0.1.47 → falyx-0.1.49}/falyx/parsers/__init__.py +0 -0
  50. {falyx-0.1.47 → falyx-0.1.49}/falyx/parsers/parsers.py +0 -0
  51. {falyx-0.1.47 → falyx-0.1.49}/falyx/parsers/utils.py +0 -0
  52. {falyx-0.1.47 → falyx-0.1.49}/falyx/prompt_utils.py +0 -0
  53. {falyx-0.1.47 → falyx-0.1.49}/falyx/protocols.py +0 -0
  54. {falyx-0.1.47 → falyx-0.1.49}/falyx/retry.py +0 -0
  55. {falyx-0.1.47 → falyx-0.1.49}/falyx/retry_utils.py +0 -0
  56. {falyx-0.1.47 → falyx-0.1.49}/falyx/signals.py +0 -0
  57. {falyx-0.1.47 → falyx-0.1.49}/falyx/tagged_table.py +0 -0
  58. {falyx-0.1.47 → falyx-0.1.49}/falyx/themes/__init__.py +0 -0
  59. {falyx-0.1.47 → falyx-0.1.49}/falyx/themes/colors.py +0 -0
  60. {falyx-0.1.47 → falyx-0.1.49}/falyx/utils.py +0 -0
  61. {falyx-0.1.47 → falyx-0.1.49}/falyx/validators.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: falyx
3
- Version: 0.1.47
3
+ Version: 0.1.49
4
4
  Summary: Reliable and introspectable async CLI action framework.
5
5
  License: MIT
6
6
  Author: Roland Thomas Jr
@@ -74,7 +74,7 @@ class BaseAction(ABC):
74
74
  self.inject_into: str = inject_into
75
75
  self._never_prompt: bool = never_prompt
76
76
  self._skip_in_chain: bool = False
77
- self.console = Console(color_system="auto")
77
+ self.console = Console(color_system="truecolor")
78
78
  self.options_manager: OptionsManager | None = None
79
79
 
80
80
  if logging_hooks:
@@ -51,7 +51,10 @@ class MenuAction(BaseAction):
51
51
  self.columns = columns
52
52
  self.prompt_message = prompt_message
53
53
  self.default_selection = default_selection
54
- self.console = console or Console(color_system="auto")
54
+ if isinstance(console, Console):
55
+ self.console = console
56
+ elif console:
57
+ raise ValueError("`console` must be an instance of `rich.console.Console`")
55
58
  self.prompt_session = prompt_session or PromptSession()
56
59
  self.include_reserved = include_reserved
57
60
  self.show_table = show_table
@@ -43,7 +43,10 @@ class PromptMenuAction(BaseAction):
43
43
  self.menu_options = menu_options
44
44
  self.prompt_message = prompt_message
45
45
  self.default_selection = default_selection
46
- self.console = console or Console(color_system="auto")
46
+ if isinstance(console, Console):
47
+ self.console = console
48
+ elif console:
49
+ raise ValueError("`console` must be an instance of `rich.console.Console`")
47
50
  self.prompt_session = prompt_session or PromptSession()
48
51
  self.include_reserved = include_reserved
49
52
 
@@ -76,7 +76,10 @@ class SelectFileAction(BaseAction):
76
76
  self.prompt_message = prompt_message
77
77
  self.suffix_filter = suffix_filter
78
78
  self.style = style
79
- self.console = console or Console(color_system="auto")
79
+ if isinstance(console, Console):
80
+ self.console = console
81
+ elif console:
82
+ raise ValueError("`console` must be an instance of `rich.console.Console`")
80
83
  self.prompt_session = prompt_session or PromptSession()
81
84
  self.return_type = self._coerce_return_type(return_type)
82
85
 
@@ -67,7 +67,10 @@ class SelectionAction(BaseAction):
67
67
  self.return_type: SelectionReturnType = self._coerce_return_type(return_type)
68
68
  self.title = title
69
69
  self.columns = columns
70
- self.console = console or Console(color_system="auto")
70
+ if isinstance(console, Console):
71
+ self.console = console
72
+ elif console:
73
+ raise ValueError("`console` must be an instance of `rich.console.Console`")
71
74
  self.prompt_session = prompt_session or PromptSession()
72
75
  self.default_selection = default_selection
73
76
  self.prompt_message = prompt_message
@@ -40,7 +40,10 @@ class UserInputAction(BaseAction):
40
40
  )
41
41
  self.prompt_text = prompt_text
42
42
  self.validator = validator
43
- self.console = console or Console(color_system="auto")
43
+ if isinstance(console, Console):
44
+ self.console = console
45
+ elif console:
46
+ raise ValueError("`console` must be an instance of `rich.console.Console`")
44
47
  self.prompt_session = prompt_session or PromptSession()
45
48
 
46
49
  def get_infer_target(self) -> tuple[None, None]:
@@ -30,7 +30,7 @@ class BottomBar:
30
30
  key_validator: Callable[[str], bool] | None = None,
31
31
  ) -> None:
32
32
  self.columns = columns
33
- self.console = Console(color_system="auto")
33
+ self.console = Console(color_system="truecolor")
34
34
  self._named_items: dict[str, Callable[[], HTML]] = {}
35
35
  self._value_getters: dict[str, Callable[[], Any]] = CaseInsensitiveDict()
36
36
  self.toggle_keys: list[str] = []
@@ -44,7 +44,7 @@ from falyx.signals import CancelSignal
44
44
  from falyx.themes import OneColors
45
45
  from falyx.utils import ensure_async
46
46
 
47
- console = Console(color_system="auto")
47
+ console = Console(color_system="truecolor")
48
48
 
49
49
 
50
50
  class Command(BaseModel):
@@ -18,11 +18,10 @@ from falyx.action.base import BaseAction
18
18
  from falyx.command import Command
19
19
  from falyx.falyx import Falyx
20
20
  from falyx.logger import logger
21
- from falyx.parsers import CommandArgumentParser
22
21
  from falyx.retry import RetryPolicy
23
22
  from falyx.themes import OneColors
24
23
 
25
- console = Console(color_system="auto")
24
+ console = Console(color_system="truecolor")
26
25
 
27
26
 
28
27
  def wrap_if_needed(obj: Any, name=None) -> BaseAction | Command:
@@ -80,8 +80,10 @@ class ExecutionContext(BaseModel):
80
80
  start_wall: datetime | None = None
81
81
  end_wall: datetime | None = None
82
82
 
83
+ index: int | None = None
84
+
83
85
  extra: dict[str, Any] = Field(default_factory=dict)
84
- console: Console = Field(default_factory=lambda: Console(color_system="auto"))
86
+ console: Console = Field(default_factory=lambda: Console(color_system="truecolor"))
85
87
 
86
88
  shared_context: SharedContext | None = None
87
89
 
@@ -29,7 +29,8 @@ from __future__ import annotations
29
29
 
30
30
  from collections import defaultdict
31
31
  from datetime import datetime
32
- from typing import Dict, List
32
+ from threading import Lock
33
+ from typing import Any, Literal
33
34
 
34
35
  from rich import box
35
36
  from rich.console import Console
@@ -70,23 +71,30 @@ class ExecutionRegistry:
70
71
  ExecutionRegistry.summary()
71
72
  """
72
73
 
73
- _store_by_name: Dict[str, List[ExecutionContext]] = defaultdict(list)
74
- _store_all: List[ExecutionContext] = []
75
- _console = Console(color_system="auto")
74
+ _store_by_name: dict[str, list[ExecutionContext]] = defaultdict(list)
75
+ _store_by_index: dict[int, ExecutionContext] = {}
76
+ _store_all: list[ExecutionContext] = []
77
+ _console = Console(color_system="truecolor")
78
+ _index = 0
79
+ _lock = Lock()
76
80
 
77
81
  @classmethod
78
82
  def record(cls, context: ExecutionContext):
79
83
  """Record an execution context."""
80
84
  logger.debug(context.to_log_line())
85
+ with cls._lock:
86
+ context.index = cls._index
87
+ cls._store_by_index[cls._index] = context
88
+ cls._index += 1
81
89
  cls._store_by_name[context.name].append(context)
82
90
  cls._store_all.append(context)
83
91
 
84
92
  @classmethod
85
- def get_all(cls) -> List[ExecutionContext]:
93
+ def get_all(cls) -> list[ExecutionContext]:
86
94
  return cls._store_all
87
95
 
88
96
  @classmethod
89
- def get_by_name(cls, name: str) -> List[ExecutionContext]:
97
+ def get_by_name(cls, name: str) -> list[ExecutionContext]:
90
98
  return cls._store_by_name.get(name, [])
91
99
 
92
100
  @classmethod
@@ -97,11 +105,74 @@ class ExecutionRegistry:
97
105
  def clear(cls):
98
106
  cls._store_by_name.clear()
99
107
  cls._store_all.clear()
108
+ cls._store_by_index.clear()
100
109
 
101
110
  @classmethod
102
- def summary(cls):
103
- table = Table(title="📊 Execution History", expand=True, box=box.SIMPLE)
104
-
111
+ def summary(
112
+ cls,
113
+ name: str = "",
114
+ index: int = -1,
115
+ result: int = -1,
116
+ clear: bool = False,
117
+ last_result: bool = False,
118
+ status: Literal["all", "success", "error"] = "all",
119
+ ):
120
+ if clear:
121
+ cls.clear()
122
+ cls._console.print(f"[{OneColors.GREEN}]✅ Execution history cleared.")
123
+ return
124
+
125
+ if last_result:
126
+ for ctx in reversed(cls._store_all):
127
+ if ctx.name.upper() not in [
128
+ "HISTORY",
129
+ "HELP",
130
+ "EXIT",
131
+ "VIEW EXECUTION HISTORY",
132
+ "BACK",
133
+ ]:
134
+ cls._console.print(ctx.result)
135
+ return
136
+ cls._console.print(
137
+ f"[{OneColors.DARK_RED}]❌ No valid executions found to display last result."
138
+ )
139
+ return
140
+
141
+ if result and result >= 0:
142
+ try:
143
+ result_context = cls._store_by_index[result]
144
+ except KeyError:
145
+ cls._console.print(
146
+ f"[{OneColors.DARK_RED}]❌ No execution found for index {index}."
147
+ )
148
+ return
149
+ cls._console.print(result_context.result)
150
+ return
151
+
152
+ if name:
153
+ contexts = cls.get_by_name(name)
154
+ if not contexts:
155
+ cls._console.print(
156
+ f"[{OneColors.DARK_RED}]❌ No executions found for action '{name}'."
157
+ )
158
+ return
159
+ title = f"📊 Execution History for '{contexts[0].name}'"
160
+ elif index and index >= 0:
161
+ try:
162
+ contexts = [cls._store_by_index[index]]
163
+ except KeyError:
164
+ cls._console.print(
165
+ f"[{OneColors.DARK_RED}]❌ No execution found for index {index}."
166
+ )
167
+ return
168
+ title = f"📊 Execution History for Index {index}"
169
+ else:
170
+ contexts = cls.get_all()
171
+ title = "📊 Execution History"
172
+
173
+ table = Table(title=title, expand=True, box=box.SIMPLE)
174
+
175
+ table.add_column("Index", justify="right", style="dim")
105
176
  table.add_column("Name", style="bold cyan")
106
177
  table.add_column("Start", justify="right", style="dim")
107
178
  table.add_column("End", justify="right", style="dim")
@@ -109,7 +180,7 @@ class ExecutionRegistry:
109
180
  table.add_column("Status", style="bold")
110
181
  table.add_column("Result / Exception", overflow="fold")
111
182
 
112
- for ctx in cls.get_all():
183
+ for ctx in contexts:
113
184
  start = (
114
185
  datetime.fromtimestamp(ctx.start_time).strftime("%H:%M:%S")
115
186
  if ctx.start_time
@@ -122,15 +193,19 @@ class ExecutionRegistry:
122
193
  )
123
194
  duration = f"{ctx.duration:.3f}s" if ctx.duration else "n/a"
124
195
 
125
- if ctx.exception:
126
- status = f"[{OneColors.DARK_RED}]❌ Error"
127
- result = repr(ctx.exception)
196
+ if ctx.exception and status.lower() in ["all", "error"]:
197
+ final_status = f"[{OneColors.DARK_RED}]❌ Error"
198
+ final_result = repr(ctx.exception)
199
+ elif status.lower() in ["all", "success"]:
200
+ final_status = f"[{OneColors.GREEN}]✅ Success"
201
+ final_result = repr(ctx.result)
202
+ if len(final_result) > 1000:
203
+ final_result = f"{final_result[:1000]}..."
128
204
  else:
129
- status = f"[{OneColors.GREEN}]✅ Success"
130
- result = repr(ctx.result)
131
- if len(result) > 1000:
132
- result = f"{result[:1000]}..."
205
+ continue
133
206
 
134
- table.add_row(ctx.name, start, end, duration, status, result)
207
+ table.add_row(
208
+ str(ctx.index), ctx.name, start, end, duration, final_status, final_result
209
+ )
135
210
 
136
211
  cls._console.print(table)
@@ -201,7 +201,7 @@ class Falyx:
201
201
  self.help_command: Command | None = (
202
202
  self._get_help_command() if include_help_command else None
203
203
  )
204
- self.console: Console = Console(color_system="auto", theme=get_nord_theme())
204
+ self.console: Console = Console(color_system="truecolor", theme=get_nord_theme())
205
205
  self.welcome_message: str | Markdown | dict[str, Any] = welcome_message
206
206
  self.exit_message: str | Markdown | dict[str, Any] = exit_message
207
207
  self.hooks: HookManager = HookManager()
@@ -300,6 +300,40 @@ class Falyx:
300
300
 
301
301
  def _get_history_command(self) -> Command:
302
302
  """Returns the history command for the menu."""
303
+ parser = CommandArgumentParser(
304
+ command_key="Y",
305
+ command_description="History",
306
+ command_style=OneColors.DARK_YELLOW,
307
+ aliases=["HISTORY"],
308
+ )
309
+ parser.add_argument(
310
+ "-n",
311
+ "--name",
312
+ help="Filter by execution name.",
313
+ )
314
+ parser.add_argument(
315
+ "-i",
316
+ "--index",
317
+ type=int,
318
+ help="Filter by execution index (0-based).",
319
+ )
320
+ parser.add_argument(
321
+ "-s",
322
+ "--status",
323
+ choices=["all", "success", "error"],
324
+ default="all",
325
+ help="Filter by execution status (default: all).",
326
+ )
327
+ parser.add_argument(
328
+ "-c",
329
+ "--clear",
330
+ action="store_true",
331
+ help="Clear the Execution History.",
332
+ )
333
+ parser.add_argument("-r", "--result", type=int, help="Get the result by index")
334
+ parser.add_argument(
335
+ "-l", "--last-result", action="store_true", help="Get the last result"
336
+ )
303
337
  return Command(
304
338
  key="Y",
305
339
  description="History",
@@ -307,6 +341,8 @@ class Falyx:
307
341
  action=Action(name="View Execution History", action=er.summary),
308
342
  style=OneColors.DARK_YELLOW,
309
343
  simple_help_signature=True,
344
+ arg_parser=parser,
345
+ help_text="View the execution history of commands.",
310
346
  )
311
347
 
312
348
  async def _show_help(self, tag: str = "") -> None:
@@ -746,12 +782,10 @@ class Falyx:
746
782
  """
747
783
  table = Table(title=self.title, show_header=False, box=box.SIMPLE) # type: ignore[arg-type]
748
784
  visible_commands = [item for item in self.commands.items() if not item[1].hidden]
749
- space = self.console.width // self.columns
750
785
  for chunk in chunks(visible_commands, self.columns):
751
786
  row = []
752
787
  for key, command in chunk:
753
- cell = f"[{key}] [{command.style}]{command.description}"
754
- row.append(f"{cell:<{space}}")
788
+ row.append(f"[{key}] [{command.style}]{command.description}")
755
789
  table.add_row(*row)
756
790
  bottom_row = self.get_bottom_row()
757
791
  for row in chunks(bottom_row, self.columns):
@@ -811,7 +845,7 @@ class Falyx:
811
845
  args, kwargs = await name_map[choice].parse_args(
812
846
  input_args, from_validate
813
847
  )
814
- except CommandArgumentError as error:
848
+ except (CommandArgumentError, Exception) as error:
815
849
  if not from_validate:
816
850
  name_map[choice].show_help()
817
851
  self.console.print(f"[{OneColors.DARK_RED}]❌ [{choice}]: {error}")
@@ -98,7 +98,7 @@ commands:
98
98
  aliases: [clean, cleanup]
99
99
  """
100
100
 
101
- console = Console(color_system="auto")
101
+ console = Console(color_system="truecolor")
102
102
 
103
103
 
104
104
  def init_project(name: str) -> None:
@@ -159,7 +159,7 @@ class CommandArgumentParser:
159
159
  aliases: list[str] | None = None,
160
160
  ) -> None:
161
161
  """Initialize the CommandArgumentParser."""
162
- self.console = Console(color_system="auto")
162
+ self.console = Console(color_system="truecolor")
163
163
  self.command_key: str = command_key
164
164
  self.command_description: str = command_description
165
165
  self.command_style: str = command_style
@@ -292,10 +292,10 @@ class CommandArgumentParser:
292
292
  if not isinstance(choice, expected_type):
293
293
  try:
294
294
  coerce_value(choice, expected_type)
295
- except Exception:
295
+ except Exception as error:
296
296
  raise CommandArgumentError(
297
- f"Invalid choice {choice!r}: not coercible to {expected_type.__name__}"
298
- )
297
+ f"Invalid choice {choice!r}: not coercible to {expected_type.__name__} error: {error}"
298
+ ) from error
299
299
  return choices
300
300
 
301
301
  def _validate_default_type(
@@ -305,10 +305,10 @@ class CommandArgumentParser:
305
305
  if default is not None and not isinstance(default, expected_type):
306
306
  try:
307
307
  coerce_value(default, expected_type)
308
- except Exception:
308
+ except Exception as error:
309
309
  raise CommandArgumentError(
310
- f"Default value {default!r} for '{dest}' cannot be coerced to {expected_type.__name__}"
311
- )
310
+ f"Default value {default!r} for '{dest}' cannot be coerced to {expected_type.__name__} error: {error}"
311
+ ) from error
312
312
 
313
313
  def _validate_default_list_type(
314
314
  self, default: list[Any], expected_type: type, dest: str
@@ -318,10 +318,10 @@ class CommandArgumentParser:
318
318
  if not isinstance(item, expected_type):
319
319
  try:
320
320
  coerce_value(item, expected_type)
321
- except Exception:
321
+ except Exception as error:
322
322
  raise CommandArgumentError(
323
- f"Default list value {default!r} for '{dest}' cannot be coerced to {expected_type.__name__}"
324
- )
323
+ f"Default list value {default!r} for '{dest}' cannot be coerced to {expected_type.__name__} error: {error}"
324
+ ) from error
325
325
 
326
326
  def _validate_resolver(
327
327
  self, action: ArgumentAction, resolver: BaseAction | None
@@ -597,10 +597,10 @@ class CommandArgumentParser:
597
597
 
598
598
  try:
599
599
  typed = [coerce_value(value, spec.type) for value in values]
600
- except Exception:
600
+ except Exception as error:
601
601
  raise CommandArgumentError(
602
- f"Invalid value for '{spec.dest}': expected {spec.type.__name__}"
603
- )
602
+ f"Invalid value for '{spec.dest}': {error}"
603
+ ) from error
604
604
  if spec.action == ArgumentAction.ACTION:
605
605
  assert isinstance(
606
606
  spec.resolver, BaseAction
@@ -684,10 +684,10 @@ class CommandArgumentParser:
684
684
  typed_values = [
685
685
  coerce_value(value, spec.type) for value in values
686
686
  ]
687
- except ValueError:
687
+ except ValueError as error:
688
688
  raise CommandArgumentError(
689
- f"Invalid value for '{spec.dest}': expected {spec.type.__name__}"
690
- )
689
+ f"Invalid value for '{spec.dest}': {error}"
690
+ ) from error
691
691
  try:
692
692
  result[spec.dest] = await spec.resolver(*typed_values)
693
693
  except Exception as error:
@@ -715,10 +715,10 @@ class CommandArgumentParser:
715
715
  typed_values = [
716
716
  coerce_value(value, spec.type) for value in values
717
717
  ]
718
- except ValueError:
718
+ except ValueError as error:
719
719
  raise CommandArgumentError(
720
- f"Invalid value for '{spec.dest}': expected {spec.type.__name__}"
721
- )
720
+ f"Invalid value for '{spec.dest}': {error}"
721
+ ) from error
722
722
  if spec.nargs is None:
723
723
  result[spec.dest].append(spec.type(values[0]))
724
724
  else:
@@ -732,10 +732,10 @@ class CommandArgumentParser:
732
732
  typed_values = [
733
733
  coerce_value(value, spec.type) for value in values
734
734
  ]
735
- except ValueError:
735
+ except ValueError as error:
736
736
  raise CommandArgumentError(
737
- f"Invalid value for '{spec.dest}': expected {spec.type.__name__}"
738
- )
737
+ f"Invalid value for '{spec.dest}': {error}"
738
+ ) from error
739
739
  result[spec.dest].extend(typed_values)
740
740
  consumed_indices.update(range(i, new_i))
741
741
  i = new_i
@@ -745,10 +745,10 @@ class CommandArgumentParser:
745
745
  typed_values = [
746
746
  coerce_value(value, spec.type) for value in values
747
747
  ]
748
- except ValueError:
748
+ except ValueError as error:
749
749
  raise CommandArgumentError(
750
- f"Invalid value for '{spec.dest}': expected {spec.type.__name__}"
751
- )
750
+ f"Invalid value for '{spec.dest}': {error}"
751
+ ) from error
752
752
  if not typed_values and spec.nargs not in ("*", "?"):
753
753
  raise CommandArgumentError(
754
754
  f"Expected at least one value for '{spec.dest}'"
@@ -31,11 +31,16 @@ def infer_args_from_func(
31
31
  ):
32
32
  continue
33
33
 
34
- arg_type = (
35
- param.annotation if param.annotation is not inspect.Parameter.empty else str
36
- )
37
- if isinstance(arg_type, str):
38
- arg_type = str
34
+ if metadata.get("type"):
35
+ arg_type = metadata["type"]
36
+ else:
37
+ arg_type = (
38
+ param.annotation
39
+ if param.annotation is not inspect.Parameter.empty
40
+ else str
41
+ )
42
+ if isinstance(arg_type, str):
43
+ arg_type = str
39
44
  default = param.default if param.default is not inspect.Parameter.empty else None
40
45
  is_required = param.default is inspect.Parameter.empty
41
46
  if is_required:
@@ -273,7 +273,7 @@ async def prompt_for_index(
273
273
  show_table: bool = True,
274
274
  ) -> int:
275
275
  prompt_session = prompt_session or PromptSession()
276
- console = console or Console(color_system="auto")
276
+ console = console or Console(color_system="truecolor")
277
277
 
278
278
  if show_table:
279
279
  console.print(table, justify="center")
@@ -298,7 +298,7 @@ async def prompt_for_selection(
298
298
  ) -> str:
299
299
  """Prompt the user to select a key from a set of options. Return the selected key."""
300
300
  prompt_session = prompt_session or PromptSession()
301
- console = console or Console(color_system="auto")
301
+ console = console or Console(color_system="truecolor")
302
302
 
303
303
  if show_table:
304
304
  console.print(table, justify="center")
@@ -351,7 +351,7 @@ async def select_value_from_list(
351
351
  highlight=highlight,
352
352
  )
353
353
  prompt_session = prompt_session or PromptSession()
354
- console = console or Console(color_system="auto")
354
+ console = console or Console(color_system="truecolor")
355
355
 
356
356
  selection_index = await prompt_for_index(
357
357
  len(selections) - 1,
@@ -376,7 +376,7 @@ async def select_key_from_dict(
376
376
  ) -> Any:
377
377
  """Prompt for a key from a dict, returns the key."""
378
378
  prompt_session = prompt_session or PromptSession()
379
- console = console or Console(color_system="auto")
379
+ console = console or Console(color_system="truecolor")
380
380
 
381
381
  console.print(table, justify="center")
382
382
 
@@ -401,7 +401,7 @@ async def select_value_from_dict(
401
401
  ) -> Any:
402
402
  """Prompt for a key from a dict, but return the value."""
403
403
  prompt_session = prompt_session or PromptSession()
404
- console = console or Console(color_system="auto")
404
+ console = console or Console(color_system="truecolor")
405
405
 
406
406
  console.print(table, justify="center")
407
407
 
@@ -0,0 +1 @@
1
+ __version__ = "0.1.49"
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "falyx"
3
- version = "0.1.47"
3
+ version = "0.1.49"
4
4
  description = "Reliable and introspectable async CLI action framework."
5
5
  authors = ["Roland Thomas Jr <roland@rtj.dev>"]
6
6
  license = "MIT"
@@ -1 +0,0 @@
1
- __version__ = "0.1.47"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes