falyx 0.1.37__py3-none-any.whl → 0.1.39__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.
- falyx/__init__.py +0 -11
- falyx/action/__init__.py +9 -9
- falyx/action/action.py +4 -724
- falyx/action/action_factory.py +1 -1
- falyx/action/action_group.py +170 -0
- falyx/action/base.py +156 -0
- falyx/action/chained_action.py +208 -0
- falyx/action/fallback_action.py +49 -0
- falyx/action/io_action.py +1 -1
- falyx/action/literal_input_action.py +47 -0
- falyx/action/menu_action.py +1 -1
- falyx/action/mixins.py +33 -0
- falyx/action/process_action.py +128 -0
- falyx/action/process_pool_action.py +166 -0
- falyx/action/prompt_menu_action.py +1 -1
- falyx/action/select_file_action.py +1 -1
- falyx/action/selection_action.py +1 -2
- falyx/action/user_input_action.py +1 -1
- falyx/command.py +2 -1
- falyx/config.py +2 -1
- falyx/falyx.py +21 -13
- falyx/menu.py +1 -1
- falyx/parsers/argparse.py +152 -53
- falyx/parsers/utils.py +1 -1
- falyx/protocols.py +1 -1
- falyx/retry_utils.py +2 -1
- falyx/utils.py +1 -1
- falyx/version.py +1 -1
- {falyx-0.1.37.dist-info → falyx-0.1.39.dist-info}/METADATA +6 -5
- falyx-0.1.39.dist-info/RECORD +61 -0
- falyx-0.1.37.dist-info/RECORD +0 -53
- {falyx-0.1.37.dist-info → falyx-0.1.39.dist-info}/LICENSE +0 -0
- {falyx-0.1.37.dist-info → falyx-0.1.39.dist-info}/WHEEL +0 -0
- {falyx-0.1.37.dist-info → falyx-0.1.39.dist-info}/entry_points.txt +0 -0
falyx/parsers/argparse.py
CHANGED
@@ -10,6 +10,7 @@ from rich.console import Console
|
|
10
10
|
from rich.markup import escape
|
11
11
|
from rich.text import Text
|
12
12
|
|
13
|
+
from falyx.action.base import BaseAction
|
13
14
|
from falyx.exceptions import CommandArgumentError
|
14
15
|
from falyx.signals import HelpSignal
|
15
16
|
|
@@ -17,6 +18,7 @@ from falyx.signals import HelpSignal
|
|
17
18
|
class ArgumentAction(Enum):
|
18
19
|
"""Defines the action to be taken when the argument is encountered."""
|
19
20
|
|
21
|
+
ACTION = "action"
|
20
22
|
STORE = "store"
|
21
23
|
STORE_TRUE = "store_true"
|
22
24
|
STORE_FALSE = "store_false"
|
@@ -51,6 +53,7 @@ class Argument:
|
|
51
53
|
help: str = "" # Help text for the argument
|
52
54
|
nargs: int | str | None = None # int, '?', '*', '+', None
|
53
55
|
positional: bool = False # True if no leading - or -- in flags
|
56
|
+
resolver: BaseAction | None = None # Action object for the argument
|
54
57
|
|
55
58
|
def get_positional_text(self) -> str:
|
56
59
|
"""Get the positional text for the argument."""
|
@@ -104,6 +107,8 @@ class Argument:
|
|
104
107
|
and self.required == other.required
|
105
108
|
and self.nargs == other.nargs
|
106
109
|
and self.positional == other.positional
|
110
|
+
and self.default == other.default
|
111
|
+
and self.help == other.help
|
107
112
|
)
|
108
113
|
|
109
114
|
def __hash__(self) -> int:
|
@@ -117,6 +122,8 @@ class Argument:
|
|
117
122
|
self.required,
|
118
123
|
self.nargs,
|
119
124
|
self.positional,
|
125
|
+
self.default,
|
126
|
+
self.help,
|
120
127
|
)
|
121
128
|
)
|
122
129
|
|
@@ -159,8 +166,8 @@ class CommandArgumentParser:
|
|
159
166
|
self.help_epilogue: str = help_epilogue
|
160
167
|
self.aliases: list[str] = aliases or []
|
161
168
|
self._arguments: list[Argument] = []
|
162
|
-
self._positional:
|
163
|
-
self._keyword:
|
169
|
+
self._positional: dict[str, Argument] = {}
|
170
|
+
self._keyword: dict[str, Argument] = {}
|
164
171
|
self._flag_map: dict[str, Argument] = {}
|
165
172
|
self._dest_set: set[str] = set()
|
166
173
|
self._add_help()
|
@@ -220,6 +227,12 @@ class CommandArgumentParser:
|
|
220
227
|
if required:
|
221
228
|
return True
|
222
229
|
if positional:
|
230
|
+
assert (
|
231
|
+
nargs is None
|
232
|
+
or isinstance(nargs, int)
|
233
|
+
or isinstance(nargs, str)
|
234
|
+
and nargs in ("+", "*", "?")
|
235
|
+
), f"Invalid nargs value: {nargs}"
|
223
236
|
if isinstance(nargs, int):
|
224
237
|
return nargs > 0
|
225
238
|
elif isinstance(nargs, str):
|
@@ -227,8 +240,8 @@ class CommandArgumentParser:
|
|
227
240
|
return True
|
228
241
|
elif nargs in ("*", "?"):
|
229
242
|
return False
|
230
|
-
|
231
|
-
|
243
|
+
else:
|
244
|
+
return True
|
232
245
|
|
233
246
|
return required
|
234
247
|
|
@@ -247,7 +260,7 @@ class CommandArgumentParser:
|
|
247
260
|
)
|
248
261
|
return None
|
249
262
|
if nargs is None:
|
250
|
-
|
263
|
+
return None
|
251
264
|
allowed_nargs = ("?", "*", "+")
|
252
265
|
if isinstance(nargs, int):
|
253
266
|
if nargs <= 0:
|
@@ -308,6 +321,23 @@ class CommandArgumentParser:
|
|
308
321
|
f"Default list value {default!r} for '{dest}' cannot be coerced to {expected_type.__name__}"
|
309
322
|
)
|
310
323
|
|
324
|
+
def _validate_resolver(
|
325
|
+
self, action: ArgumentAction, resolver: BaseAction | None
|
326
|
+
) -> BaseAction | None:
|
327
|
+
"""Validate the action object."""
|
328
|
+
if action != ArgumentAction.ACTION and resolver is None:
|
329
|
+
return None
|
330
|
+
elif action == ArgumentAction.ACTION and resolver is None:
|
331
|
+
raise CommandArgumentError("resolver must be provided for ACTION action")
|
332
|
+
elif action != ArgumentAction.ACTION and resolver is not None:
|
333
|
+
raise CommandArgumentError(
|
334
|
+
f"resolver should not be provided for action {action}"
|
335
|
+
)
|
336
|
+
|
337
|
+
if not isinstance(resolver, BaseAction):
|
338
|
+
raise CommandArgumentError("resolver must be an instance of BaseAction")
|
339
|
+
return resolver
|
340
|
+
|
311
341
|
def _validate_action(
|
312
342
|
self, action: ArgumentAction | str, positional: bool
|
313
343
|
) -> ArgumentAction:
|
@@ -347,6 +377,8 @@ class CommandArgumentParser:
|
|
347
377
|
return 0
|
348
378
|
elif action in (ArgumentAction.APPEND, ArgumentAction.EXTEND):
|
349
379
|
return []
|
380
|
+
elif isinstance(nargs, int):
|
381
|
+
return []
|
350
382
|
elif nargs in ("+", "*"):
|
351
383
|
return []
|
352
384
|
else:
|
@@ -380,8 +412,15 @@ class CommandArgumentParser:
|
|
380
412
|
required: bool = False,
|
381
413
|
help: str = "",
|
382
414
|
dest: str | None = None,
|
415
|
+
resolver: BaseAction | None = None,
|
383
416
|
) -> None:
|
384
417
|
"""Add an argument to the parser.
|
418
|
+
For `ArgumentAction.ACTION`, `nargs` and `type` determine how many and what kind
|
419
|
+
of inputs are passed to the `resolver`.
|
420
|
+
|
421
|
+
The return value of the `resolver` is used directly (no type coercion is applied).
|
422
|
+
Validation, structure, and post-processing should be handled within the `resolver`.
|
423
|
+
|
385
424
|
Args:
|
386
425
|
name or flags: Either a name or prefixed flags (e.g. 'faylx', '-f', '--falyx').
|
387
426
|
action: The action to be taken when the argument is encountered.
|
@@ -392,6 +431,7 @@ class CommandArgumentParser:
|
|
392
431
|
required: Whether or not the argument is required.
|
393
432
|
help: A brief description of the argument.
|
394
433
|
dest: The name of the attribute to be added to the object returned by parse_args().
|
434
|
+
resolver: A BaseAction called with optional nargs specified parsed arguments.
|
395
435
|
"""
|
396
436
|
expected_type = type
|
397
437
|
self._validate_flags(flags)
|
@@ -403,8 +443,8 @@ class CommandArgumentParser:
|
|
403
443
|
"Merging multiple arguments into the same dest (e.g. positional + flagged) "
|
404
444
|
"is not supported. Define a unique 'dest' for each argument."
|
405
445
|
)
|
406
|
-
self._dest_set.add(dest)
|
407
446
|
action = self._validate_action(action, positional)
|
447
|
+
resolver = self._validate_resolver(action, resolver)
|
408
448
|
nargs = self._validate_nargs(nargs, action)
|
409
449
|
default = self._resolve_default(default, action, nargs)
|
410
450
|
if (
|
@@ -432,6 +472,7 @@ class CommandArgumentParser:
|
|
432
472
|
help=help,
|
433
473
|
nargs=nargs,
|
434
474
|
positional=positional,
|
475
|
+
resolver=resolver,
|
435
476
|
)
|
436
477
|
for flag in flags:
|
437
478
|
if flag in self._flag_map:
|
@@ -439,12 +480,14 @@ class CommandArgumentParser:
|
|
439
480
|
raise CommandArgumentError(
|
440
481
|
f"Flag '{flag}' is already used by argument '{existing.dest}'"
|
441
482
|
)
|
483
|
+
for flag in flags:
|
442
484
|
self._flag_map[flag] = argument
|
485
|
+
if not positional:
|
486
|
+
self._keyword[flag] = argument
|
487
|
+
self._dest_set.add(dest)
|
443
488
|
self._arguments.append(argument)
|
444
489
|
if positional:
|
445
|
-
self._positional
|
446
|
-
else:
|
447
|
-
self._keyword.append(argument)
|
490
|
+
self._positional[dest] = argument
|
448
491
|
|
449
492
|
def get_argument(self, dest: str) -> Argument | None:
|
450
493
|
return next((a for a in self._arguments if a.dest == dest), None)
|
@@ -462,6 +505,8 @@ class CommandArgumentParser:
|
|
462
505
|
"required": arg.required,
|
463
506
|
"nargs": arg.nargs,
|
464
507
|
"positional": arg.positional,
|
508
|
+
"default": arg.default,
|
509
|
+
"help": arg.help,
|
465
510
|
}
|
466
511
|
)
|
467
512
|
return defs
|
@@ -469,14 +514,17 @@ class CommandArgumentParser:
|
|
469
514
|
def _consume_nargs(
|
470
515
|
self, args: list[str], start: int, spec: Argument
|
471
516
|
) -> tuple[list[str], int]:
|
517
|
+
assert (
|
518
|
+
spec.nargs is None
|
519
|
+
or isinstance(spec.nargs, int)
|
520
|
+
or isinstance(spec.nargs, str)
|
521
|
+
and spec.nargs in ("+", "*", "?")
|
522
|
+
), f"Invalid nargs value: {spec.nargs}"
|
472
523
|
values = []
|
473
524
|
i = start
|
474
525
|
if isinstance(spec.nargs, int):
|
475
526
|
values = args[i : i + spec.nargs]
|
476
527
|
return values, i + spec.nargs
|
477
|
-
elif spec.nargs is None:
|
478
|
-
values = [args[i]]
|
479
|
-
return values, i + 1
|
480
528
|
elif spec.nargs == "+":
|
481
529
|
if i >= len(args):
|
482
530
|
raise CommandArgumentError(
|
@@ -496,10 +544,13 @@ class CommandArgumentParser:
|
|
496
544
|
if i < len(args) and not args[i].startswith("-"):
|
497
545
|
return [args[i]], i + 1
|
498
546
|
return [], i
|
499
|
-
|
500
|
-
|
547
|
+
elif spec.nargs is None:
|
548
|
+
if i < len(args) and not args[i].startswith("-"):
|
549
|
+
return [args[i]], i + 1
|
550
|
+
return [], i
|
551
|
+
assert False, "Invalid nargs value: shouldn't happen"
|
501
552
|
|
502
|
-
def _consume_all_positional_args(
|
553
|
+
async def _consume_all_positional_args(
|
503
554
|
self,
|
504
555
|
args: list[str],
|
505
556
|
result: dict[str, Any],
|
@@ -519,18 +570,22 @@ class CommandArgumentParser:
|
|
519
570
|
remaining = len(args) - i
|
520
571
|
min_required = 0
|
521
572
|
for next_spec in positional_args[j + 1 :]:
|
522
|
-
|
523
|
-
|
524
|
-
|
573
|
+
assert (
|
574
|
+
next_spec.nargs is None
|
575
|
+
or isinstance(next_spec.nargs, int)
|
576
|
+
or isinstance(next_spec.nargs, str)
|
577
|
+
and next_spec.nargs in ("+", "*", "?")
|
578
|
+
), f"Invalid nargs value: {spec.nargs}"
|
579
|
+
if next_spec.nargs is None:
|
525
580
|
min_required += 1
|
581
|
+
elif isinstance(next_spec.nargs, int):
|
582
|
+
min_required += next_spec.nargs
|
526
583
|
elif next_spec.nargs == "+":
|
527
584
|
min_required += 1
|
528
585
|
elif next_spec.nargs == "?":
|
529
586
|
min_required += 0
|
530
587
|
elif next_spec.nargs == "*":
|
531
588
|
min_required += 0
|
532
|
-
else:
|
533
|
-
assert False, "Invalid nargs value: shouldn't happen"
|
534
589
|
|
535
590
|
slice_args = args[i:] if is_last else args[i : i + (remaining - min_required)]
|
536
591
|
values, new_i = self._consume_nargs(slice_args, 0, spec)
|
@@ -542,10 +597,19 @@ class CommandArgumentParser:
|
|
542
597
|
raise CommandArgumentError(
|
543
598
|
f"Invalid value for '{spec.dest}': expected {spec.type.__name__}"
|
544
599
|
)
|
545
|
-
|
546
|
-
|
600
|
+
if spec.action == ArgumentAction.ACTION:
|
601
|
+
assert isinstance(
|
602
|
+
spec.resolver, BaseAction
|
603
|
+
), "resolver should be an instance of BaseAction"
|
604
|
+
try:
|
605
|
+
result[spec.dest] = await spec.resolver(*typed)
|
606
|
+
except Exception as error:
|
607
|
+
raise CommandArgumentError(
|
608
|
+
f"[{spec.dest}] Action failed: {error}"
|
609
|
+
) from error
|
610
|
+
elif spec.action == ArgumentAction.APPEND:
|
547
611
|
assert result.get(spec.dest) is not None, "dest should not be None"
|
548
|
-
if spec.nargs
|
612
|
+
if spec.nargs is None:
|
549
613
|
result[spec.dest].append(typed[0])
|
550
614
|
else:
|
551
615
|
result[spec.dest].append(typed)
|
@@ -565,6 +629,23 @@ class CommandArgumentParser:
|
|
565
629
|
|
566
630
|
return i
|
567
631
|
|
632
|
+
def _expand_posix_bundling(self, args: list[str]) -> list[str]:
|
633
|
+
"""Expand POSIX-style bundled arguments into separate arguments."""
|
634
|
+
expanded = []
|
635
|
+
for token in args:
|
636
|
+
if token.startswith("-") and not token.startswith("--") and len(token) > 2:
|
637
|
+
# POSIX bundle
|
638
|
+
# e.g. -abc -> -a -b -c
|
639
|
+
for char in token[1:]:
|
640
|
+
flag = f"-{char}"
|
641
|
+
arg = self._flag_map.get(flag)
|
642
|
+
if not arg:
|
643
|
+
raise CommandArgumentError(f"Unrecognized option: {flag}")
|
644
|
+
expanded.append(flag)
|
645
|
+
else:
|
646
|
+
expanded.append(token)
|
647
|
+
return expanded
|
648
|
+
|
568
649
|
async def parse_args(
|
569
650
|
self, args: list[str] | None = None, from_validate: bool = False
|
570
651
|
) -> dict[str, Any]:
|
@@ -572,22 +653,43 @@ class CommandArgumentParser:
|
|
572
653
|
if args is None:
|
573
654
|
args = []
|
574
655
|
|
656
|
+
args = self._expand_posix_bundling(args)
|
657
|
+
|
575
658
|
result = {arg.dest: deepcopy(arg.default) for arg in self._arguments}
|
576
659
|
positional_args = [arg for arg in self._arguments if arg.positional]
|
577
660
|
consumed_positional_indices: set[int] = set()
|
578
|
-
|
579
661
|
consumed_indices: set[int] = set()
|
662
|
+
|
580
663
|
i = 0
|
581
664
|
while i < len(args):
|
582
665
|
token = args[i]
|
583
|
-
if token in self.
|
584
|
-
spec = self.
|
666
|
+
if token in self._keyword:
|
667
|
+
spec = self._keyword[token]
|
585
668
|
action = spec.action
|
586
669
|
|
587
670
|
if action == ArgumentAction.HELP:
|
588
671
|
if not from_validate:
|
589
672
|
self.render_help()
|
590
673
|
raise HelpSignal()
|
674
|
+
elif action == ArgumentAction.ACTION:
|
675
|
+
assert isinstance(
|
676
|
+
spec.resolver, BaseAction
|
677
|
+
), "resolver should be an instance of BaseAction"
|
678
|
+
values, new_i = self._consume_nargs(args, i + 1, spec)
|
679
|
+
try:
|
680
|
+
typed_values = [spec.type(value) for value in values]
|
681
|
+
except ValueError:
|
682
|
+
raise CommandArgumentError(
|
683
|
+
f"Invalid value for '{spec.dest}': expected {spec.type.__name__}"
|
684
|
+
)
|
685
|
+
try:
|
686
|
+
result[spec.dest] = await spec.resolver(*typed_values)
|
687
|
+
except Exception as error:
|
688
|
+
raise CommandArgumentError(
|
689
|
+
f"[{spec.dest}] Action failed: {error}"
|
690
|
+
) from error
|
691
|
+
consumed_indices.update(range(i, new_i))
|
692
|
+
i = new_i
|
591
693
|
elif action == ArgumentAction.STORE_TRUE:
|
592
694
|
result[spec.dest] = True
|
593
695
|
consumed_indices.add(i)
|
@@ -609,13 +711,8 @@ class CommandArgumentParser:
|
|
609
711
|
raise CommandArgumentError(
|
610
712
|
f"Invalid value for '{spec.dest}': expected {spec.type.__name__}"
|
611
713
|
)
|
612
|
-
if spec.nargs
|
613
|
-
|
614
|
-
result[spec.dest].append(spec.type(values[0]))
|
615
|
-
except ValueError:
|
616
|
-
raise CommandArgumentError(
|
617
|
-
f"Invalid value for '{spec.dest}': expected {spec.type.__name__}"
|
618
|
-
)
|
714
|
+
if spec.nargs is None:
|
715
|
+
result[spec.dest].append(spec.type(values[0]))
|
619
716
|
else:
|
620
717
|
result[spec.dest].append(typed_values)
|
621
718
|
consumed_indices.update(range(i, new_i))
|
@@ -640,6 +737,10 @@ class CommandArgumentParser:
|
|
640
737
|
raise CommandArgumentError(
|
641
738
|
f"Invalid value for '{spec.dest}': expected {spec.type.__name__}"
|
642
739
|
)
|
740
|
+
if not typed_values and spec.nargs not in ("*", "?"):
|
741
|
+
raise CommandArgumentError(
|
742
|
+
f"Expected at least one value for '{spec.dest}'"
|
743
|
+
)
|
643
744
|
if (
|
644
745
|
spec.nargs in (None, 1, "?")
|
645
746
|
and spec.action != ArgumentAction.APPEND
|
@@ -651,6 +752,9 @@ class CommandArgumentParser:
|
|
651
752
|
result[spec.dest] = typed_values
|
652
753
|
consumed_indices.update(range(i, new_i))
|
653
754
|
i = new_i
|
755
|
+
elif token.startswith("-"):
|
756
|
+
# Handle unrecognized option
|
757
|
+
raise CommandArgumentError(f"Unrecognized flag: {token}")
|
654
758
|
else:
|
655
759
|
# Get the next flagged argument index if it exists
|
656
760
|
next_flagged_index = -1
|
@@ -660,8 +764,7 @@ class CommandArgumentParser:
|
|
660
764
|
break
|
661
765
|
if next_flagged_index == -1:
|
662
766
|
next_flagged_index = len(args)
|
663
|
-
|
664
|
-
args_consumed = self._consume_all_positional_args(
|
767
|
+
args_consumed = await self._consume_all_positional_args(
|
665
768
|
args[i:next_flagged_index],
|
666
769
|
result,
|
667
770
|
positional_args,
|
@@ -681,26 +784,22 @@ class CommandArgumentParser:
|
|
681
784
|
f"Invalid value for {spec.dest}: must be one of {spec.choices}"
|
682
785
|
)
|
683
786
|
|
787
|
+
if spec.action == ArgumentAction.ACTION:
|
788
|
+
continue
|
789
|
+
|
684
790
|
if isinstance(spec.nargs, int) and spec.nargs > 1:
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
791
|
+
assert isinstance(
|
792
|
+
result.get(spec.dest), list
|
793
|
+
), f"Invalid value for {spec.dest}: expected a list"
|
794
|
+
if not result[spec.dest] and not spec.required:
|
795
|
+
continue
|
689
796
|
if spec.action == ArgumentAction.APPEND:
|
690
|
-
if not isinstance(result[spec.dest], list):
|
691
|
-
raise CommandArgumentError(
|
692
|
-
f"Invalid value for {spec.dest}: expected a list"
|
693
|
-
)
|
694
797
|
for group in result[spec.dest]:
|
695
798
|
if len(group) % spec.nargs != 0:
|
696
799
|
raise CommandArgumentError(
|
697
800
|
f"Invalid number of values for {spec.dest}: expected a multiple of {spec.nargs}"
|
698
801
|
)
|
699
802
|
elif spec.action == ArgumentAction.EXTEND:
|
700
|
-
if not isinstance(result[spec.dest], list):
|
701
|
-
raise CommandArgumentError(
|
702
|
-
f"Invalid value for {spec.dest}: expected a list"
|
703
|
-
)
|
704
803
|
if len(result[spec.dest]) % spec.nargs != 0:
|
705
804
|
raise CommandArgumentError(
|
706
805
|
f"Invalid number of values for {spec.dest}: expected a multiple of {spec.nargs}"
|
@@ -737,7 +836,7 @@ class CommandArgumentParser:
|
|
737
836
|
# Options
|
738
837
|
# Add all keyword arguments to the options list
|
739
838
|
options_list = []
|
740
|
-
for arg in self._keyword:
|
839
|
+
for arg in self._keyword.values():
|
741
840
|
choice_text = arg.get_choice_text()
|
742
841
|
if choice_text:
|
743
842
|
options_list.extend([f"[{arg.flags[0]} {choice_text}]"])
|
@@ -745,7 +844,7 @@ class CommandArgumentParser:
|
|
745
844
|
options_list.extend([f"[{arg.flags[0]}]"])
|
746
845
|
|
747
846
|
# Add positional arguments to the options list
|
748
|
-
for arg in self._positional:
|
847
|
+
for arg in self._positional.values():
|
749
848
|
choice_text = arg.get_choice_text()
|
750
849
|
if isinstance(arg.nargs, int):
|
751
850
|
choice_text = " ".join([choice_text] * arg.nargs)
|
@@ -771,14 +870,14 @@ class CommandArgumentParser:
|
|
771
870
|
if self._arguments:
|
772
871
|
if self._positional:
|
773
872
|
self.console.print("[bold]positional:[/bold]")
|
774
|
-
for arg in self._positional:
|
873
|
+
for arg in self._positional.values():
|
775
874
|
flags = arg.get_positional_text()
|
776
875
|
arg_line = Text(f" {flags:<30} ")
|
777
876
|
help_text = arg.help or ""
|
778
877
|
arg_line.append(help_text)
|
779
878
|
self.console.print(arg_line)
|
780
879
|
self.console.print("[bold]options:[/bold]")
|
781
|
-
for arg in self._keyword:
|
880
|
+
for arg in self._keyword.values():
|
782
881
|
flags = ", ".join(arg.flags)
|
783
882
|
flags_choice = f"{flags} {arg.get_choice_text()}"
|
784
883
|
arg_line = Text(f" {flags_choice:<30} ")
|
@@ -807,8 +906,8 @@ class CommandArgumentParser:
|
|
807
906
|
required = sum(arg.required for arg in self._arguments)
|
808
907
|
return (
|
809
908
|
f"CommandArgumentParser(args={len(self._arguments)}, "
|
810
|
-
f"flags={len(self._flag_map)},
|
811
|
-
f"
|
909
|
+
f"flags={len(self._flag_map)}, keywords={len(self._keyword)}, "
|
910
|
+
f"positional={positional}, required={required})"
|
812
911
|
)
|
813
912
|
|
814
913
|
def __repr__(self) -> str:
|
falyx/parsers/utils.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
from typing import Any
|
2
2
|
|
3
|
+
from falyx.action.base import BaseAction
|
3
4
|
from falyx.logger import logger
|
4
5
|
from falyx.parsers.signature import infer_args_from_func
|
5
6
|
|
@@ -8,7 +9,6 @@ def same_argument_definitions(
|
|
8
9
|
actions: list[Any],
|
9
10
|
arg_metadata: dict[str, str | dict[str, Any]] | None = None,
|
10
11
|
) -> list[dict[str, Any]] | None:
|
11
|
-
from falyx.action.action import BaseAction
|
12
12
|
|
13
13
|
arg_sets = []
|
14
14
|
for action in actions:
|
falyx/protocols.py
CHANGED
falyx/retry_utils.py
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# Falyx CLI Framework — (c) 2025 rtj.dev LLC — MIT Licensed
|
2
2
|
"""retry_utils.py"""
|
3
|
-
from falyx.action.action import Action
|
3
|
+
from falyx.action.action import Action
|
4
|
+
from falyx.action.base import BaseAction
|
4
5
|
from falyx.hook_manager import HookType
|
5
6
|
from falyx.retry import RetryHandler, RetryPolicy
|
6
7
|
|
falyx/utils.py
CHANGED
@@ -184,7 +184,7 @@ def setup_logging(
|
|
184
184
|
console_handler.setLevel(console_log_level)
|
185
185
|
root.addHandler(console_handler)
|
186
186
|
|
187
|
-
file_handler = logging.FileHandler(log_filename)
|
187
|
+
file_handler = logging.FileHandler(log_filename, "a", "UTF-8")
|
188
188
|
file_handler.setLevel(file_log_level)
|
189
189
|
if json_log_to_file:
|
190
190
|
file_handler.setFormatter(
|
falyx/version.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__version__ = "0.1.
|
1
|
+
__version__ = "0.1.39"
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: falyx
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.39
|
4
4
|
Summary: Reliable and introspectable async CLI action framework.
|
5
5
|
License: MIT
|
6
6
|
Author: Roland Thomas Jr
|
@@ -75,7 +75,8 @@ poetry install
|
|
75
75
|
import asyncio
|
76
76
|
import random
|
77
77
|
|
78
|
-
from falyx import Falyx
|
78
|
+
from falyx import Falyx
|
79
|
+
from falyx.action import Action, ChainedAction
|
79
80
|
|
80
81
|
# A flaky async step that fails randomly
|
81
82
|
async def flaky_step():
|
@@ -85,8 +86,8 @@ async def flaky_step():
|
|
85
86
|
return "ok"
|
86
87
|
|
87
88
|
# Create the actions
|
88
|
-
step1 = Action(name="step_1", action=flaky_step
|
89
|
-
step2 = Action(name="step_2", action=flaky_step
|
89
|
+
step1 = Action(name="step_1", action=flaky_step)
|
90
|
+
step2 = Action(name="step_2", action=flaky_step)
|
90
91
|
|
91
92
|
# Chain the actions
|
92
93
|
chain = ChainedAction(name="my_pipeline", actions=[step1, step2])
|
@@ -97,9 +98,9 @@ falyx.add_command(
|
|
97
98
|
key="R",
|
98
99
|
description="Run My Pipeline",
|
99
100
|
action=chain,
|
100
|
-
logging_hooks=True,
|
101
101
|
preview_before_confirm=True,
|
102
102
|
confirm=True,
|
103
|
+
retry_all=True,
|
103
104
|
)
|
104
105
|
|
105
106
|
# Entry point
|
@@ -0,0 +1,61 @@
|
|
1
|
+
falyx/.pytyped,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
+
falyx/__init__.py,sha256=Gh88lQ5pbD7xbGWrBgslE2kSTZKY9TkvKSa53rZ3l8U,305
|
3
|
+
falyx/__main__.py,sha256=g_LwJieofK3DJzCYtpkAMEeOXhzSLQenb7pRVUqcf-Y,2152
|
4
|
+
falyx/action/.pytyped,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
5
|
+
falyx/action/__init__.py,sha256=4E3Rb0GgGcmggrPJh0YFiwbVgN_PQjIzL06-Z3qMReo,1247
|
6
|
+
falyx/action/action.py,sha256=w6xDbsB1SlMPSvpo2Dh0e11lRGP6a4E3K6AdfjlEqGY,5759
|
7
|
+
falyx/action/action_factory.py,sha256=br-P7Oip-4tZkO8qVT_ECwLe6idYjJa_GuBi5QR7vS4,4832
|
8
|
+
falyx/action/action_group.py,sha256=dfCEJM0RfdopuLFtfaxpvNzbZT6hVMrUuRBAFz--Uss,6835
|
9
|
+
falyx/action/base.py,sha256=W6jAG5qaZA0_6hcMZLCjSxUqIr1eVYw2YFkG20PiWQY,5861
|
10
|
+
falyx/action/chained_action.py,sha256=aV_plUdDVdc1o-oU57anbWkw33jgRIh4W29QwEA_1Mw,8501
|
11
|
+
falyx/action/fallback_action.py,sha256=0z5l0s_LKnhIwgMdykm8lJqs246DKSpyYs-p7PnsKok,1619
|
12
|
+
falyx/action/http_action.py,sha256=DNeSBWh58UTFGlfFyTk2GnhS54hpLAJLC0QNbq2cYic,5799
|
13
|
+
falyx/action/io_action.py,sha256=8x9HpvLhqJF7lI8PUo7Hs9F2NdJ1WfGs_wP5Myyoor8,10059
|
14
|
+
falyx/action/literal_input_action.py,sha256=7H2VX_L5VaytVdV2uis-VTGi782kQtwKTB8T04c7J1k,1293
|
15
|
+
falyx/action/menu_action.py,sha256=YGnfe6fNIi9-um1uiKn0NlcF3mdatvc4FsAS6xqhhmo,5759
|
16
|
+
falyx/action/mixins.py,sha256=eni8_PwzMnuwh0ZqOdzCdAyWlOphoiqL7z27xnFsg5s,1117
|
17
|
+
falyx/action/process_action.py,sha256=HsDqlKy1PkG3HHC6mHa4O6ayY_oKVY2qj5nDRJuSn24,4571
|
18
|
+
falyx/action/process_pool_action.py,sha256=1fFVEKpe-_XiMJxo4xF-j749Yd1noXjf76EOCPeX9xA,5940
|
19
|
+
falyx/action/prompt_menu_action.py,sha256=mt_XC4kZij6btGZn1r6nB-ko-FzCV57wBaLGps7cZmk,5163
|
20
|
+
falyx/action/select_file_action.py,sha256=umsE238ekkTRdM2pXcN9Q_srX-VDenEYXdh9CkTqqCw,8603
|
21
|
+
falyx/action/selection_action.py,sha256=5ZCdnsSvLZi9Gp5lcY61n4ff8bYHJOzw9DYMlgIMTZA,12894
|
22
|
+
falyx/action/signal_action.py,sha256=5UMqvzy7fBnLANGwYUWoe1VRhrr7e-yOVeLdOnCBiJo,1350
|
23
|
+
falyx/action/types.py,sha256=NfZz1ufZuvCgp-he2JIItbnjX7LjOUadjtKbjpRlSIY,1399
|
24
|
+
falyx/action/user_input_action.py,sha256=7kL5G7L0j2LuLvHu-CMwOaHyEisagE7O_2G2EhqWRr8,3483
|
25
|
+
falyx/bottom_bar.py,sha256=iWxgOKWgn5YmREeZBuGA50FzqzEfz1-Vnqm0V_fhldc,7383
|
26
|
+
falyx/command.py,sha256=QCuhbsTu93PPggGhhPTmL0iiR3AoY7XhFiO7FnRaYP0,14623
|
27
|
+
falyx/config.py,sha256=wfxKnhCyfwxHP0pLbYf-i8Tt1lBJlSeB-5DgI8JbnKM,9626
|
28
|
+
falyx/context.py,sha256=NfBpxzFzn-dYP6I3wrtGFucqm__UZo4SSBLmM8yYayE,10330
|
29
|
+
falyx/debug.py,sha256=IRpYtdH8yeXJEsfP5rASALmBQb2U_EwrTudF2GIDdZY,1545
|
30
|
+
falyx/exceptions.py,sha256=kK9k1v7LVNjJSwYztRa9Krhr3ZOI-6Htq2ZjlYICPKg,922
|
31
|
+
falyx/execution_registry.py,sha256=rctsz0mrIHPToLZqylblVjDdKWdq1x_JBc8GwMP5sJ8,4710
|
32
|
+
falyx/falyx.py,sha256=rz4md-dOMOuFsdZ8lLMeIFz82PKgj8fpeegZRtHOETs,44741
|
33
|
+
falyx/hook_manager.py,sha256=TFuHQnAncS_rk6vuw-VSx8bnAppLuHfrZCrzLwqcO9o,2979
|
34
|
+
falyx/hooks.py,sha256=xMfQROib0BNsaQF4AXJpmCiGePoE1f1xpcdibgnVZWM,2913
|
35
|
+
falyx/init.py,sha256=abcSlPmxVeByLIHdUkNjqtO_tEkO3ApC6f9WbxsSEWg,3393
|
36
|
+
falyx/logger.py,sha256=1Mfb_vJFJ1tQwziuyU2p-cSMi2Js8N2byniFEnI6vOQ,132
|
37
|
+
falyx/menu.py,sha256=E580qZsx08bnWcqRVjJuD2Fy8Zh_1zIexp5f0lC7L2c,3745
|
38
|
+
falyx/options_manager.py,sha256=dFAnQw543tQ6Xupvh1PwBrhiSWlSACHw8K-sHP_lUh4,2842
|
39
|
+
falyx/parsers/.pytyped,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
40
|
+
falyx/parsers/__init__.py,sha256=l0QMf89uJHhTpOqQfiV3tx7aAHvELqDFWAyjCbwEgBQ,370
|
41
|
+
falyx/parsers/argparse.py,sha256=P4iH0N5_vqzbw7dS9mU1HJ2TPhZMF6BN7t0RxRadXbM,36076
|
42
|
+
falyx/parsers/parsers.py,sha256=yGyAwNIJQz12LU_WF87aW4xbBmJtyEY7RnLRFqXAsio,5763
|
43
|
+
falyx/parsers/signature.py,sha256=i4iOiJxv70sxQYivKKXC_YOsShRUYfcI8Cjq8yVZvMo,2262
|
44
|
+
falyx/parsers/utils.py,sha256=Z4qLu8NVIprcHK2RDXoISpcDKBUii3n05G9mlgiPDgw,889
|
45
|
+
falyx/prompt_utils.py,sha256=qgk0bXs7mwzflqzWyFhEOTpKQ_ZtMIqGhKeg-ocwNnE,1542
|
46
|
+
falyx/protocols.py,sha256=-9GbCBUzzsEgw2_KOCYqxxzWJuez0eHmwnZp_ShY0jc,493
|
47
|
+
falyx/retry.py,sha256=sGRE9QhdZK98M99G8F15WUsJ_fYLNyLlCgu3UANaSQs,3744
|
48
|
+
falyx/retry_utils.py,sha256=vwoZmFVCGVqZ13BX_xi3qZZVsmSxkp-jfaf6kJtBV9c,723
|
49
|
+
falyx/selection.py,sha256=PLfiULkJ76cn1yBFwUux9KEMt6NFF00_Nj5oHS2e498,12894
|
50
|
+
falyx/signals.py,sha256=Y_neFXpfHs7qY0syw9XcfR9WeAGRcRw1nG_2L1JJqKE,1083
|
51
|
+
falyx/tagged_table.py,sha256=4SV-SdXFrAhy1JNToeBCvyxT-iWVf6cWY7XETTys4n8,1067
|
52
|
+
falyx/themes/__init__.py,sha256=1CZhEUCin9cUk8IGYBUFkVvdHRNNJBEFXccHwpUKZCA,284
|
53
|
+
falyx/themes/colors.py,sha256=4aaeAHJetmeNInI0Zytg4E3YqKfPFelpf04vtjSvsS8,19776
|
54
|
+
falyx/utils.py,sha256=U45xnZFUdoFC4xiji_9S1jHS5V7MvxSDtufP8EgB0SM,6732
|
55
|
+
falyx/validators.py,sha256=t5iyzVpY8tdC4rfhr4isEfWpD5gNTzjeX_Hbi_Uq6sA,1328
|
56
|
+
falyx/version.py,sha256=sfc5YqMhonehojmfXWQszjknUHF_Q-G6tt4E1pfsXOY,23
|
57
|
+
falyx-0.1.39.dist-info/LICENSE,sha256=B0yqgaHuSdhN7T3OBmgQSiDTy8HqT5Oe_dLypRe4Ra4,1073
|
58
|
+
falyx-0.1.39.dist-info/METADATA,sha256=jZJgj9PMmgsdng7LEe4t0jCTFfHXHuGwpwk6vy55o14,5517
|
59
|
+
falyx-0.1.39.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
|
60
|
+
falyx-0.1.39.dist-info/entry_points.txt,sha256=j8owOSl2j1Ss8DtGMnKfgehKaolqnIPhVFHaUBLUnMs,45
|
61
|
+
falyx-0.1.39.dist-info/RECORD,,
|
falyx-0.1.37.dist-info/RECORD
DELETED
@@ -1,53 +0,0 @@
|
|
1
|
-
falyx/.pytyped,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
-
falyx/__init__.py,sha256=MZzno-7HvIYM6pDjDP4t22aN7OaBTTlQYOb7W3Gw_7g,615
|
3
|
-
falyx/__main__.py,sha256=g_LwJieofK3DJzCYtpkAMEeOXhzSLQenb7pRVUqcf-Y,2152
|
4
|
-
falyx/action/.pytyped,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
5
|
-
falyx/action/__init__.py,sha256=Vd-XNp_MEpyfbHL9PAhSOXQAlwKKsIpZFbO5pskHdOA,1047
|
6
|
-
falyx/action/action.py,sha256=guC5ztULpozlsryqpH8GPYxOxpH2KDVhlcPkDoQYKZk,33350
|
7
|
-
falyx/action/action_factory.py,sha256=iJ7jx8WxQ9W2v1-xBoUzFD3X-3IFQjjClfGYCg5io4o,4834
|
8
|
-
falyx/action/http_action.py,sha256=DNeSBWh58UTFGlfFyTk2GnhS54hpLAJLC0QNbq2cYic,5799
|
9
|
-
falyx/action/io_action.py,sha256=tQVonWst44ZXR_87H6-aQ62DZf7qfpbBiKE1oTp9qVA,10061
|
10
|
-
falyx/action/menu_action.py,sha256=B4RBqjo0llYaAlblnO4XlRjVPhU89n7oiPKJLpzjwzc,5761
|
11
|
-
falyx/action/prompt_menu_action.py,sha256=8Qh8egbqiF9agTdeU45IrlehY6fxGc9rtyhIe0nfKK4,5165
|
12
|
-
falyx/action/select_file_action.py,sha256=JzKa989xxVOHv2WX6ql9VEBKPoaUolFH62rbIRKL5RU,8605
|
13
|
-
falyx/action/selection_action.py,sha256=8ts0WM3UF5ziMGLeg8faTVKwvzzOGPKxeZJF2z3o9BY,12918
|
14
|
-
falyx/action/signal_action.py,sha256=5UMqvzy7fBnLANGwYUWoe1VRhrr7e-yOVeLdOnCBiJo,1350
|
15
|
-
falyx/action/types.py,sha256=NfZz1ufZuvCgp-he2JIItbnjX7LjOUadjtKbjpRlSIY,1399
|
16
|
-
falyx/action/user_input_action.py,sha256=TQ7BlH9lQ5h_t28q6UXCH0hbr2b0vJP7syJmM-okwS0,3478
|
17
|
-
falyx/bottom_bar.py,sha256=iWxgOKWgn5YmREeZBuGA50FzqzEfz1-Vnqm0V_fhldc,7383
|
18
|
-
falyx/command.py,sha256=1ppsADoWLpMW1hPy1gVvp2OMeQlEkWFUz1yd5rdDvxg,14594
|
19
|
-
falyx/config.py,sha256=sApU53PHM8h8eV9YIuTDM1qSGp24AbNy_jJ6GAZhjEo,9597
|
20
|
-
falyx/context.py,sha256=NfBpxzFzn-dYP6I3wrtGFucqm__UZo4SSBLmM8yYayE,10330
|
21
|
-
falyx/debug.py,sha256=IRpYtdH8yeXJEsfP5rASALmBQb2U_EwrTudF2GIDdZY,1545
|
22
|
-
falyx/exceptions.py,sha256=kK9k1v7LVNjJSwYztRa9Krhr3ZOI-6Htq2ZjlYICPKg,922
|
23
|
-
falyx/execution_registry.py,sha256=rctsz0mrIHPToLZqylblVjDdKWdq1x_JBc8GwMP5sJ8,4710
|
24
|
-
falyx/falyx.py,sha256=_cbjz1OU1ZhuEucbOHWyVv21G9NwfeQAPhsrkJaDG2w,44427
|
25
|
-
falyx/hook_manager.py,sha256=TFuHQnAncS_rk6vuw-VSx8bnAppLuHfrZCrzLwqcO9o,2979
|
26
|
-
falyx/hooks.py,sha256=xMfQROib0BNsaQF4AXJpmCiGePoE1f1xpcdibgnVZWM,2913
|
27
|
-
falyx/init.py,sha256=abcSlPmxVeByLIHdUkNjqtO_tEkO3ApC6f9WbxsSEWg,3393
|
28
|
-
falyx/logger.py,sha256=1Mfb_vJFJ1tQwziuyU2p-cSMi2Js8N2byniFEnI6vOQ,132
|
29
|
-
falyx/menu.py,sha256=Dp_afAkRZoLe7s2DmNrePQelL-ZSpOELUFr-7pC_eGo,3740
|
30
|
-
falyx/options_manager.py,sha256=dFAnQw543tQ6Xupvh1PwBrhiSWlSACHw8K-sHP_lUh4,2842
|
31
|
-
falyx/parsers/.pytyped,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
32
|
-
falyx/parsers/__init__.py,sha256=l0QMf89uJHhTpOqQfiV3tx7aAHvELqDFWAyjCbwEgBQ,370
|
33
|
-
falyx/parsers/argparse.py,sha256=k8j0MiTwV53UqesZ-GsYSi7-T7XOOo7o1DB8UnFFZ7Q,31545
|
34
|
-
falyx/parsers/parsers.py,sha256=yGyAwNIJQz12LU_WF87aW4xbBmJtyEY7RnLRFqXAsio,5763
|
35
|
-
falyx/parsers/signature.py,sha256=i4iOiJxv70sxQYivKKXC_YOsShRUYfcI8Cjq8yVZvMo,2262
|
36
|
-
falyx/parsers/utils.py,sha256=MDYXYtbXlRU5t_oNNaB-FKQA4ZgdXcMk9VnU6Iwrgtg,895
|
37
|
-
falyx/prompt_utils.py,sha256=qgk0bXs7mwzflqzWyFhEOTpKQ_ZtMIqGhKeg-ocwNnE,1542
|
38
|
-
falyx/protocols.py,sha256=mesdq5CjPF_5Kyu7Evwr6qMT71tUHlw0SjjtmnggTZw,495
|
39
|
-
falyx/retry.py,sha256=sGRE9QhdZK98M99G8F15WUsJ_fYLNyLlCgu3UANaSQs,3744
|
40
|
-
falyx/retry_utils.py,sha256=EAzc-ECTu8AxKkmlw28ioOW9y-Y9tLQ0KasvSkBRYgs,694
|
41
|
-
falyx/selection.py,sha256=PLfiULkJ76cn1yBFwUux9KEMt6NFF00_Nj5oHS2e498,12894
|
42
|
-
falyx/signals.py,sha256=Y_neFXpfHs7qY0syw9XcfR9WeAGRcRw1nG_2L1JJqKE,1083
|
43
|
-
falyx/tagged_table.py,sha256=4SV-SdXFrAhy1JNToeBCvyxT-iWVf6cWY7XETTys4n8,1067
|
44
|
-
falyx/themes/__init__.py,sha256=1CZhEUCin9cUk8IGYBUFkVvdHRNNJBEFXccHwpUKZCA,284
|
45
|
-
falyx/themes/colors.py,sha256=4aaeAHJetmeNInI0Zytg4E3YqKfPFelpf04vtjSvsS8,19776
|
46
|
-
falyx/utils.py,sha256=u3puR4Bh-unNBw9a0V9sw7PDTIzRaNLolap0oz5bVIk,6718
|
47
|
-
falyx/validators.py,sha256=t5iyzVpY8tdC4rfhr4isEfWpD5gNTzjeX_Hbi_Uq6sA,1328
|
48
|
-
falyx/version.py,sha256=fZtrhA5kKp_n3T2sxpEh-YEt3P1ZAtGrg_Xuu1JX3ZQ,23
|
49
|
-
falyx-0.1.37.dist-info/LICENSE,sha256=B0yqgaHuSdhN7T3OBmgQSiDTy8HqT5Oe_dLypRe4Ra4,1073
|
50
|
-
falyx-0.1.37.dist-info/METADATA,sha256=8JFkRdWqbMFVlG16bZPKD6XABilRSFQcpeKQFxPfCJY,5521
|
51
|
-
falyx-0.1.37.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
|
52
|
-
falyx-0.1.37.dist-info/entry_points.txt,sha256=j8owOSl2j1Ss8DtGMnKfgehKaolqnIPhVFHaUBLUnMs,45
|
53
|
-
falyx-0.1.37.dist-info/RECORD,,
|