falyx 0.1.18__tar.gz → 0.1.19__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 (40) hide show
  1. {falyx-0.1.18 → falyx-0.1.19}/PKG-INFO +1 -1
  2. {falyx-0.1.18 → falyx-0.1.19}/falyx/action.py +18 -0
  3. falyx-0.1.19/falyx/action_factory.py +23 -0
  4. {falyx-0.1.18 → falyx-0.1.19}/falyx/falyx.py +12 -12
  5. {falyx-0.1.18 → falyx-0.1.19}/falyx/http_action.py +4 -1
  6. {falyx-0.1.18 → falyx-0.1.19}/falyx/init.py +4 -2
  7. {falyx-0.1.18 → falyx-0.1.19}/falyx/menu_action.py +2 -4
  8. {falyx-0.1.18 → falyx-0.1.19}/falyx/prompt_utils.py +1 -2
  9. {falyx-0.1.18 → falyx-0.1.19}/falyx/select_files_action.py +3 -3
  10. {falyx-0.1.18 → falyx-0.1.19}/falyx/selection.py +17 -17
  11. {falyx-0.1.18 → falyx-0.1.19}/falyx/selection_action.py +10 -8
  12. falyx-0.1.19/falyx/version.py +1 -0
  13. {falyx-0.1.18 → falyx-0.1.19}/pyproject.toml +1 -1
  14. falyx-0.1.18/falyx/.coverage +0 -0
  15. falyx-0.1.18/falyx/version.py +0 -1
  16. {falyx-0.1.18 → falyx-0.1.19}/LICENSE +0 -0
  17. {falyx-0.1.18 → falyx-0.1.19}/README.md +0 -0
  18. {falyx-0.1.18 → falyx-0.1.19}/falyx/.pytyped +0 -0
  19. {falyx-0.1.18 → falyx-0.1.19}/falyx/__init__.py +0 -0
  20. {falyx-0.1.18 → falyx-0.1.19}/falyx/__main__.py +0 -0
  21. {falyx-0.1.18 → falyx-0.1.19}/falyx/bottom_bar.py +0 -0
  22. {falyx-0.1.18 → falyx-0.1.19}/falyx/command.py +0 -0
  23. {falyx-0.1.18 → falyx-0.1.19}/falyx/config.py +0 -0
  24. {falyx-0.1.18 → falyx-0.1.19}/falyx/context.py +0 -0
  25. {falyx-0.1.18 → falyx-0.1.19}/falyx/debug.py +0 -0
  26. {falyx-0.1.18 → falyx-0.1.19}/falyx/exceptions.py +0 -0
  27. {falyx-0.1.18 → falyx-0.1.19}/falyx/execution_registry.py +0 -0
  28. {falyx-0.1.18 → falyx-0.1.19}/falyx/hook_manager.py +0 -0
  29. {falyx-0.1.18 → falyx-0.1.19}/falyx/hooks.py +0 -0
  30. {falyx-0.1.18 → falyx-0.1.19}/falyx/io_action.py +0 -0
  31. {falyx-0.1.18 → falyx-0.1.19}/falyx/options_manager.py +0 -0
  32. {falyx-0.1.18 → falyx-0.1.19}/falyx/parsers.py +0 -0
  33. {falyx-0.1.18 → falyx-0.1.19}/falyx/retry.py +0 -0
  34. {falyx-0.1.18 → falyx-0.1.19}/falyx/retry_utils.py +0 -0
  35. {falyx-0.1.18 → falyx-0.1.19}/falyx/signal_action.py +0 -0
  36. {falyx-0.1.18 → falyx-0.1.19}/falyx/signals.py +0 -0
  37. {falyx-0.1.18 → falyx-0.1.19}/falyx/tagged_table.py +0 -0
  38. {falyx-0.1.18 → falyx-0.1.19}/falyx/themes/colors.py +0 -0
  39. {falyx-0.1.18 → falyx-0.1.19}/falyx/utils.py +0 -0
  40. {falyx-0.1.18 → falyx-0.1.19}/falyx/validators.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: falyx
3
- Version: 0.1.18
3
+ Version: 0.1.19
4
4
  Summary: Reliable and introspectable async CLI action framework.
5
5
  License: MIT
6
6
  Author: Roland Thomas Jr
@@ -448,6 +448,8 @@ class ChainedAction(BaseAction, ActionListMixin):
448
448
  if self.actions and self.auto_inject and not action.inject_last_result:
449
449
  action.inject_last_result = True
