falyx 0.1.44__py3-none-any.whl → 0.1.45__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/command.py CHANGED
@@ -128,7 +128,7 @@ class Command(BaseModel):
128
128
  tags: list[str] = Field(default_factory=list)
129
129
  logging_hooks: bool = False
130
130
  options_manager: OptionsManager = Field(default_factory=OptionsManager)
131
- arg_parser: CommandArgumentParser = Field(default_factory=CommandArgumentParser)
131
+ arg_parser: CommandArgumentParser | None = None
132
132
  arguments: list[dict[str, Any]] = Field(default_factory=list)
133
133
  argument_config: Callable[[CommandArgumentParser], None] | None = None
134
134
  custom_parser: ArgParserProtocol | None = None
@@ -167,6 +167,12 @@ class Command(BaseModel):
167
167
  raw_args,
168
168
  )
169
169
  return ((), {})
170
+ if not isinstance(self.arg_parser, CommandArgumentParser):
171
+ logger.warning(
172
+ "[Command:%s] No argument parser configured, using default parsing.",
173
+ self.key,
174
+ )
175
+ return ((), {})
170
176
  return await self.arg_parser.parse_args_split(
171
177
  raw_args, from_validate=from_validate
172
178
  )
@@ -183,7 +189,9 @@ class Command(BaseModel):
183
189
  def get_argument_definitions(self) -> list[dict[str, Any]]:
184
190
  if self.arguments:
185
191
  return self.arguments
186
- elif callable(self.argument_config):
192
+ elif callable(self.argument_config) and isinstance(
193
+ self.arg_parser, CommandArgumentParser
194
+ ):
187
195
  self.argument_config(self.arg_parser)
188
196
  elif self.auto_args:
189
197
  if isinstance(self.action, BaseAction):
@@ -219,8 +227,17 @@ class Command(BaseModel):
219
227
  if self.logging_hooks and isinstance(self.action, BaseAction):
220
228
  register_debug_hooks(self.action.hooks)
221
229
 
222
- for arg_def in self.get_argument_definitions():
223
- self.arg_parser.add_argument(*arg_def.pop("flags"), **arg_def)
230
+ if self.arg_parser is None:
231
+ self.arg_parser = CommandArgumentParser(
232
+ command_key=self.key,
233
+ command_description=self.description,
234
+ command_style=self.style,
235
+ help_text=self.help_text,
236
+ help_epilogue=self.help_epilogue,
237
+ aliases=self.aliases,
238
+ )
239
+ for arg_def in self.get_argument_definitions():
240
+ self.arg_parser.add_argument(*arg_def.pop("flags"), **arg_def)
224
241
 
225
242
  def _inject_options_manager(self) -> None:
226
243
  """Inject the options manager into the action if applicable."""
falyx/config.py CHANGED
@@ -118,14 +118,6 @@ def convert_commands(raw_commands: list[dict[str, Any]]) -> list[Command]:
118
118
  commands = []
119
119
  for entry in raw_commands:
120
120
  raw_command = RawCommand(**entry)
121
- parser = CommandArgumentParser(
122
- command_key=raw_command.key,
123
- command_description=raw_command.description,
124
- command_style=raw_command.style,
125
- help_text=raw_command.help_text,
126
- help_epilogue=raw_command.help_epilogue,
127
- aliases=raw_command.aliases,
128
- )
129
121
  commands.append(
130
122
  Command.model_validate(
131
123
  {
@@ -133,7 +125,6 @@ def convert_commands(raw_commands: list[dict[str, Any]]) -> list[Command]:
133
125
  "action": wrap_if_needed(
134
126
  import_action(raw_command.action), name=raw_command.description
135
127
  ),
136
- "arg_parser": parser,
137
128
  }
138
129
  )
139
130
  )
falyx/falyx.py CHANGED
@@ -359,7 +359,6 @@ class Falyx:
359
359
  action=Action("Help", self._show_help),
360
360
  style=OneColors.LIGHT_YELLOW,
