falyx 0.1.27__py3-none-any.whl → 0.1.29__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.
@@ -2,7 +2,7 @@
2
2
  """parsers.py
3
3
  This module contains the argument parsers used for the Falyx CLI.
4
4
  """
5
- from argparse import ArgumentParser, Namespace, _SubParsersAction
5
+ from argparse import REMAINDER, ArgumentParser, Namespace, _SubParsersAction
6
6
  from dataclasses import asdict, dataclass
7
7
  from typing import Any, Sequence
8
8
 
@@ -114,6 +114,12 @@ def get_arg_parsers(
114
114
  help="Skip confirmation prompts",
115
115
  )
116
116
 
117
+ run_group.add_argument(
118
+ "command_args",
119
+ nargs=REMAINDER,
120
+ help="Arguments to pass to the command (if applicable)",
121
+ )
122
+
117
123
  run_all_parser = subparsers.add_parser(
118
124
  "run-all", help="Run all commands with a given tag"
119
125
  )
@@ -0,0 +1,71 @@
1
+ import inspect
2
+ from typing import Any, Callable
3
+
4
+ from falyx import logger
5
+
6
+
7
+ def infer_args_from_func(
8
+ func: Callable[[Any], Any],
9
+ arg_metadata: dict[str, str | dict[str, Any]] | None = None,
10
+ ) -> list[dict[str, Any]]:
11
+ """
12
+ Infer argument definitions from a callable's signature.
13
+ Returns a list of kwargs suitable for CommandArgumentParser.add_argument.
14
+ """
15
+ arg_metadata = arg_metadata or {}
16
+ signature = inspect.signature(func)
17
+ arg_defs = []
18
+
19
+ for name, param in signature.parameters.items():
20
+ raw_metadata = arg_metadata.get(name, {})
21
+ metadata = (
22
+ {"help": raw_metadata} if isinstance(raw_metadata, str) else raw_metadata
23
+ )
24
+
25
+ if param.kind not in (
26
+ inspect.Parameter.POSITIONAL_ONLY,
27
+ inspect.Parameter.POSITIONAL_OR_KEYWORD,
28
+ inspect.Parameter.KEYWORD_ONLY,
29
+ ):
30
+ continue
31
+
32
+ arg_type = (
33
+ param.annotation if param.annotation is not inspect.Parameter.empty else str
34
+ )
35
+ default = param.default if param.default is not inspect.Parameter.empty else None
36
+ is_required = param.default is inspect.Parameter.empty
37
+ if is_required:
38
+ flags = [f"{name.replace('_', '-')}"]
39
+ else:
40
+ flags = [f"--{name.replace('_', '-')}"]
41
+ action = "store"
42
+ nargs: int | str = 1
43
+
44
+ if arg_type is bool:
45
+ if param.default is False:
46
+ action = "store_true"
47
+ else:
48
+ action = "store_false"
49
+
50
+ if arg_type is list:
51
+ action = "append"
52
+ if is_required:
53
+ nargs = "+"
54
+ else:
55
+ nargs = "*"
56
+
57
+ arg_defs.append(
58
+ {
59
+ "flags": flags,
60
+ "dest": name,
61
+ "type": arg_type,
62
+ "default": default,
63
+ "required": is_required,
64
+ "nargs": nargs,
65
+ "action": action,
66
+ "help": metadata.get("help", ""),
67
+ "choices": metadata.get("choices"),
68
+ }
69
+ )
70
+
71
+ return arg_defs
falyx/parsers/utils.py ADDED
@@ -0,0 +1,33 @@
1
+ from typing import Any
2
+
3
+ from falyx import logger
4
+ from falyx.action.action import Action, ChainedAction, ProcessAction
5
+ from falyx.parsers.signature import infer_args_from_func
6
+
7
+
8
+ def same_argument_definitions(
9
+ actions: list[Any],
10
+ arg_metadata: dict[str, str | dict[str, Any]] | None = None,
11
+ ) -> list[dict[str, Any]] | None:
12
+ arg_sets = []
13
+ for action in actions:
14
+ if isinstance(action, (Action, ProcessAction)):
15
+ arg_defs = infer_args_from_func(action.action, arg_metadata)
16
+ elif isinstance(action, ChainedAction):
17
+ if action.actions:
18
+ action = action.actions[0]
19
+ if isinstance(action, Action):
20
+ arg_defs = infer_args_from_func(action.action, arg_metadata)
21
+ elif callable(action):
22
+ arg_defs = infer_args_from_func(action, arg_metadata)
23
+ elif callable(action):
24
+ arg_defs = infer_args_from_func(action, arg_metadata)
25
+ else:
26
+ logger.debug("Auto args unsupported for action: %s", action)
27
+ return None
28
+ arg_sets.append(arg_defs)
29
+
30
+ first = arg_sets[0]
31
+ if all(arg_set == first for arg_set in arg_sets[1:]):
32
+ return first
33
+ return None
falyx/protocols.py CHANGED
@@ -2,10 +2,16 @@
2
2
  """protocols.py"""