450
450
  super().add_action(action)
451
+ if hasattr(action, "register_teardown") and callable(action.register_teardown):
452
+ action.register_teardown(self.hooks)
451
453
 
452
454
  async def _run(self, *args, **kwargs) -> list[Any]:
453
455
  if not self.actions:
@@ -617,6 +619,22 @@ class ActionGroup(BaseAction, ActionListMixin):
617
619
  if actions:
618
620
  self.set_actions(actions)
619
621
 
622
+ def _wrap_if_needed(self, action: BaseAction | Any) -> BaseAction:
623
+ if isinstance(action, BaseAction):
624
+ return action
625
+ elif callable(action):
626
+ return Action(name=action.__name__, action=action)
627
+ else:
628
+ raise TypeError(
629
+ f"ActionGroup only accepts BaseAction or callable, got {type(action).__name__}"
630
+ )
631
+
632
+ def add_action(self, action: BaseAction | Any) -> None:
633
+ action = self._wrap_if_needed(action)
634
+ super().add_action(action)
635
+ if hasattr(action, "register_teardown") and callable(action.register_teardown):
636
+ action.register_teardown(self.hooks)
637
+
620
638
  async def _run(self, *args, **kwargs) -> list[tuple[str, Any]]:
621
639
  shared_context = SharedContext(name=self.name, is_parallel=True)
622
640
  if self.shared_context:
@@ -0,0 +1,23 @@
1
+ from typing import Callable
2
+
3
+ from falyx.action import BaseAction
4
+
5
+
6
+ class ActionFactoryAction(BaseAction):
7
+ def __init__(
8
+ self,
9
+ name: str,
10
+ factory: Callable[[dict], BaseAction],
11
+ *,
12
+ inject_last_result: bool = False,
13
+ inject_last_result_as: str = "last_result",
14
+ ):
15
+ super().__init__(name, inject_last_result=inject_last_result, inject_last_result_as=inject_last_result_as)
16
+ self.factory = factory
17
+
18
+ async def _run(self, *args, **kwargs) -> BaseAction:
19
+ kwargs = self._maybe_inject_last_result(kwargs)
20
+ action = self.factory(kwargs)
21
+ if not isinstance(action, BaseAction):
22
+ raise TypeError(f"[{self.name}] Factory did not return a valid BaseAction.")
23
+ return action
@@ -148,7 +148,7 @@ class Falyx:
148
148
  self.render_menu: Callable[["Falyx"], None] | None = render_menu
149
149
  self.custom_table: Callable[["Falyx"], Table] | Table | None = custom_table
150
150
  self.validate_options(cli_args, options)
151
- self._session: PromptSession | None = None
151
+ self._prompt_session: PromptSession | None = None
152
152
 
153
153
  def validate_options(
154
154
  self,
@@ -337,11 +337,11 @@ class Falyx:
337
337
  move_cursor_to_end=True,
338
338
  )
339
339
 
340
- def _invalidate_session_cache(self):
341
- """Forces the session to be recreated on the next access."""
342
- if hasattr(self, "session"):
343
- del self.session
344
- self._session = None
340
+ def _invalidate_prompt_session_cache(self):
341
+ """Forces the prompt session to be recreated on the next access."""
342
+ if hasattr(self, "prompt_session"):
343
+ del self.prompt_session
344
+ self._prompt_session = None
345
345
 
346
346
  def add_help_command(self):
347
347
  """Adds a help command to the menu if it doesn't already exist."""
@@ -375,7 +375,7 @@ class Falyx:
375
375
  raise FalyxError(
376
376
  "Bottom bar must be a string, callable, or BottomBar instance."
377
377
  )
378
- self._invalidate_session_cache()
378
+ self._invalidate_prompt_session_cache()
379
379
 
380
380
  def _get_bottom_bar_render(self) -> Callable[[], Any] | str | None:
381
381
  """Returns the bottom bar for the menu."""
@@ -390,10 +390,10 @@ class Falyx:
390
390
  return None
391
391
 
392
392
  @cached_property
393
- def session(self) -> PromptSession:
393
+ def prompt_session(self) -> PromptSession:
394
394
  """Returns the prompt session for the menu."""
