falyx 0.1.48__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.48 → falyx-0.1.49}/PKG-INFO +1 -1
  2. {falyx-0.1.48 → falyx-0.1.49}/falyx/action/base.py +1 -1
  3. {falyx-0.1.48 → falyx-0.1.49}/falyx/action/menu_action.py +4 -1
  4. {falyx-0.1.48 → falyx-0.1.49}/falyx/action/prompt_menu_action.py +4 -1
  5. {falyx-0.1.48 → falyx-0.1.49}/falyx/action/select_file_action.py +4 -1
  6. {falyx-0.1.48 → falyx-0.1.49}/falyx/action/selection_action.py +4 -1
  7. {falyx-0.1.48 → falyx-0.1.49}/falyx/action/user_input_action.py +4 -1
  8. {falyx-0.1.48 → falyx-0.1.49}/falyx/bottom_bar.py +1 -1
  9. {falyx-0.1.48 → falyx-0.1.49}/falyx/command.py +1 -1
  10. {falyx-0.1.48 → falyx-0.1.49}/falyx/config.py +1 -2
  11. {falyx-0.1.48 → falyx-0.1.49}/falyx/context.py +3 -1
  12. {falyx-0.1.48 → falyx-0.1.49}/falyx/execution_registry.py +93 -18
  13. {falyx-0.1.48 → falyx-0.1.49}/falyx/falyx.py +37 -1
  14. {falyx-0.1.48 → falyx-0.1.49}/falyx/init.py +1 -1
  15. {falyx-0.1.48 → falyx-0.1.49}/falyx/parsers/argparse.py +1 -1
  16. {falyx-0.1.48 → falyx-0.1.49}/falyx/selection.py +5 -5
  17. falyx-0.1.49/falyx/version.py +1 -0
  18. {falyx-0.1.48 → falyx-0.1.49}/pyproject.toml +1 -1
  19. falyx-0.1.48/falyx/version.py +0 -1
  20. {falyx-0.1.48 → falyx-0.1.49}/LICENSE +0 -0
  21. {falyx-0.1.48 → falyx-0.1.49}/README.md +0 -0
  22. {falyx-0.1.48 → falyx-0.1.49}/falyx/.pytyped +0 -0
  23. {falyx-0.1.48 → falyx-0.1.49}/falyx/__init__.py +0 -0
  24. {falyx-0.1.48 → falyx-0.1.49}/falyx/__main__.py +0 -0
  25. {falyx-0.1.48 → falyx-0.1.49}/falyx/action/.pytyped +0 -0
  26. {falyx-0.1.48 → falyx-0.1.49}/falyx/action/__init__.py +0 -0
  27. {falyx-0.1.48 → falyx-0.1.49}/falyx/action/action.py +0 -0
  28. {falyx-0.1.48 → falyx-0.1.49}/falyx/action/action_factory.py +0 -0
  29. {falyx-0.1.48 → falyx-0.1.49}/falyx/action/action_group.py +0 -0
  30. {falyx-0.1.48 → falyx-0.1.49}/falyx/action/chained_action.py +0 -0
  31. {falyx-0.1.48 → falyx-0.1.49}/falyx/action/fallback_action.py +0 -0
  32. {falyx-0.1.48 → falyx-0.1.49}/falyx/action/http_action.py +0 -0
  33. {falyx-0.1.48 → falyx-0.1.49}/falyx/action/io_action.py +0 -0
  34. {falyx-0.1.48 → falyx-0.1.49}/falyx/action/literal_input_action.py +0 -0
  35. {falyx-0.1.48 → falyx-0.1.49}/falyx/action/mixins.py +0 -0
  36. {falyx-0.1.48 → falyx-0.1.49}/falyx/action/process_action.py +0 -0
  37. {falyx-0.1.48 → falyx-0.1.49}/falyx/action/process_pool_action.py +0 -0
  38. {falyx-0.1.48 → falyx-0.1.49}/falyx/action/signal_action.py +0 -0
  39. {falyx-0.1.48 → falyx-0.1.49}/falyx/action/types.py +0 -0
  40. {falyx-0.1.48 → falyx-0.1.49}/falyx/debug.py +0 -0
  41. {falyx-0.1.48 → falyx-0.1.49}/falyx/exceptions.py +0 -0
  42. {falyx-0.1.48 → falyx-0.1.49}/falyx/hook_manager.py +0 -0
  43. {falyx-0.1.48 → falyx-0.1.49}/falyx/hooks.py +0 -0
  44. {falyx-0.1.48 → falyx-0.1.49}/falyx/logger.py +0 -0
  45. {falyx-0.1.48 → falyx-0.1.49}/falyx/menu.py +0 -0
  46. {falyx-0.1.48 → falyx-0.1.49}/falyx/options_manager.py +0 -0
  47. {falyx-0.1.48 → falyx-0.1.49}/falyx/parsers/.pytyped +0 -0
  48. {falyx-0.1.48 → falyx-0.1.49}/falyx/parsers/__init__.py +0 -0
  49. {falyx-0.1.48 → falyx-0.1.49}/falyx/parsers/parsers.py +0 -0
  50. {falyx-0.1.48 → falyx-0.1.49}/falyx/parsers/signature.py +0 -0
  51. {falyx-0.1.48 → falyx-0.1.49}/falyx/parsers/utils.py +0 -0
  52. {falyx-0.1.48 → falyx-0.1.49}/falyx/prompt_utils.py +0 -0
  53. {falyx-0.1.48 → falyx-0.1.49}/falyx/protocols.py +0 -0
  54. {falyx-0.1.48 → falyx-0.1.49}/falyx/retry.py +0 -0
  55. {falyx-0.1.48 → falyx-0.1.49}/falyx/retry_utils.py +0 -0
  56. {falyx-0.1.48 → falyx-0.1.49}/falyx/signals.py +0 -0
  57. {falyx-0.1.48 → falyx-0.1.49}/falyx/tagged_table.py +0 -0
  58. {falyx-0.1.48 → falyx-0.1.49}/falyx/themes/__init__.py +0 -0
  59. {falyx-0.1.48 → falyx-0.1.49}/falyx/themes/colors.py +0 -0
  60. {falyx-0.1.48 → falyx-0.1.49}/falyx/utils.py +0 -0
  61. {falyx-0.1.48 → 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.48
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:
@@ -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
@@ -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.48"
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.48"
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