cli-command-parser 2025.11.1__tar.gz → 2026.2.1__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 (67) hide show
  1. {cli_command_parser-2025.11.1 → cli_command_parser-2026.2.1}/PKG-INFO +1 -1
  2. {cli_command_parser-2025.11.1 → cli_command_parser-2026.2.1}/lib/cli_command_parser/__version__.py +1 -1
  3. {cli_command_parser-2025.11.1 → cli_command_parser-2026.2.1}/lib/cli_command_parser/command_parameters.py +2 -10
  4. {cli_command_parser-2025.11.1 → cli_command_parser-2026.2.1}/lib/cli_command_parser/core.py +1 -1
  5. {cli_command_parser-2025.11.1 → cli_command_parser-2026.2.1}/lib/cli_command_parser/inputs/__init__.py +2 -2
  6. {cli_command_parser-2025.11.1 → cli_command_parser-2026.2.1}/lib/cli_command_parser/inputs/base.py +9 -0
  7. {cli_command_parser-2025.11.1 → cli_command_parser-2026.2.1}/lib/cli_command_parser/inputs/numeric.py +126 -11
  8. {cli_command_parser-2025.11.1 → cli_command_parser-2026.2.1}/lib/cli_command_parser/inputs/time.py +2 -7
  9. {cli_command_parser-2025.11.1 → cli_command_parser-2026.2.1}/lib/cli_command_parser/parameters/base.py +7 -1
  10. {cli_command_parser-2025.11.1 → cli_command_parser-2026.2.1}/lib/cli_command_parser.egg-info/PKG-INFO +1 -1
  11. {cli_command_parser-2025.11.1 → cli_command_parser-2026.2.1}/LICENSE +0 -0
  12. {cli_command_parser-2025.11.1 → cli_command_parser-2026.2.1}/MANIFEST.in +0 -0
  13. {cli_command_parser-2025.11.1 → cli_command_parser-2026.2.1}/lib/cli_command_parser/__init__.py +0 -0
  14. {cli_command_parser-2025.11.1 → cli_command_parser-2026.2.1}/lib/cli_command_parser/__main__.py +0 -0
  15. {cli_command_parser-2025.11.1 → cli_command_parser-2026.2.1}/lib/cli_command_parser/annotations.py +0 -0
  16. {cli_command_parser-2025.11.1 → cli_command_parser-2026.2.1}/lib/cli_command_parser/commands.py +0 -0
  17. {cli_command_parser-2025.11.1 → cli_command_parser-2026.2.1}/lib/cli_command_parser/compat.py +0 -0
  18. {cli_command_parser-2025.11.1 → cli_command_parser-2026.2.1}/lib/cli_command_parser/config.py +0 -0
  19. {cli_command_parser-2025.11.1 → cli_command_parser-2026.2.1}/lib/cli_command_parser/context.py +0 -0
  20. {cli_command_parser-2025.11.1 → cli_command_parser-2026.2.1}/lib/cli_command_parser/conversion/__init__.py +0 -0
  21. {cli_command_parser-2025.11.1 → cli_command_parser-2026.2.1}/lib/cli_command_parser/conversion/__main__.py +0 -0
  22. {cli_command_parser-2025.11.1 → cli_command_parser-2026.2.1}/lib/cli_command_parser/conversion/argparse_ast.py +0 -0
  23. {cli_command_parser-2025.11.1 → cli_command_parser-2026.2.1}/lib/cli_command_parser/conversion/argparse_utils.py +0 -0
  24. {cli_command_parser-2025.11.1 → cli_command_parser-2026.2.1}/lib/cli_command_parser/conversion/cli.py +0 -0
  25. {cli_command_parser-2025.11.1 → cli_command_parser-2026.2.1}/lib/cli_command_parser/conversion/command_builder.py +0 -0
  26. {cli_command_parser-2025.11.1 → cli_command_parser-2026.2.1}/lib/cli_command_parser/conversion/utils.py +0 -0
  27. {cli_command_parser-2025.11.1 → cli_command_parser-2026.2.1}/lib/cli_command_parser/conversion/visitor.py +0 -0
  28. {cli_command_parser-2025.11.1 → cli_command_parser-2026.2.1}/lib/cli_command_parser/documentation.py +0 -0
  29. {cli_command_parser-2025.11.1 → cli_command_parser-2026.2.1}/lib/cli_command_parser/error_handling/__init__.py +0 -0
  30. {cli_command_parser-2025.11.1 → cli_command_parser-2026.2.1}/lib/cli_command_parser/error_handling/base.py +0 -0
  31. {cli_command_parser-2025.11.1 → cli_command_parser-2026.2.1}/lib/cli_command_parser/error_handling/other.py +0 -0
  32. {cli_command_parser-2025.11.1 → cli_command_parser-2026.2.1}/lib/cli_command_parser/error_handling/windows.py +0 -0
  33. {cli_command_parser-2025.11.1 → cli_command_parser-2026.2.1}/lib/cli_command_parser/exceptions.py +0 -0
  34. {cli_command_parser-2025.11.1 → cli_command_parser-2026.2.1}/lib/cli_command_parser/formatting/__init__.py +0 -0
  35. {cli_command_parser-2025.11.1 → cli_command_parser-2026.2.1}/lib/cli_command_parser/formatting/commands.py +0 -0
  36. {cli_command_parser-2025.11.1 → cli_command_parser-2026.2.1}/lib/cli_command_parser/formatting/params.py +0 -0
  37. {cli_command_parser-2025.11.1 → cli_command_parser-2026.2.1}/lib/cli_command_parser/formatting/restructured_text.py +0 -0
  38. {cli_command_parser-2025.11.1 → cli_command_parser-2026.2.1}/lib/cli_command_parser/formatting/utils.py +0 -0
  39. {cli_command_parser-2025.11.1 → cli_command_parser-2026.2.1}/lib/cli_command_parser/inputs/choices.py +0 -0
  40. {cli_command_parser-2025.11.1 → cli_command_parser-2026.2.1}/lib/cli_command_parser/inputs/exceptions.py +0 -0
  41. {cli_command_parser-2025.11.1 → cli_command_parser-2026.2.1}/lib/cli_command_parser/inputs/files.py +0 -0
  42. {cli_command_parser-2025.11.1 → cli_command_parser-2026.2.1}/lib/cli_command_parser/inputs/patterns.py +0 -0
  43. {cli_command_parser-2025.11.1 → cli_command_parser-2026.2.1}/lib/cli_command_parser/inputs/utils.py +0 -0
  44. {cli_command_parser-2025.11.1 → cli_command_parser-2026.2.1}/lib/cli_command_parser/metadata.py +0 -0
  45. {cli_command_parser-2025.11.1 → cli_command_parser-2026.2.1}/lib/cli_command_parser/nargs.py +0 -0
  46. {cli_command_parser-2025.11.1 → cli_command_parser-2026.2.1}/lib/cli_command_parser/parameters/__init__.py +0 -0
  47. {cli_command_parser-2025.11.1 → cli_command_parser-2026.2.1}/lib/cli_command_parser/parameters/actions.py +0 -0
  48. {cli_command_parser-2025.11.1 → cli_command_parser-2026.2.1}/lib/cli_command_parser/parameters/choice_map.py +0 -0
  49. {cli_command_parser-2025.11.1 → cli_command_parser-2026.2.1}/lib/cli_command_parser/parameters/groups.py +0 -0
  50. {cli_command_parser-2025.11.1 → cli_command_parser-2026.2.1}/lib/cli_command_parser/parameters/option_strings.py +0 -0
  51. {cli_command_parser-2025.11.1 → cli_command_parser-2026.2.1}/lib/cli_command_parser/parameters/options.py +0 -0
  52. {cli_command_parser-2025.11.1 → cli_command_parser-2026.2.1}/lib/cli_command_parser/parameters/pass_thru.py +0 -0
  53. {cli_command_parser-2025.11.1 → cli_command_parser-2026.2.1}/lib/cli_command_parser/parameters/positionals.py +0 -0
  54. {cli_command_parser-2025.11.1 → cli_command_parser-2026.2.1}/lib/cli_command_parser/parse_tree.py +0 -0
  55. {cli_command_parser-2025.11.1 → cli_command_parser-2026.2.1}/lib/cli_command_parser/parser.py +0 -0
  56. {cli_command_parser-2025.11.1 → cli_command_parser-2026.2.1}/lib/cli_command_parser/testing.py +0 -0
  57. {cli_command_parser-2025.11.1 → cli_command_parser-2026.2.1}/lib/cli_command_parser/typing.py +0 -0
  58. {cli_command_parser-2025.11.1 → cli_command_parser-2026.2.1}/lib/cli_command_parser/utils.py +0 -0
  59. {cli_command_parser-2025.11.1 → cli_command_parser-2026.2.1}/lib/cli_command_parser.egg-info/SOURCES.txt +0 -0
  60. {cli_command_parser-2025.11.1 → cli_command_parser-2026.2.1}/lib/cli_command_parser.egg-info/dependency_links.txt +0 -0
  61. {cli_command_parser-2025.11.1 → cli_command_parser-2026.2.1}/lib/cli_command_parser.egg-info/entry_points.txt +0 -0
  62. {cli_command_parser-2025.11.1 → cli_command_parser-2026.2.1}/lib/cli_command_parser.egg-info/requires.txt +0 -0
  63. {cli_command_parser-2025.11.1 → cli_command_parser-2026.2.1}/lib/cli_command_parser.egg-info/top_level.txt +0 -0
  64. {cli_command_parser-2025.11.1 → cli_command_parser-2026.2.1}/pyproject.toml +0 -0
  65. {cli_command_parser-2025.11.1 → cli_command_parser-2026.2.1}/readme.rst +0 -0
  66. {cli_command_parser-2025.11.1 → cli_command_parser-2026.2.1}/requirements-dev.txt +0 -0
  67. {cli_command_parser-2025.11.1 → cli_command_parser-2026.2.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cli_command_parser
3
- Version: 2025.11.1
3
+ Version: 2026.2.1
4
4
  Summary: CLI Command Parser
5
5
  Author-email: Doug Skrypa <dskrypa@gmail.com>
6
6
  Project-URL: Source, https://github.com/dskrypa/cli_command_parser
@@ -1,7 +1,7 @@
1
1
  __title__ = 'cli_command_parser'
2
2
  __description__ = 'CLI Command Parser'
3
3
  __url__ = 'https://github.com/dskrypa/cli_command_parser'
4
- __version__ = '2025.11.01'
4
+ __version__ = '2026.02.01'
5
5
  __author__ = 'Doug Skrypa'
6
6
  __author_email__ = 'dskrypa@gmail.com'
7
7
  __license__ = 'Apache 2.0'
@@ -34,7 +34,6 @@ class CommandParameters:
34
34
  # fmt: off
35
35
  command: CommandCls #: The Command associated with this CommandParameters object
36
36
  formatter: CommandHelpFormatter #: The formatter used for this Command's help text
37
- command_parent: CommandCls | None #: The parent Command, if any
38
37
  parent: CommandParameters | None #: The parent Command's CommandParameters
39
38
  action: Action | None = None #: An Action Parameter, if specified
40
39
  _pass_thru: PassThru | None = None #: A PassThru Parameter, if specified
@@ -49,15 +48,8 @@ class CommandParameters:
49
48
  option_map: OptionMap #: Mapping of {--opt / -opt: Parameter}
50
49
  # fmt: on
51
50
 
52
- def __init__(
53
- self,
54
- command: CommandCls,
55
- command_parent: CommandCls | None,
56
- parent_params: CommandParameters | None,
57
- config: CommandConfig,
58
- ):
51
+ def __init__(self, command: CommandCls, parent_params: CommandParameters | None, config: CommandConfig):
59
52
  self.command = command
60
- self.command_parent = command_parent
61
53
  self.parent = parent_params
62
54
  self.config = config
63
55
  self._process_parameters()
@@ -167,7 +159,7 @@ class CommandParameters:
167
159
  while param_group := param_group.group:
168
160
  groups.add(param_group)
169
161
 
170
- if self.config.add_help and self.command_parent is not None and (not self.parent or not self.parent._has_help):
162
+ if self.config.add_help and self.parent is not None and not self.parent._has_help:
171
163
  options.append(help_action)
172
164
 
173
165
  self._process_positionals(positionals)
@@ -211,7 +211,7 @@ class CommandMeta(ABCMeta, type):
211
211
  cls = cls.__class__
212
212
  parent = mcs.parent(cls, True)
213
213
  parent_params = mcs.params(parent) if parent is not None else None
214
- cls.__params = params = CommandParameters(cls, parent, parent_params, mcs.config(cls, DEFAULT_CONFIG))
214
+ cls.__params = params = CommandParameters(cls, parent_params, mcs.config(cls, DEFAULT_CONFIG))
215
215
  return params
216
216
 
217
217
  @classmethod
@@ -15,7 +15,7 @@ from .base import InputType
15
15
  from .choices import ChoiceMap, Choices, EnumChoices
16
16
  from .exceptions import InputValidationError, InvalidChoiceError
17
17
  from .files import File, Json, Path, Pickle, Serialized
18
- from .numeric import NumRange, Range
18
+ from .numeric import Bytes, NumRange, Range
19
19
  from .patterns import Glob, Regex, RegexMode
20
20
  from .time import Date, DateTime, Day, DTFormatMode, Month, Time, TimeDelta
21
21
  from .utils import FileWrapper, StatMode
@@ -26,7 +26,7 @@ if _t.TYPE_CHECKING:
26
26
  # fmt: off
27
27
  __all__ = [
28
28
  'StatMode', 'FileWrapper', 'Path', 'File', 'Serialized', 'Json', 'Pickle',
29
- 'Range', 'NumRange',
29
+ 'Bytes', 'Range', 'NumRange',
30
30
  'Choices', 'ChoiceMap', 'EnumChoices',
31
31
  'Regex', 'RegexMode', 'Glob',
32
32
  'Day', 'Month', 'TimeDelta', 'DateTime', 'Date', 'Time', 'DTFormatMode',
@@ -43,3 +43,12 @@ class InputType(Generic[T], ABC):
43
43
  def format_metavar(self, choice_delim: str = ',', sort_choices: bool = False) -> str:
44
44
  # TODO: Optional/required arg, or handle wrapping in []/{} in formatter
45
45
  return NotImplemented
46
+
47
+
48
+ class _FixedInputType(InputType[T], ABC):
49
+ __slots__ = ()
50
+
51
+ def fix_default(self, value: str | T | None) -> T | None:
52
+ if value is None or not isinstance(value, str) or not self._fix_default:
53
+ return value
54
+ return self(value)
@@ -7,20 +7,25 @@ Custom numeric input handlers for Parameters
7
7
 
8
8
  from __future__ import annotations
9
9
 
10
+ import re
10
11
  from abc import ABC, abstractmethod
12
+ from typing import Literal
11
13
 
12
14
  from ..typing import NT, Bool, Number, NumType, RngType
13
- from .base import InputType
15
+ from .base import _FixedInputType
14
16
  from .exceptions import InputValidationError
15
17
  from .utils import RangeMixin, range_str
16
18
 
17
- __all__ = ['Range', 'NumRange']
19
+ __all__ = ['NumericInput', 'Range', 'NumRange', 'Bytes']
18
20
 
19
21
  _range = range
20
22
 
21
23
 
22
- class NumericInput(InputType[NT], ABC):
24
+ class NumericInput(_FixedInputType[NT], ABC):
23
25
  __slots__ = ()
26
+
27
+
28
+ class _RangeInput(NumericInput[NT], ABC):
24
29
  type: NumType
25
30
 
26
31
  def is_valid_type(self, value: str) -> bool:
@@ -45,13 +50,8 @@ class NumericInput(InputType[NT], ABC):
45
50
  def format_metavar(self, choice_delim: str = ',', sort_choices: bool = False) -> str:
46
51
  return f'{{{self._range_str()}}}'
47
52
 
48
- def fix_default(self, value: str | NT | None) -> NT | None:
49
- if value is None or not isinstance(value, str) or not self._fix_default:
50
- return value
51
- return self(value)
52
-
53
53
 
54
- class Range(NumericInput[NT]):
54
+ class Range(_RangeInput[NT]):
55
55
  """
56
56
  A range of integers that uses the builtin :class:`python:range`. If a range object is passed to a
57
57
  :class:`.Parameter` as the ``type=`` value, it will automatically be wrapped by this class.
@@ -75,7 +75,7 @@ class Range(NumericInput[NT]):
75
75
  if isinstance(range, int):
76
76
  self.range = _range(range)
77
77
  elif not isinstance(range, _range):
78
- self.range = _range(*range)
78
+ self.range = _range(*range) # noqa
79
79
  else:
80
80
  self.range = range
81
81
  if type is not None:
@@ -102,7 +102,7 @@ class Range(NumericInput[NT]):
102
102
  raise InputValidationError(f'expected a value in the range {self._range_str()}')
103
103
 
104
104
 
105
- class NumRange(RangeMixin, NumericInput[NT]):
105
+ class NumRange(RangeMixin, _RangeInput[NT]):
106
106
  """
107
107
  A range of integers or floats, optionally only bounded on one side.
108
108
 
@@ -191,3 +191,118 @@ class NumRange(RangeMixin, NumericInput[NT]):
191
191
  return self.handle_invalid(self.max, self.include_max, -1)
192
192
  else:
193
193
  return value
194
+
195
+
196
+ class Bytes(NumericInput[NT]):
197
+ """
198
+ A byte count/size.
199
+
200
+ Types of user inputs that are accepted:
201
+
202
+ - Simple integer representing an exact byte count
203
+ - Number with a unit (KB, MiB, etc.), which will result in the processed value being the raw byte count after
204
+ scaling based on the provided unit
205
+
206
+ :base: Whether 2-character units (MB, GB, etc.) are treated as base 2 (historical interpretation) or
207
+ base 10 (the default) (following the International System of Units (SI) definition). Regardless of the base
208
+ specified here, user-provided units that explicitly use an ``i`` to indicate binary (MiB, GiB, etc.) will always
209
+ be treated as base 2.
210
+ :short: Whether single-letter units (other than ``B``, which is always valid) are accepted (default: True). When
211
+ accepted, they use the ``base`` parameter to determine which base to use.
212
+ :fractions: Whether fractional values are allowed (e.g., ``1.2 MB``). By default, only integers / whole numbers
213
+ are accepted.
214
+ :negative: Whether negative numeric values are accepted.
215
+ """
216
+
217
+ __slots__ = ('base', 'short', 'fractions', 'negative')
218
+ _pattern = re.compile(r'^(-?\d+(?:\.\d+)?)\s*([KMGTPEZYRQ]?i?B?)$', re.IGNORECASE)
219
+ _PREFIXES = 'KMGTPEZYRQ'
220
+
221
+ def __init__(
222
+ self,
223
+ base: Literal[2, 10] = 10,
224
+ *,
225
+ short: Bool = True,
226
+ fractions: Bool = False,
227
+ negative: Bool = False,
228
+ fix_default: Bool = True,
229
+ ):
230
+ if base not in (2, 10):
231
+ raise ValueError(f'Invalid 2-character unit {base=} - expected 2 for binary or 10 for SI / decimal')
232
+ super().__init__(fix_default)
233
+ self.base = base
234
+ self.short = short
235
+ self.fractions = fractions
236
+ self.negative = negative
237
+
238
+ def __repr__(self) -> str:
239
+ base, short, fractions, negative = self.base, self.short, self.fractions, self.negative
240
+ return f'<{self.__class__.__name__}(({base=}, {short=}, {fractions=}, {negative=})>'
241
+
242
+ @classmethod
243
+ def is_valid_type(cls, value: str) -> bool:
244
+ """
245
+ Called during parsing when :meth:`.ParamAction.would_accept` is called to determine if the value would be
246
+ accepted later for processing / conversion when called.
247
+
248
+ :param value: The parsed argument to validate
249
+ :return: True if this input would accept it for processing later (where it may still be rejected), False if
250
+ it should be rejected before attempting to process / convert / store it.
251
+ """
252
+ try:
253
+ return bool(cls._pattern.match(value))
254
+ except TypeError:
255
+ return False
256
+
257
+ def format_metavar(self, choice_delim: str = ',', sort_choices: bool = False) -> str:
258
+ return 'BYTES[B|KB|MiB|...]'
259
+
260
+ def _type_desc(self) -> str:
261
+ parts = ['a']
262
+ if not self.negative:
263
+ parts.append('positive')
264
+
265
+ if not self.fractions:
266
+ if len(parts) == 1:
267
+ parts[0] = 'an'
268
+ parts.append('integer')
269
+
270
+ parts.append('byte count/size')
271
+ return ' '.join(parts)
272
+
273
+ def __call__(self, value: str) -> NT:
274
+ try:
275
+ num, unit = self._pattern.match(value.strip()).groups()
276
+ except (TypeError, AttributeError):
277
+ raise InputValidationError(f'expected {self._type_desc()} with optional unit') from None
278
+
279
+ try:
280
+ num = float(num) if self.fractions else int(num)
281
+ except ValueError as e: # This should only be caused when int is expected at this point
282
+ raise InputValidationError(f'expected {self._type_desc()}, but found {num!r}') from e
283
+
284
+ if not self.negative and num < 0:
285
+ raise InputValidationError(f'expected {self._type_desc()}, but found {num!r}')
286
+
287
+ return num * self._get_multiplier(unit)
288
+
289
+ def _get_multiplier(self, unit: str | None) -> int:
290
+ if not unit:
291
+ return 1
292
+
293
+ uc_unit = unit.upper()
294
+ if uc_unit == 'B':
295
+ return 1
296
+ elif uc_unit.endswith('I') or (not self.short and len(uc_unit) == 1):
297
+ # `i` alone or Ki/Mi/etc, or only a single character was provided
298
+ raise InputValidationError(f'invalid byte {unit=}')
299
+
300
+ try:
301
+ exp = self._PREFIXES.index(uc_unit[0]) + 1
302
+ except ValueError:
303
+ raise InputValidationError(f'invalid byte {unit=}')
304
+
305
+ if 'I' in uc_unit or self.base == 2:
306
+ return 1024**exp
307
+ else:
308
+ return 1000**exp
@@ -26,7 +26,7 @@ from typing import Collection, Iterator, Literal, Sequence, Type, TypeVar, overl
26
26
 
27
27
  from ..typing import Bool, Locale, Number, T, TimeBound
28
28
  from ..utils import MissingMixin
29
- from .base import InputType
29
+ from .base import InputType, _FixedInputType
30
30
  from .exceptions import InputValidationError, InvalidChoiceError
31
31
  from .utils import RangeMixin, range_str
32
32
 
@@ -73,7 +73,7 @@ class different_locale:
73
73
  self._lock.release()
74
74
 
75
75
 
76
- class DTInput(InputType[T], ABC):
76
+ class DTInput(_FixedInputType[T], ABC):
77
77
  __slots__ = ('locale',)
78
78
  dt_type: str
79
79
  locale: Locale | None
@@ -93,11 +93,6 @@ class DTInput(InputType[T], ABC):
93
93
  def choice_str(self, choice_delim: str = ',', sort_choices: bool = False) -> str:
94
94
  raise NotImplementedError
95
95
 
96
- def fix_default(self, value: str | T | None) -> T | None:
97
- if value is None or not isinstance(value, str) or not self._fix_default:
98
- return value
99
- return self(value)
100
-
101
96
 
102
97
  # region Calendar Unit Inputs
103
98
 
@@ -20,6 +20,7 @@ from ..exceptions import BadArgument, InvalidChoice, MissingArgument, ParameterD
20
20
  from ..inputs import InputType, normalize_input_type
21
21
  from ..inputs.choices import _ChoicesBase
22
22
  from ..inputs.exceptions import InputValidationError, InvalidChoiceError
23
+ from ..inputs.numeric import NumericInput
23
24
  from ..nargs import REMAINDER, Nargs
24
25
  from ..typing import CommandMethod, DefaultFunc, T_co
25
26
  from ..utils import _NotSet
@@ -360,7 +361,12 @@ class Parameter(ParamBase, Generic[T_co], ABC):
360
361
  if not isinstance(value, str) or not value or not value[0] == '-':
361
362
  return
362
363
  elif self.allow_leading_dash == AllowLeadingDash.NUMERIC:
363
- if not joined and len(value) > 1 and not _is_numeric(value):
364
+ # `joined` indicates the value was provided as --opt=val, so it's explicitly intended for this param.
365
+ # If the value is obviously numeric, that has been allowed via config.
366
+ # If this param's type is a numeric input type, it is allowed because `prepare_validation_value` is always
367
+ # called before this method, and the type is validated there (some of this validation should definitely be
368
+ # simplified).
369
+ if not joined and len(value) > 1 and not _is_numeric(value) and not isinstance(self.type, NumericInput):
364
370
  raise BadArgument(self, f'invalid {value=}')
365
371
  elif self.allow_leading_dash == AllowLeadingDash.NEVER:
366
372
  raise BadArgument(self, f'invalid {value=}')
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cli_command_parser
3
- Version: 2025.11.1
3
+ Version: 2026.2.1
4
4
  Summary: CLI Command Parser
5
5
  Author-email: Doug Skrypa <dskrypa@gmail.com>
6
6
  Project-URL: Source, https://github.com/dskrypa/cli_command_parser