361
361
  arg_parser=parser,
362
- auto_args=False,
363
362
  )
364
363
 
365
364
  def _get_completer(self) -> WordCompleter:
@@ -655,15 +654,6 @@ class Falyx:
655
654
  "arg_parser must be an instance of CommandArgumentParser."
656
655
  )
657
656
  arg_parser = arg_parser
658
- else:
659
- arg_parser = CommandArgumentParser(
660
- command_key=key,
661
- command_description=description,
662
- command_style=style,
663
- help_text=help_text,
664
- help_epilogue=help_epilogue,
665
- aliases=aliases,
666
- )
667
657
 
668
658
  command = Command(
669
659
  key=key,
falyx/parsers/argparse.py CHANGED
@@ -12,6 +12,7 @@ from rich.text import Text
12
12
 
13
13
  from falyx.action.base import BaseAction
14
14
  from falyx.exceptions import CommandArgumentError
15
+ from falyx.parsers.utils import coerce_value
15
16
  from falyx.signals import HelpSignal
16
17
 
17
18
 
@@ -290,7 +291,7 @@ class CommandArgumentParser:
290
291
  for choice in choices:
291
292
  if not isinstance(choice, expected_type):
292
293
  try:
293
- expected_type(choice)
294
+ coerce_value(choice, expected_type)
294
295
  except Exception:
295
296
  raise CommandArgumentError(
296
297
  f"Invalid choice {choice!r}: not coercible to {expected_type.__name__}"
@@ -303,7 +304,7 @@ class CommandArgumentParser:
303
304
  """Validate the default value type."""
304
305
  if default is not None and not isinstance(default, expected_type):
305
306
  try:
306
- expected_type(default)
307
+ coerce_value(default, expected_type)
307
308
  except Exception:
308
309
  raise CommandArgumentError(
309
310
  f"Default value {default!r} for '{dest}' cannot be coerced to {expected_type.__name__}"
@@ -316,7 +317,7 @@ class CommandArgumentParser:
316
317
  for item in default:
317
318
  if not isinstance(item, expected_type):
318
319
  try:
319
- expected_type(item)
320
+ coerce_value(item, expected_type)
320
321
  except Exception:
321
322
  raise CommandArgumentError(
322
323
  f"Default list value {default!r} for '{dest}' cannot be coerced to {expected_type.__name__}"
@@ -595,7 +596,7 @@ class CommandArgumentParser:
595
596
  i += new_i
596
597
 
597
598
  try:
598
- typed = [spec.type(v) for v in values]
599
+ typed = [coerce_value(value, spec.type) for value in values]
599
600
  except Exception:
600
601
  raise CommandArgumentError(
601
602
  f"Invalid value for '{spec.dest}': expected {spec.type.__name__}"
@@ -680,7 +681,9 @@ class CommandArgumentParser:
680
681
  ), "resolver should be an instance of BaseAction"
681
682
  values, new_i = self._consume_nargs(args, i + 1, spec)
682
683
  try:
683
- typed_values = [spec.type(value) for value in values]
684
+ typed_values = [
685
+ coerce_value(value, spec.type) for value in values
686
+ ]
684
687
  except ValueError:
685
688
  raise CommandArgumentError(
686
689
  f"Invalid value for '{spec.dest}': expected {spec.type.__name__}"
@@ -709,7 +712,9 @@ class CommandArgumentParser:
709
712
  assert result.get(spec.dest) is not None, "dest should not be None"
710
713
  values, new_i = self._consume_nargs(args, i + 1, spec)
711
714
  try:
712
- typed_values = [spec.type(value) for value in values]
715
+ typed_values = [
716
+ coerce_value(value, spec.type) for value in values
717
+ ]
713
718
  except ValueError:
714
719
  raise CommandArgumentError(
715
720
  f"Invalid value for '{spec.dest}': expected {spec.type.__name__}"
@@ -724,7 +729,9 @@ class CommandArgumentParser:
724
729
  assert result.get(spec.dest) is not None, "dest should not be None"
725
730
  values, new_i = self._consume_nargs(args, i + 1, spec)
726
731
  try:
727
- typed_values = [spec.type(value) for value in values]
732
+ typed_values = [
733
+ coerce_value(value, spec.type) for value in values
734
+ ]
728
735
  except ValueError:
729
736
  raise CommandArgumentError(
730
737
  f"Invalid value for '{spec.dest}': expected {spec.type.__name__}"
@@ -735,7 +742,9 @@ class CommandArgumentParser:
735
742
  else:
736
743
  values, new_i = self._consume_nargs(args, i + 1, spec)
737
744
  try:
738
- typed_values = [spec.type(v) for v in values]
745
+ typed_values = [
746
+ coerce_value(value, spec.type) for value in values
747
+ ]
739
748
  except ValueError:
740
749
  raise CommandArgumentError(
741
750
  f"Invalid value for '{spec.dest}': expected {spec.type.__name__}"
falyx/parsers/utils.py CHANGED
@@ -1,10 +1,79 @@
1
- from typing import Any
1
+ import types
2
+ from datetime import datetime
3
+ from enum import EnumMeta
4
+ from typing import Any, Literal, Union, get_args, get_origin
5
+
6
+ from dateutil import parser as date_parser
2
7
 
3
8
  from falyx.action.base import BaseAction
4
9
  from falyx.logger import logger
5
10
  from falyx.parsers.signature import infer_args_from_func
6
11
 
7
12
 
13
+ def coerce_bool(value: str) -> bool:
14
+ if isinstance(value, bool):
15
+ return value
16
+ value = value.strip().lower()
17
+ if value in {"true", "1", "yes", "on"}:
18
+ return True
19
+ elif value in {"false", "0", "no", "off"}:
20
+ return False
21
+ return bool(value)
22
+
23
+
24
+ def coerce_enum(value: Any, enum_type: EnumMeta) -> Any:
25
+ if isinstance(value, enum_type):
26
+ return value
27
+
28
+ if isinstance(value, str):
29
+ try:
30
+ return enum_type[value]
31
+ except KeyError:
32
+ pass
33
+
34
+ base_type = type(next(iter(enum_type)).value)
35
+ print(base_type)
36
+ try:
37
+ coerced_value = base_type(value)
38
+ return enum_type(coerced_value)
39
+ except (ValueError, TypeError):
40
+ raise ValueError(f"Value '{value}' could not be coerced to enum type {enum_type}")
41
+
42
+
43
+ def coerce_value(value: str, target_type: type) -> Any:
44
+ origin = get_origin(target_type)
45
+ args = get_args(target_type)
46
+
47
+ if origin is Literal:
48
+ if value not in args:
49
+ raise ValueError(
50
+ f"Value '{value}' is not a valid literal for type {target_type}"
51
+ )
52
+ return value
53
+
54
+ if isinstance(target_type, types.UnionType) or get_origin(target_type) is Union:
55
+ for arg in args:
56
+ try:
57
+ return coerce_value(value, arg)
58
+ except Exception:
59
+ continue
60
+ raise ValueError(f"Value '{value}' could not be coerced to any of {args!r}")
61
+
62
+ if isinstance(target_type, EnumMeta):
63
+ return coerce_enum(value, target_type)
64
+
65
+ if target_type is bool:
66
+ return coerce_bool(value)
67
+
68
+ if target_type is datetime:
69
+ try:
70
+ return date_parser.parse(value)
71
+ except ValueError as e:
72
+ raise ValueError(f"Value '{value}' could not be parsed as a datetime") from e
73
+
74
+ return target_type(value)
75
+
76
+
8
77
  def same_argument_definitions(
9
78
  actions: list[Any],
10
79
  arg_metadata: dict[str, str | dict[str, Any]] | None = None,
falyx/version.py CHANGED
@@ -1 +1 @@
1
- __version__ = "0.1.44"
1
+ __version__ = "0.1.45"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: falyx
3
- Version: 0.1.44
3
+ Version: 0.1.45
4
4
  Summary: Reliable and introspectable async CLI action framework.
5
5
  License: MIT
6
6
  Author: Roland Thomas Jr
@@ -15,6 +15,7 @@ Classifier: Programming Language :: Python :: 3.13
15
15
  Requires-Dist: aiohttp (>=3.11,<4.0)
16
16
  Requires-Dist: prompt_toolkit (>=3.0,<4.0)
17
17
  Requires-Dist: pydantic (>=2.0,<3.0)
18
+ Requires-Dist: python-dateutil (>=2.8,<3.0)
18
19
  Requires-Dist: python-json-logger (>=3.3.0,<4.0.0)
19
20
  Requires-Dist: pyyaml (>=6.0,<7.0)
20
21
  Requires-Dist: rich (>=13.0,<14.0)
@@ -23,13 +23,13 @@ falyx/action/signal_action.py,sha256=5UMqvzy7fBnLANGwYUWoe1VRhrr7e-yOVeLdOnCBiJo
23
23
  falyx/action/types.py,sha256=NfZz1ufZuvCgp-he2JIItbnjX7LjOUadjtKbjpRlSIY,1399
24
24
  falyx/action/user_input_action.py,sha256=7kL5G7L0j2LuLvHu-CMwOaHyEisagE7O_2G2EhqWRr8,3483
25
25
  falyx/bottom_bar.py,sha256=iWxgOKWgn5YmREeZBuGA50FzqzEfz1-Vnqm0V_fhldc,7383
26
- falyx/command.py,sha256=x9IuXT3iAArX9MXZ5yLRyaeqIHqklPV_39UlMs2wuuQ,15752
27
- falyx/config.py,sha256=BBDRv5uj0teydBm25eTIvEl9hKQqGGGX-Z6H9EHOHHw,10077
26
+ falyx/command.py,sha256=ZILV-cUDBvz9nozVBizfuRERPQATF7wesGXl7RFlJqE,16420
27
+ falyx/config.py,sha256=ENaODVyGp870qNELjhaegyyrzP8xqahRWABZ2B6Y03g,9703
28
28
  falyx/context.py,sha256=NfBpxzFzn-dYP6I3wrtGFucqm__UZo4SSBLmM8yYayE,10330
29
29
  falyx/debug.py,sha256=IRpYtdH8yeXJEsfP5rASALmBQb2U_EwrTudF2GIDdZY,1545
30
30
  falyx/exceptions.py,sha256=kK9k1v7LVNjJSwYztRa9Krhr3ZOI-6Htq2ZjlYICPKg,922
31
31
  falyx/execution_registry.py,sha256=rctsz0mrIHPToLZqylblVjDdKWdq1x_JBc8GwMP5sJ8,4710
32
- falyx/falyx.py,sha256=nMFc-fRqab28gRB7ov62C1YILEZZOAJvExKL-d4tTiI,48088
32
+ falyx/falyx.py,sha256=_Aj7UxyFl8KAwZVuQW2aUuHQnHuXWBi6nKh2hsCxYis,47749
33
33
  falyx/hook_manager.py,sha256=TFuHQnAncS_rk6vuw-VSx8bnAppLuHfrZCrzLwqcO9o,2979
34
34
  falyx/hooks.py,sha256=xMfQROib0BNsaQF4AXJpmCiGePoE1f1xpcdibgnVZWM,2913
35
35
  falyx/init.py,sha256=VZ3rYMxo7g01EraYATdl_pRN4ZqrsVueo2ZFx54gojo,3326
@@ -38,10 +38,10 @@ falyx/menu.py,sha256=E580qZsx08bnWcqRVjJuD2Fy8Zh_1zIexp5f0lC7L2c,3745
38
38
  falyx/options_manager.py,sha256=dFAnQw543tQ6Xupvh1PwBrhiSWlSACHw8K-sHP_lUh4,2842
39
39
  falyx/parsers/.pytyped,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
40
40
  falyx/parsers/__init__.py,sha256=ZfPmbtEUechDvgl99-lWhTXmFnXS_FMXJ_xb8KGEJLo,448
41
- falyx/parsers/argparse.py,sha256=WB8InZJ_WOaqi6J80IfnxInRyJ7aBCP0sv0zalo8al0,36924
41
+ falyx/parsers/argparse.py,sha256=7JwGVwrfx5l0rWOOvtc-WHGiEH1nQ3g-sxG9A65q4tg,37313
42
42
  falyx/parsers/parsers.py,sha256=MXWC8OQ3apDaeKfY0O4J8NnkxofWVOCRnKatC00lGm0,8796
43
43
  falyx/parsers/signature.py,sha256=PfDe432PYcJDhDXguNzumFqWjDLk13s6jhZF33r__AM,2326
44
- falyx/parsers/utils.py,sha256=Z4qLu8NVIprcHK2RDXoISpcDKBUii3n05G9mlgiPDgw,889
44
+ falyx/parsers/utils.py,sha256=w_UzvvP62EDKXWSf3jslEsJfd45usGyFqXKNziQhLRI,2893
45
45
  falyx/prompt_utils.py,sha256=qgk0bXs7mwzflqzWyFhEOTpKQ_ZtMIqGhKeg-ocwNnE,1542
46
46
  falyx/protocols.py,sha256=-9GbCBUzzsEgw2_KOCYqxxzWJuez0eHmwnZp_ShY0jc,493
47
47
  falyx/retry.py,sha256=sGRE9QhdZK98M99G8F15WUsJ_fYLNyLlCgu3UANaSQs,3744
@@ -53,9 +53,9 @@ falyx/themes/__init__.py,sha256=1CZhEUCin9cUk8IGYBUFkVvdHRNNJBEFXccHwpUKZCA,284
53
53
  falyx/themes/colors.py,sha256=4aaeAHJetmeNInI0Zytg4E3YqKfPFelpf04vtjSvsS8,19776
54
54
  falyx/utils.py,sha256=U45xnZFUdoFC4xiji_9S1jHS5V7MvxSDtufP8EgB0SM,6732
55
55
  falyx/validators.py,sha256=t5iyzVpY8tdC4rfhr4isEfWpD5gNTzjeX_Hbi_Uq6sA,1328
56
- falyx/version.py,sha256=AoJtnEXXv6E20uj57ChQUsGoLfKG8mvSQpdz97tcyis,23
57
- falyx-0.1.44.dist-info/LICENSE,sha256=B0yqgaHuSdhN7T3OBmgQSiDTy8HqT5Oe_dLypRe4Ra4,1073
58
- falyx-0.1.44.dist-info/METADATA,sha256=4Ug2vw0YvPHn_0o9o4Wij4XiSHG5Nk_kstjOYPp51mM,5517
59
- falyx-0.1.44.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
60
- falyx-0.1.44.dist-info/entry_points.txt,sha256=j8owOSl2j1Ss8DtGMnKfgehKaolqnIPhVFHaUBLUnMs,45
61
- falyx-0.1.44.dist-info/RECORD,,
56
+ falyx/version.py,sha256=G1Bw5SEeRoSlHVxmFKf6PIpK38SHTYTFhSFGm9d8YQM,23
57
+ falyx-0.1.45.dist-info/LICENSE,sha256=B0yqgaHuSdhN7T3OBmgQSiDTy8HqT5Oe_dLypRe4Ra4,1073
58
+ falyx-0.1.45.dist-info/METADATA,sha256=f0xryZQMB6pu-oM5OPOLBnzPWmV9lD4UZch2iJw7CzM,5561
59
+ falyx-0.1.45.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
60
+ falyx-0.1.45.dist-info/entry_points.txt,sha256=j8owOSl2j1Ss8DtGMnKfgehKaolqnIPhVFHaUBLUnMs,45
61
+ falyx-0.1.45.dist-info/RECORD,,
File without changes