3
3
  from __future__ import annotations
4
4
 
5
- from typing import Any, Awaitable, Protocol
5
+ from typing import Any, Awaitable, Protocol, runtime_checkable
6
6
 
7
7
  from falyx.action.action import BaseAction
8
8
 
9
9
 
10
+ @runtime_checkable
10
11
  class ActionFactoryProtocol(Protocol):
11
12
  async def __call__(self, *args: Any, **kwargs: Any) -> Awaitable[BaseAction]: ...
13
+
14
+
15
+ @runtime_checkable
16
+ class ArgParserProtocol(Protocol):
17
+ def __call__(self, args: list[str]) -> tuple[tuple, dict]: ...
falyx/signals.py CHANGED
@@ -29,3 +29,10 @@ class CancelSignal(FlowSignal):
29
29
 
30
30
  def __init__(self, message: str = "Cancel signal received."):
31
31
  super().__init__(message)
32
+
33
+
34
+ class HelpSignal(FlowSignal):
35
+ """Raised to display help information."""
36
+
37
+ def __init__(self, message: str = "Help signal received."):
38
+ super().__init__(message)
falyx/version.py CHANGED
@@ -1 +1 @@
1
- __version__ = "0.1.27"
1
+ __version__ = "0.1.29"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: falyx
3
- Version: 0.1.27
3
+ Version: 0.1.29
4
4
  Summary: Reliable and introspectable async CLI action framework.
5
5
  License: MIT
6
6
  Author: Roland Thomas Jr
@@ -1,11 +1,12 @@
1
1
  falyx/.pytyped,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  falyx/__init__.py,sha256=L40665QyjAqHQxHdxxY2_yPeDa4p0LE7Nu_2dkm08Ls,650
3
3
  falyx/__main__.py,sha256=g_LwJieofK3DJzCYtpkAMEeOXhzSLQenb7pRVUqcf-Y,2152
4
+ falyx/action/.pytyped,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
5
  falyx/action/__init__.py,sha256=zpOK5g4DybydV8d3QI0Zq52aWaKFPYi-J6szAQTsQ2c,974
5
6
  falyx/action/action.py,sha256=CJB9eeeEqBGkZHjMpG24eXHRjouKSfESCI1zWzoE7JQ,32488
6
7
  falyx/action/action_factory.py,sha256=qNtEnsbKsNl-WijChbTQYfdI3k14fN-1bzDsGFx8yZI,4517
7
8
  falyx/action/http_action.py,sha256=aIieGHyZSkz1ZGay-fwgDYZ0QF17XypAWtKeVAYp5f4,5806