395
- if self._session is None:
396
- self._session = PromptSession(
395
+ if self._prompt_session is None:
396
+ self._prompt_session = PromptSession(
397
397
  message=self.prompt,
398
398
  multiline=False,
399
399
  completer=self._get_completer(),
@@ -402,7 +402,7 @@ class Falyx:
402
402
  bottom_toolbar=self._get_bottom_bar_render(),
403
403
  key_bindings=self.key_bindings,
404
404
  )
405
- return self._session
405
+ return self._prompt_session
406
406
 
407
407
  def register_all_hooks(self, hook_type: HookType, hooks: Hook | list[Hook]) -> None:
408
408
  """Registers hooks for all commands in the menu and actions recursively."""
@@ -717,7 +717,7 @@ class Falyx:
717
717
 
718
718
  async def process_command(self) -> bool:
719
719
  """Processes the action of the selected command."""
720
- choice = await self.session.prompt_async()
720
+ choice = await self.prompt_session.prompt_async()
721
721
  selected_command = self.get_command(choice)
722
722
  if not selected_command:
723
723
  logger.info(f"Invalid command '{choice}'.")
@@ -15,6 +15,7 @@ from rich.tree import Tree
15
15
 
16
16
  from falyx.action import Action
17
17
  from falyx.context import ExecutionContext, SharedContext
18
+ from falyx.hook_manager import HookManager, HookType
18
19
  from falyx.themes.colors import OneColors
19
20
  from falyx.utils import logger
20
21
 
@@ -97,7 +98,6 @@ class HTTPAction(Action):
97
98
  )
98
99
 
99
100
  async def _request(self, *args, **kwargs) -> dict[str, Any]:
100
- # TODO: Add check for HOOK registration
101
101
  if self.shared_context:
102
102
  context: SharedContext = self.shared_context
103
103
  session = context.get("http_session")
@@ -128,6 +128,9 @@ class HTTPAction(Action):
128
128
  if not self.shared_context:
129
129
  await session.close()
130
130
 
131
+ def register_teardown(self, hooks: HookManager):
132
+ hooks.register(HookType.ON_TEARDOWN, close_shared_http_session)
133
+
131
134
  async def preview(self, parent: Tree | None = None):