8
- falyx/action/io_action.py,sha256=zdDq07zSLlaShBQ3ztXTRC6aZL0JoERNZSmvHy1V22w,9718
9
+ falyx/action/io_action.py,sha256=Xy4k4Zx5qrrRs7Y7NZE5qCzAnHe6iJMkVrPZ8KpDD0k,9850
9
10
  falyx/action/menu_action.py,sha256=cboCpXyl0fZUxpFsvEPu0dGhFfr_vdfllceQnICA0gU,5683
10
11
  falyx/action/select_file_action.py,sha256=hHLhmTSacWaUXhRTeIIiXt8gR7zbjkXJ2MAkKQYCpp4,7799
11
12
  falyx/action/selection_action.py,sha256=22rF7UqRrQAMjGIheDqAbUizVMBg9aCl9e4VOLLZZJo,8811
@@ -13,35 +14,39 @@ falyx/action/signal_action.py,sha256=5UMqvzy7fBnLANGwYUWoe1VRhrr7e-yOVeLdOnCBiJo
13
14
  falyx/action/types.py,sha256=iVD-bHm1GRXOTIlHOeT_KcDBRZm4Hz5Xzl_BOalvEf4,961
14
15
  falyx/action/user_input_action.py,sha256=LSTzC_3TfsfXdz-qV3GlOIGpZWAOgO9J5DnNsHO7ee8,3398
15
16
  falyx/bottom_bar.py,sha256=iWxgOKWgn5YmREeZBuGA50FzqzEfz1-Vnqm0V_fhldc,7383
16
- falyx/command.py,sha256=CeleZJ039996d6qn895JXagLeh7gMZltx7jABecjSXY,12224
17
+ falyx/command.py,sha256=YTkLFzVwKNmVkRRL237qbEMD8lpYycRaVtKVn4rt2OQ,16177
17
18
  falyx/config.py,sha256=8dkQfL-Ro-vWw1AcO2fD1PGZ92Cyfnwl885ZlpLkp4Y,9636
18
- falyx/config_schema.py,sha256=j5GQuHVlaU-VLxLF9t8idZRjqOP9MIKp1hyd9NhpAGU,3124
19
19
  falyx/context.py,sha256=FNF-IS7RMDxel2l3kskEqQImZ0mLO6zvGw_xC9cIzgI,10338
20
20
  falyx/debug.py,sha256=oWWTLOF8elrx_RGZ1G4pbzfFr46FjB0woFXpVU2wmjU,1567
21
- falyx/exceptions.py,sha256=Qxp6UScZWEyno-6Lgksrv3s9iwjbr2U-d6hun-_xpc0,798
21
+ falyx/exceptions.py,sha256=kK9k1v7LVNjJSwYztRa9Krhr3ZOI-6Htq2ZjlYICPKg,922
22
22
  falyx/execution_registry.py,sha256=rctsz0mrIHPToLZqylblVjDdKWdq1x_JBc8GwMP5sJ8,4710
23
- falyx/falyx.py,sha256=ECL6nDgqxS0s8lzOlMnBOSqwZEsLN0kYzeBCs0lUsYI,40860
23
+ falyx/falyx.py,sha256=TX446soM31kq3pNCsGWcrxDtZoQQlgtcfRCJ1OMq5XY,44595
24
24
  falyx/hook_manager.py,sha256=GuGxVVz9FXrU39Tk220QcsLsMXeut7ZDkGW3hU9GcwQ,2952
25
25
  falyx/hooks.py,sha256=IV2nbj5FjY2m3_L7x4mYBnaRDG45E8tWQU90i4butlw,2940
26
26
  falyx/init.py,sha256=abcSlPmxVeByLIHdUkNjqtO_tEkO3ApC6f9WbxsSEWg,3393
27
27
  falyx/logger.py,sha256=1Mfb_vJFJ1tQwziuyU2p-cSMi2Js8N2byniFEnI6vOQ,132
28
28
  falyx/menu.py,sha256=faxGgocqQYY6HtzVbenHaFj8YqsmycBEyziC8Ahzqjo,2870
29
29
  falyx/options_manager.py,sha256=dFAnQw543tQ6Xupvh1PwBrhiSWlSACHw8K-sHP_lUh4,2842
30
- falyx/parsers.py,sha256=hxrBouQEqdgk6aWzNa7UwTg7u55vJffSEUUTiiQoI0U,5602
30
+ falyx/parsers/.pytyped,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
31
+ falyx/parsers/__init__.py,sha256=Z5HjwqVlf2Bp8STSz3XMMYWWkkerRS0MDGf_Y4KXGVA,520
32
+ falyx/parsers/argparse.py,sha256=ElWg4nl1ygkDY9Q4lTfjj4KMXI0FKDLVdc08eiGw4AQ,29889
33
+ falyx/parsers/parsers.py,sha256=KsDFEmJLM86d2X4Kh4SHA9mBbUk351NjLhhFYzQkaPk,5762
34
+ falyx/parsers/signature.py,sha256=mDMgg-H27OKqrORksIbQSETfEUoL_DaHKVnDviCLsCQ,2125
35
+ falyx/parsers/utils.py,sha256=3kjvrYBnvw90b1ALrAaRWUkQU7axLY9XqJOmf8W1BAQ,1230
31
36
  falyx/prompt_utils.py,sha256=qgk0bXs7mwzflqzWyFhEOTpKQ_ZtMIqGhKeg-ocwNnE,1542
32
- falyx/protocols.py,sha256=ejSz18D8Qg63ONdgwbvn2YanKK9bGF0e3Bjxh9y3Buc,334
37
+ falyx/protocols.py,sha256=mesdq5CjPF_5Kyu7Evwr6qMT71tUHlw0SjjtmnggTZw,495
33
38
  falyx/retry.py,sha256=UUzY6FlKobr84Afw7yJO9rj3AIQepDk2fcWs6_1gi6Q,3788
34
39
  falyx/retry_utils.py,sha256=EAzc-ECTu8AxKkmlw28ioOW9y-Y9tLQ0KasvSkBRYgs,694
35
40
  falyx/selection.py,sha256=l2LLISqgP8xfHdcTAEbTTqs_Bae4-LVUKMN7VQH7tM0,10731
36
- falyx/signals.py,sha256=PGlc0Cm8DfUB3lCf58u_kwTwm2XUEFQ2joFq0Qc_GXI,906
41
+ falyx/signals.py,sha256=Y_neFXpfHs7qY0syw9XcfR9WeAGRcRw1nG_2L1JJqKE,1083
37
42
  falyx/tagged_table.py,sha256=4SV-SdXFrAhy1JNToeBCvyxT-iWVf6cWY7XETTys4n8,1067
38
43
  falyx/themes/__init__.py,sha256=1CZhEUCin9cUk8IGYBUFkVvdHRNNJBEFXccHwpUKZCA,284
39
44
  falyx/themes/colors.py,sha256=4aaeAHJetmeNInI0Zytg4E3YqKfPFelpf04vtjSvsS8,19776
40
45
  falyx/utils.py,sha256=u3puR4Bh-unNBw9a0V9sw7PDTIzRaNLolap0oz5bVIk,6718
41
46
  falyx/validators.py,sha256=t5iyzVpY8tdC4rfhr4isEfWpD5gNTzjeX_Hbi_Uq6sA,1328