132
135
  label = [
133
136
  f"[{OneColors.CYAN_b}]🌐 HTTPAction[/] '{self.name}'",
@@ -33,8 +33,10 @@ async def cleanup():
33
33
  """
34
34
 
35
35
  GLOBAL_CONFIG = """\
36
- async def cleanup():
37
- print("🧹 Cleaning temp files...")
36
+ - key: C
37
+ description: Cleanup temp files
38
+ action: tasks.cleanup
39
+ aliases: [clean, cleanup]
38
40
  """
39
41
 
40
42
  console = Console(color_system="auto")
@@ -168,15 +168,13 @@ class MenuAction(BaseAction):
168
168
  await self.hooks.trigger(HookType.BEFORE, context)
169
169
  key = effective_default
170
170
  if not self.never_prompt:
171
- console = self.console
172
- session = self.prompt_session
173
171
  table = self._build_table()
174
172
  key = await prompt_for_selection(
175
173
  self.menu_options.keys(),
176
174
  table,
177
175
  default_selection=self.default_selection,
178
- console=console,
179
- session=session,
176
+ console=self.console,
177
+ prompt_session=self.prompt_session,
180
178
  prompt_message=self.prompt_message,
181
179
  show_table=self.show_table,
182
180
  )
@@ -9,11 +9,10 @@ def should_prompt_user(
9
9
  ):
10
10
  """Determine whether to prompt the user for confirmation based on command and global options."""
11
11
  never_prompt = options.get("never_prompt", False, namespace)
12
- always_confirm = options.get("always_confirm", False, namespace)
13
12
  force_confirm = options.get("force_confirm", False, namespace)
14
13
  skip_confirm = options.get("skip_confirm", False, namespace)
15
14
 
16
15
  if never_prompt or skip_confirm:
17
16
  return False
18
17
 
19
- return confirm or always_confirm or force_confirm
18
+ return confirm or force_confirm
@@ -10,7 +10,7 @@ class SelectFilesAction(BaseAction):
10
10
  suffix_filter: str | None = None,
11
11
  return_path: bool = True,
12
12
  console: Console | None = None,
13
- session: PromptSession | None = None,
13
+ prompt_session: PromptSession | None = None,
14
14
  ):
15
15
  super().__init__(name)
16
16
  self.directory = Path(directory).resolve()
@@ -20,7 +20,7 @@ class SelectFilesAction(BaseAction):
20
20
  self.style = style
21
21
  self.return_path = return_path
22
22
  self.console = console or Console()
23
- self.session = session or PromptSession()
23
+ self.prompt_session = prompt_session or PromptSession()
24
24
 
25
25
  async def _run(self, *args, **kwargs) -> Any:
26
26
  context = ExecutionContext(name=self.name, args=args, kwargs=kwargs, action=self)
@@ -49,7 +49,7 @@ class SelectFilesAction(BaseAction):
49
49
  options.keys(),
50
50
  table,
51
51
  console=self.console,
52
- session=self.session,
52
+ prompt_session=self.prompt_session,
53
53
  prompt_message=self.prompt_message,
54
54
  )
55
55
 
@@ -203,17 +203,17 @@ async def prompt_for_index(
203
203
  min_index: int = 0,
204
204
  default_selection: str = "",
205
205
  console: Console | None = None,
206
- session: PromptSession | None = None,
206
+ prompt_session: PromptSession | None = None,
207
207
  prompt_message: str = "Select an option > ",
208
208
  show_table: bool = True,
209
209
  ):
210
- session = session or PromptSession()
210
+ prompt_session = prompt_session or PromptSession()
211
211
  console = console or Console(color_system="auto")
212
212
 
213
213
  if show_table:
214
214
  console.print(table)
215
215
 
216
- selection = await session.prompt_async(
216
+ selection = await prompt_session.prompt_async(
217
217
  message=prompt_message,
218
218
  validator=int_range_validator(min_index, max_index),
219
219
  default=default_selection,
@@ -226,18 +226,18 @@ async def prompt_for_selection(
226
226
  table: Table,
227
227
  default_selection: str = "",
228
228
  console: Console | None = None,
229
- session: PromptSession | None = None,
229
+ prompt_session: PromptSession | None = None,
230
230
  prompt_message: str = "Select an option > ",
231
231
  show_table: bool = True,
232
232
  ) -> str:
233
233
  """Prompt the user to select a key from a set of options. Return the selected key."""
234
- session = session or PromptSession()
234
+ prompt_session = prompt_session or PromptSession()
235
235
  console = console or Console(color_system="auto")
236
236
 
237
237
  if show_table:
238
238
  console.print(table, justify="center")
239
239
 
240
- selected = await session.prompt_async(
240
+ selected = await prompt_session.prompt_async(
241
241
  message=prompt_message,
242
242
  validator=key_validator(keys),
243
243
  default=default_selection,
@@ -250,7 +250,7 @@ async def select_value_from_list(
250
250
  title: str,
251
251
  selections: Sequence[str],
252
252
  console: Console | None = None,
253
- session: PromptSession | None = None,
253
+ prompt_session: PromptSession | None = None,
254
254
  prompt_message: str = "Select an option > ",
255
255
  default_selection: str = "",
256
256
  columns: int = 4,
@@ -283,7 +283,7 @@ async def select_value_from_list(
283
283
  caption_style,
284
284
  highlight,
285
285
  )
286
- session = session or PromptSession()
286
+ prompt_session = prompt_session or PromptSession()
287
287
  console = console or Console(color_system="auto")
288
288
 
289
289
  selection_index = await prompt_for_index(
@@ -291,7 +291,7 @@ async def select_value_from_list(
291
291
  table,
292
292
  default_selection=default_selection,
293
293
  console=console,
294
- session=session,
294
+ prompt_session=prompt_session,
295
295
  prompt_message=prompt_message,
296
296
  )
297
297
 
@@ -302,12 +302,12 @@ async def select_key_from_dict(
302
302
  selections: dict[str, SelectionOption],
303
303
  table: Table,
304
304
  console: Console | None = None,
305
- session: PromptSession | None = None,
305
+ prompt_session: PromptSession | None = None,
306
306
  prompt_message: str = "Select an option > ",
307
307
  default_selection: str = "",
308
308
  ) -> Any:
309
309
  """Prompt for a key from a dict, returns the key."""
310
- session = session or PromptSession()
310
+ prompt_session = prompt_session or PromptSession()
311
311
  console = console or Console(color_system="auto")
312
312
 
313
313
  console.print(table)
@@ -317,7 +317,7 @@ async def select_key_from_dict(
317
317
  table,
318
318
  default_selection=default_selection,
319
319
  console=console,
320
- session=session,
320
+ prompt_session=prompt_session,
321
321
  prompt_message=prompt_message,
322
322
  )
323
323
 
@@ -326,12 +326,12 @@ async def select_value_from_dict(
326
326
  selections: dict[str, SelectionOption],
327
327
  table: Table,
328
328
  console: Console | None = None,
329
- session: PromptSession | None = None,
329
+ prompt_session: PromptSession | None = None,
330
330
  prompt_message: str = "Select an option > ",
331
331
  default_selection: str = "",
332
332
  ) -> Any:
333
333
  """Prompt for a key from a dict, but return the value."""
334
- session = session or PromptSession()
334
+ prompt_session = prompt_session or PromptSession()
335
335
  console = console or Console(color_system="auto")
336
336
 
337
337
  console.print(table)
@@ -341,7 +341,7 @@ async def select_value_from_dict(
341
341
  table,
342
342
  default_selection=default_selection,
343
343
  console=console,
344
- session=session,
344
+ prompt_session=prompt_session,
345
345
  prompt_message=prompt_message,
346
346
  )
347
347
 
@@ -352,7 +352,7 @@ async def get_selection_from_dict_menu(
352
352
  title: str,
353
353
  selections: dict[str, SelectionOption],
354
354
  console: Console | None = None,
355
- session: PromptSession | None = None,
355
+ prompt_session: PromptSession | None = None,
356
356
  prompt_message: str = "Select an option > ",
357
357
  default_selection: str = "",
358
358
  ):
@@ -366,7 +366,7 @@ async def get_selection_from_dict_menu(
366
366
  selections,
367
367
  table,
368
368
  console,
369
- session,
369
+ prompt_session,
370
370
  prompt_message,
371
371
  default_selection,
372
372
  )
@@ -26,7 +26,7 @@ class SelectionAction(BaseAction):
26
26
  def __init__(
27
27
  self,
28
28
  name: str,
29
- selections: list[str] | dict[str, SelectionOption],
29
+ selections: list[str] | set[str] | tuple[str, ...] | dict[str, SelectionOption],
30
30
  *,
31
31
  title: str = "Select an option",
32
32
  columns: int = 2,
@@ -36,7 +36,7 @@ class SelectionAction(BaseAction):
36
36
  inject_last_result_as: str = "last_result",
37
37
  return_key: bool = False,
38
38
  console: Console | None = None,
39
- session: PromptSession | None = None,
39
+ prompt_session: PromptSession | None = None,
40
40
  never_prompt: bool = False,
41
41
  show_table: bool = True,
42
42
  ):
@@ -51,7 +51,7 @@ class SelectionAction(BaseAction):
51
51
  self.title = title
52
52
  self.columns = columns
53
53
  self.console = console or Console(color_system="auto")
54
- self.session = session or PromptSession()
54
+ self.prompt_session = prompt_session or PromptSession()
55
55
  self.default_selection = default_selection
56
56
  self.prompt_message = prompt_message
57
57
  self.show_table = show_table
@@ -61,9 +61,11 @@ class SelectionAction(BaseAction):
61
61
  return self._selections
62
62
 
63
63
  @selections.setter
64
- def selections(self, value: list[str] | dict[str, SelectionOption]):
65
- if isinstance(value, list):
66
- self._selections: list[str] | CaseInsensitiveDict = value
64
+ def selections(
65
+ self, value: list[str] | set[str] | tuple[str, ...] | dict[str, SelectionOption]
66
+ ):
67
+ if isinstance(value, (list, tuple, set)):
68
+ self._selections: list[str] | CaseInsensitiveDict = list(value)
67
69
  elif isinstance(value, dict):
68
70
  cid = CaseInsensitiveDict()
69
71
  cid.update(value)
@@ -123,7 +125,7 @@ class SelectionAction(BaseAction):
123
125
  table,
124
126
  default_selection=effective_default,
125
127
  console=self.console,
126
- session=self.session,
128
+ prompt_session=self.prompt_session,
127
129
  prompt_message=self.prompt_message,
128
130
  show_table=self.show_table,
129
131
  )
@@ -140,7 +142,7 @@ class SelectionAction(BaseAction):
140
142
  table,
141
143
  default_selection=effective_default,
142
144
  console=self.console,
143
- session=self.session,
145
+ prompt_session=self.prompt_session,
144
146
  prompt_message=self.prompt_message,
145
147
  show_table=self.show_table,
146
148
  )
@@ -0,0 +1 @@
1
+ __version__ = "0.1.19"
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "falyx"
3
- version = "0.1.18"
3
+ version = "0.1.19"
4
4
  description = "Reliable and introspectable async CLI action framework."
5
5
  authors = ["Roland Thomas Jr <roland@rtj.dev>"]
6
6
  license = "MIT"
Binary file
@@ -1 +0,0 @@
1
- __version__ = "0.1.18"
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