42
- falyx/version.py,sha256=vEF032D64gj-9WJp4kp0yS1eFIq4XHIqJr91sJJNwWg,23
43
- falyx-0.1.27.dist-info/LICENSE,sha256=B0yqgaHuSdhN7T3OBmgQSiDTy8HqT5Oe_dLypRe4Ra4,1073
44
- falyx-0.1.27.dist-info/METADATA,sha256=rs1IR_MPVQKge3QAZjwHUbh3sOfIutI5qckcMIaVR5w,5521
45
- falyx-0.1.27.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
46
- falyx-0.1.27.dist-info/entry_points.txt,sha256=j8owOSl2j1Ss8DtGMnKfgehKaolqnIPhVFHaUBLUnMs,45
47
- falyx-0.1.27.dist-info/RECORD,,
47
+ falyx/version.py,sha256=A-lFHZ4YpCrWZ6nw3tlt_yurFJ00mInm3gR6hz51Eww,23
48
+ falyx-0.1.29.dist-info/LICENSE,sha256=B0yqgaHuSdhN7T3OBmgQSiDTy8HqT5Oe_dLypRe4Ra4,1073
49
+ falyx-0.1.29.dist-info/METADATA,sha256=cUcRdSgrw8TPfn21pRyn7mS-WrvpvWChdwPd4ikSCQQ,5521
50
+ falyx-0.1.29.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
51
+ falyx-0.1.29.dist-info/entry_points.txt,sha256=j8owOSl2j1Ss8DtGMnKfgehKaolqnIPhVFHaUBLUnMs,45
52
+ falyx-0.1.29.dist-info/RECORD,,
falyx/config_schema.py DELETED
@@ -1,76 +0,0 @@
1
- FALYX_CONFIG_SCHEMA = {
2
- "$schema": "http://json-schema.org/draft-07/schema#",
3
- "title": "Falyx CLI Config",
4
- "type": "object",
5
- "properties": {
6
- "title": {"type": "string", "description": "Title shown at top of menu"},
7
- "prompt": {
8
- "oneOf": [
9
- {"type": "string"},
10
- {
11
- "type": "array",
12
- "items": {
13
- "type": "array",
14
- "prefixItems": [
15
- {
16
- "type": "string",
17
- "description": "Style string (e.g., 'bold #ff0000 italic')",
18
- },
19
- {"type": "string", "description": "Text content"},
20
- ],
21
- "minItems": 2,
22
- "maxItems": 2,
23
- },
24
- },
25
- ]
26
- },
27
- "columns": {
28
- "type": "integer",
29
- "minimum": 1,
30
- "description": "Number of menu columns",
31
- },
32
- "welcome_message": {"type": "string"},
33
- "exit_message": {"type": "string"},
34
- "commands": {
35
- "type": "array",
36
- "items": {
37
- "type": "object",
38
- "required": ["key", "description", "action"],
39
- "properties": {
40
- "key": {"type": "string", "minLength": 1},
41
- "description": {"type": "string"},
42
- "action": {
43
- "type": "string",
44
- "pattern": "^[a-zA-Z_][a-zA-Z0-9_]*(\\.[a-zA-Z_][a-zA-Z0-9_]*)+$",
45
- "description": "Dotted import path (e.g., mymodule.task)",
46
- },
47
- "args": {"type": "array"},
48
- "kwargs": {"type": "object"},
49
- "aliases": {"type": "array", "items": {"type": "string"}},
50
- "tags": {"type": "array", "items": {"type": "string"}},
51
- "style": {"type": "string"},
52
- "confirm": {"type": "boolean"},
53
- "confirm_message": {"type": "string"},
54
- "preview_before_confirm": {"type": "boolean"},
55
- "spinner": {"type": "boolean"},
56
- "spinner_message": {"type": "string"},
57
- "spinner_type": {"type": "string"},
58
- "spinner_style": {"type": "string"},
59
- "logging_hooks": {"type": "boolean"},
60
- "retry": {"type": "boolean"},
61
- "retry_all": {"type": "boolean"},
62
- "retry_policy": {
63
- "type": "object",
64
- "properties": {
65
- "enabled": {"type": "boolean"},
66
- "max_retries": {"type": "integer"},
67
- "delay": {"type": "number"},
68
- "backoff": {"type": "number"},
69
- },
70
- },
71
- },
72
- },
73
- },
74
- },
75
- "required": ["commands"],
76
- }
File without changes