• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pantsbuild / pants / 19015773527

02 Nov 2025 05:33PM UTC coverage: 17.872% (-62.4%) from 80.3%
19015773527

Pull #22816

github

web-flow
Merge a12d75757 into 6c024e162
Pull Request #22816: Update Pants internal Python to 3.14

4 of 5 new or added lines in 3 files covered. (80.0%)

28452 existing lines in 683 files now uncovered.

9831 of 55007 relevant lines covered (17.87%)

0.18 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

80.12
/src/python/pants/option/option_types.py
1
# Copyright 2021 Pants project contributors (see CONTRIBUTORS.md).
2
# Licensed under the Apache License, Version 2.0 (see LICENSE).
3

4
from __future__ import annotations
1✔
5

6
import inspect
1✔
7
from collections.abc import Callable, Iterator
1✔
8
from dataclasses import dataclass
1✔
9
from enum import Enum
1✔
10
from typing import Any, Generic, TypeVar, Union, cast, overload
1✔
11

12
from pants.option import custom_types
1✔
13
from pants.util.docutil import bin_name
1✔
14
from pants.util.strutil import softwrap
1✔
15

16

17
@dataclass(frozen=True, order=True)
1✔
18
class OptionInfo:
1✔
19
    """Registration info for a single option."""
20

21
    args: tuple[str, ...]  # The *args in the registration.
1✔
22
    kwargs: dict[str, Any]  # The **kwargs in the registration.
1✔
23

24

25
def collect_options_info(cls: type) -> Iterator[OptionInfo]:
1✔
26
    """Yields the ordered options info from the MRO of the provided class."""
27

28
    # NB: Since registration ordering matters (it impacts `help` output), we register these in
29
    # class attribute order, starting from the base class down.
UNCOV
30
    for class_ in reversed(inspect.getmro(cls)):
×
UNCOV
31
        for attrname in class_.__dict__.keys():
×
32
            # NB: We use attrname and getattr to trigger descriptors
UNCOV
33
            attr = getattr(cls, attrname)
×
UNCOV
34
            if isinstance(attr, OptionInfo):
×
UNCOV
35
                yield attr
×
36

37

38
# The type of the option.
39
_OptT = TypeVar("_OptT")
1✔
40
# The type of option's default (may be _OptT or some other type like `None`)
41
_DefaultT = TypeVar("_DefaultT")
1✔
42
# The type of pants.option.subsystem.Subsystem classes.
43
# NB: Ideally this would be `type[Subsystem]`, however where this type is used is generally
44
# provided untyped lambdas.
45
_SubsystemType = Any
1✔
46
# A "dynamic" value type. This exists to allow clients to provide callable arguments taking the
47
# subsytem type and returning a value. E.g. `prop = Option(..., default=lambda cls: cls.default)`.
48
# This is necessary to support "base" subsystem types which are subclassed with more specific
49
# values.
50
# NB: Marking this as `Callable[[_SubsystemType], _DefaultT]` will upset mypy at the call site
51
# because mypy won't be able to have enough info to deduce the correct type.
52
_DynamicDefaultT = Callable[[_SubsystemType], Any]
1✔
53
# The type of the `default` parameter for each option.
54
_MaybeDynamicT = Union[_DynamicDefaultT, _DefaultT]
1✔
55
# The type of the `help` parameter for each option.
56
_HelpT = _MaybeDynamicT[str]
1✔
57
# NB: Ideally this would be `Callable[[_SubsystemType], bool]`, however where this type is used is
58
# generally provided untyped lambdas.
59
_RegisterIfFuncT = Callable[[_SubsystemType], Any]
1✔
60

61

62
def _eval_maybe_dynamic(val: _MaybeDynamicT[_DefaultT], subsystem_cls: _SubsystemType) -> _DefaultT:
1✔
63
    return val(subsystem_cls) if inspect.isfunction(val) else val  # type: ignore[return-value]
1✔
64

65

66
class _OptionBase(Generic[_OptT, _DefaultT]):
1✔
67
    """Descriptor base for subsystem options.
68

69
    Clients shouldn't use this class directly, instead use one of the concrete classes below.
70

71
    This class serves two purposes:
72
        - Collect registration values for your option.
73
        - Provide a typed property for Python usage
74
    """
75

76
    _flag_names: tuple[str, ...] | None
1✔
77
    _default: _MaybeDynamicT[_DefaultT]
1✔
78
    _help: _HelpT
1✔
79
    _register_if: _RegisterIfFuncT
1✔
80
    _extra_kwargs: dict[str, Any]
1✔
81

82
    # NB: We define `__new__` rather than `__init__` because some subclasses need to define
83
    # `__new__` and mypy has issues if your class defines both.
84
    def __new__(
1✔
85
        cls,
86
        flag_name: str | None = None,
87
        *,
88
        default: _MaybeDynamicT[_DefaultT],
89
        help: _HelpT,
90
        # Additional bells/whistles
91
        register_if: _RegisterIfFuncT | None = None,
92
        advanced: bool | None = None,
93
        default_help_repr: str | None = None,
94
        fromfile: bool | None = None,
95
        metavar: str | None = None,
96
        mutually_exclusive_group: str | None = None,
97
        removal_version: str | None = None,
98
        removal_hint: _HelpT | None = None,
99
        deprecation_start_version: str | None = None,
100
        # Internal bells/whistles
101
        daemon: bool | None = None,
102
        fingerprint: bool | None = None,
103
    ):
104
        """Construct a new Option descriptor.
105

106
        :param flag_name: The argument name, starting with "--", e.g. "--skip". Defaults to the class
107
            attribute name in kebab-case (without leading underscore).
108
        :param default: The default value the property will return if unspecified by the user. Note
109
            that for "scalar" option types (like StrOption and IntOption) this can either be an
110
            instance of the scalar type or `None`, but __must__ be provided.
111
            For Non-scalar types (like ListOption subclasses or DictOption) the default can't be
112
            `None`, but does have an "empty" default value.
113
        :param help: The help message to use when users run `pants help` or
114
            `pants help-advanced`
115
        :param register_if: A callable (usually a lambda) which, if provided, can be used to
116
            specify if the option should be registered. This is useful for "Base" subsystem
117
            classes, who might/might not want to register options based on information provided
118
            by the subclass. The callable takes one parameter: the derived subsystem class.
119
        :param advanced: If True, this option will only show up in `help-advanced`, and not
120
            `help`. You should generally set this value if the option will primarily be used by
121
            codebase administrators, such as setting up a config file.
122
        :param default_help_repr: The string representation of the option's default value.
123
            Useful when the default value doesn't have semantic meaning to the user.
124
            (E.g. If the default is set to the number of cores, `default_help_repr` might be set
125
            to "#cores")
126
        :param fromfile: If True, allows the user to specify a string value (starting with "@")
127
            which represents a file to read the option's value from.
128
        :param metavar: Sets what users see in `pants help` as possible values for the flag.
129
            The default is based on the option type (E.g. "<str>" or "<int>").
130
        :param mutually_exclusive_group: If specified disallows all other options using the same
131
            value to also be specified by the user.
132
        :param removal_version: If the option is deprecated, sets the version this option will
133
            be removed in. You must also set `removal_hint`.
134
        :param removal_hint: If the option is deprecated, provides a message to display to the
135
            user when running `help`.
136
        :param deprecation_start_version: If the option is deprecated, sets the version at which the
137
            deprecation will begin. Must be less than the `removal_version`.
138
        """
139
        self = super().__new__(cls)
1✔
140
        self._flag_names = (flag_name,) if flag_name else None
1✔
141
        self._default = default
1✔
142
        self._help = help
1✔
143
        self._register_if = register_if or (lambda cls: True)
1✔
144
        self._extra_kwargs = {
1✔
145
            k: v
146
            for k, v in {
147
                "advanced": advanced,
148
                "daemon": daemon,
149
                "default_help_repr": default_help_repr,
150
                "fingerprint": fingerprint,
151
                "fromfile": fromfile,
152
                "metavar": metavar,
153
                "mutually_exclusive_group": mutually_exclusive_group,
154
                "removal_hint": removal_hint,
155
                "removal_version": removal_version,
156
                "deprecation_start_version": deprecation_start_version,
157
            }.items()
158
            if v is not None
159
        }
160
        return self
1✔
161

162
    def __set_name__(self, owner, name) -> None:
1✔
163
        if self._flag_names is None:
1✔
164
            kebab_name = name.strip("_").replace("_", "-")
1✔
165
            self._flag_names = (f"--{kebab_name}",)
1✔
166

167
    # Subclasses can override if necessary
168
    def get_option_type(self, subsystem_cls):
1✔
169
        return type(self).option_type
1✔
170

171
    # Subclasses can override if necessary
172
    def _convert_(self, val: Any) -> _OptT:
1✔
UNCOV
173
        return cast("_OptT", val)
×
174

175
    def get_flag_options(self, subsystem_cls) -> dict:
1✔
176
        rh = "removal_hint"
1✔
177
        if rh in self._extra_kwargs:
1✔
UNCOV
178
            extra_kwargs: dict[str, Any] = {
×
179
                **self._extra_kwargs,
180
                rh: _eval_maybe_dynamic(self._extra_kwargs[rh], subsystem_cls),
181
            }
182
        else:
183
            extra_kwargs = self._extra_kwargs
1✔
184
        return dict(
1✔
185
            help=_eval_maybe_dynamic(self._help, subsystem_cls),
186
            default=_eval_maybe_dynamic(self._default, subsystem_cls),
187
            type=self.get_option_type(subsystem_cls),
188
            **extra_kwargs,
189
        )
190

191
    @overload
192
    def __get__(self, obj: None, objtype: Any) -> OptionInfo | None: ...
193

194
    @overload
195
    def __get__(self, obj: object, objtype: Any) -> _OptT | _DefaultT: ...
196

197
    def __get__(self, obj, objtype):
1✔
198
        assert self._flag_names is not None
1✔
199
        if obj is None:
1✔
200
            if self._register_if(objtype):
1✔
201
                return OptionInfo(self._flag_names, self.get_flag_options(objtype))
1✔
UNCOV
202
            return None
×
UNCOV
203
        long_name = self._flag_names[-1]
×
UNCOV
204
        option_value = getattr(obj.options, long_name[2:].replace("-", "_"))
×
UNCOV
205
        if option_value is None:
×
UNCOV
206
            return None
×
UNCOV
207
        return self._convert_(option_value)
×
208

209

210
# The type of the list members.
211
# NB: We don't provide constraints, as our `XListOption` types act like a set of contraints
212
_ListMemberT = TypeVar("_ListMemberT")
1✔
213

214

215
class _ListOptionBase(
1✔
216
    _OptionBase["tuple[_ListMemberT, ...]", "tuple[_ListMemberT, ...]"],
217
    Generic[_ListMemberT],
218
):
219
    """Descriptor base for a subsystem option of an homogenous list of some type.
220

221
    Don't use this class directly, instead use one of the concrete classes below.
222

223
    The default value will always be set as an empty list, and the Python property always returns
224
    a tuple (for immutability).
225
    """
226

227
    option_type = list
1✔
228

229
    def __new__(
1✔
230
        cls,
231
        flag_name: str | None = None,
232
        *,
233
        default: _MaybeDynamicT[list[_ListMemberT]] | None = [],
234
        help: _HelpT,
235
        # Additional bells/whistles
236
        register_if: _RegisterIfFuncT | None = None,
237
        advanced: bool | None = None,
238
        default_help_repr: str | None = None,
239
        fromfile: bool | None = None,
240
        metavar: str | None = None,
241
        mutually_exclusive_group: str | None = None,
242
        removal_version: str | None = None,
243
        removal_hint: _HelpT | None = None,
244
        deprecation_start_version: str | None = None,
245
        # Internal bells/whistles
246
        daemon: bool | None = None,
247
        fingerprint: bool | None = None,
248
    ):
249
        default = default or []
1✔
250
        instance = super().__new__(
1✔
251
            cls,
252
            flag_name,
253
            default=default,  # type: ignore[arg-type]
254
            help=help,
255
            register_if=register_if,
256
            advanced=advanced,
257
            daemon=daemon,
258
            default_help_repr=default_help_repr,
259
            fingerprint=fingerprint,
260
            fromfile=fromfile,
261
            metavar=metavar,
262
            mutually_exclusive_group=mutually_exclusive_group,
263
            removal_hint=removal_hint,
264
            removal_version=removal_version,
265
            deprecation_start_version=deprecation_start_version,
266
        )
267
        return instance
1✔
268

269
    # Subclasses can override if necessary
270
    def get_member_type(self, subsystem_cls):
1✔
271
        return type(self).member_type
1✔
272

273
    # Subclasses can override if necessary
274
    def _convert_(self, value: list[Any]) -> tuple[_ListMemberT]:
1✔
UNCOV
275
        return cast("tuple[_ListMemberT]", tuple(value))
×
276

277
    def get_flag_options(self, subsystem_cls) -> dict[str, Any]:
1✔
278
        return dict(
1✔
279
            member_type=self.get_member_type(subsystem_cls),
280
            **super().get_flag_options(subsystem_cls),
281
        )
282

283

284
# -----------------------------------------------------------------------------------------------
285
# String Concrete Option Classes
286
# -----------------------------------------------------------------------------------------------
287
_StrDefault = TypeVar("_StrDefault", str, None)
1✔
288

289

290
class StrOption(_OptionBase[str, _StrDefault]):
1✔
291
    """A string option."""
292

293
    option_type: Any = str
1✔
294

295

296
class StrListOption(_ListOptionBase[str]):
1✔
297
    """A homogenous list of string options."""
298

299
    member_type: Any = str
1✔
300

301

302
class TargetOption(_OptionBase[str, _StrDefault]):
1✔
303
    """A Pants Target option."""
304

305
    option_type: Any = custom_types.target_option
1✔
306

307

308
class TargetListOption(StrListOption):
1✔
309
    """A homogenous list of target options."""
310

311
    member_type: Any = custom_types.target_option
1✔
312

313

314
class DirOption(_OptionBase[str, _StrDefault]):
1✔
315
    """A directory option."""
316

317
    option_type: Any = custom_types.dir_option
1✔
318

319

320
class DirListOption(StrListOption):
1✔
321
    """A homogenous list of directory options."""
322

323
    member_type: Any = custom_types.dir_option
1✔
324

325

326
class FileOption(_OptionBase[str, _StrDefault]):
1✔
327
    """A file option."""
328

329
    option_type: Any = custom_types.file_option
1✔
330

331

332
class FileListOption(StrListOption):
1✔
333
    """A homogenous list of file options."""
334

335
    member_type: Any = custom_types.file_option
1✔
336

337

338
class ShellStrOption(_OptionBase[str, _StrDefault]):
1✔
339
    """A shell string option."""
340

341
    option_type: Any = custom_types.shell_str
1✔
342

343

344
class ShellStrListOption(StrListOption):
1✔
345
    """A homogenous list of shell string options."""
346

347
    member_type: Any = custom_types.shell_str
1✔
348

349

350
class WorkspacePathOption(_OptionBase[str, _StrDefault]):
1✔
351
    """A workspace path option."""
352

353
    option_type: Any = custom_types.workspace_path
1✔
354

355

356
# -----------------------------------------------------------------------------------------------
357
# Int Concrete Option Classes
358
# -----------------------------------------------------------------------------------------------
359
_IntDefault = TypeVar("_IntDefault", int, None)
1✔
360

361

362
class IntOption(_OptionBase[int, _IntDefault]):
1✔
363
    """An int option."""
364

365
    option_type: Any = int
1✔
366

367

368
class IntListOption(_ListOptionBase[int]):
1✔
369
    """A homogenous list of int options."""
370

371
    member_type: Any = int
1✔
372

373

374
class MemorySizeOption(_OptionBase[int, _IntDefault]):
1✔
375
    """A memory size option."""
376

377
    option_type: Any = custom_types.memory_size
1✔
378

379

380
class MemorySizeListOption(IntListOption):
1✔
381
    """A homogenous list of memory size options."""
382

383
    member_type: Any = custom_types.memory_size
1✔
384

385

386
_FloatDefault = TypeVar("_FloatDefault", float, None)
1✔
387

388

389
class FloatOption(_OptionBase[float, _FloatDefault]):
1✔
390
    """A float option."""
391

392
    option_type: Any = float
1✔
393

394

395
class FloatListOption(_ListOptionBase[float]):
1✔
396
    """A homogenous list of float options."""
397

398
    member_type: Any = float
1✔
399

400

401
# -----------------------------------------------------------------------------------------------
402
# Bool Concrete Option Classes
403
# -----------------------------------------------------------------------------------------------
404
_BoolDefault = TypeVar("_BoolDefault", bool, None)
1✔
405

406

407
class BoolOption(_OptionBase[bool, _BoolDefault]):
1✔
408
    """A bool option.
409

410
    If you don't provide a `default` value, this becomes a "tri-bool" where the property will return
411
    `None` if unset by the user.
412
    """
413

414
    option_type: Any = bool
1✔
415

416

417
class BoolListOption(_ListOptionBase[bool]):
1✔
418
    """A homogenous list of bool options."""
419

420
    member_type: Any = bool
1✔
421

422

423
# -----------------------------------------------------------------------------------------------
424
# Enum Concrete Option Classes
425
# -----------------------------------------------------------------------------------------------
426
_EnumT = TypeVar("_EnumT", bound=Enum)
1✔
427

428

429
class EnumOption(_OptionBase[_OptT, _DefaultT]):
1✔
430
    """An Enum option.
431

432
    - If you provide a static non-None `default` parameter, the `enum_type` parameter will be
433
        inferred from the type of the default.
434
    - If you provide a dynamic `default` or `default` is `None`, you must also provide `enum_type`.
435

436
    E.g.
437
        # The property type is `MyEnum`
438
        EnumOption(..., default=MyEnum.Value)
439
        EnumOption(..., enum_type=MyEnum default=lambda cls: cls.default_val)
440

441
        # The property type is `MyEnum | None`
442
        EnumOption(..., enum_type=MyEnum, default=None)
443
    """
444

445
    @overload
446
    def __new__(
447
        cls,
448
        flag_name: str | None = None,
449
        *,
450
        default: _EnumT,
451
        help: _HelpT,
452
        # Additional bells/whistles
453
        register_if: _RegisterIfFuncT | None = None,
454
        advanced: bool | None = None,
455
        default_help_repr: str | None = None,
456
        fromfile: bool | None = None,
457
        metavar: str | None = None,
458
        mutually_exclusive_group: str | None = None,
459
        removal_version: str | None = None,
460
        removal_hint: _HelpT | None = None,
461
        deprecation_start_version: str | None = None,
462
        # Internal bells/whistles
463
        daemon: bool | None = None,
464
        fingerprint: bool | None = None,
465
    ) -> EnumOption[_EnumT, _EnumT]: ...
466

467
    # N.B. This has an additional param: `enum_type`.
468
    @overload  # Case: dynamic default
469
    def __new__(
470
        cls,
471
        flag_name: str | None = None,
472
        *,
473
        enum_type: type[_EnumT],
474
        default: _DynamicDefaultT,
475
        help: _HelpT,
476
        # Additional bells/whistles
477
        register_if: _RegisterIfFuncT | None = None,
478
        advanced: bool | None = None,
479
        default_help_repr: str | None = None,
480
        fromfile: bool | None = None,
481
        metavar: str | None = None,
482
        mutually_exclusive_group: str | None = None,
483
        removal_version: str | None = None,
484
        removal_hint: _HelpT | None = None,
485
        deprecation_start_version: str | None = None,
486
        # Internal bells/whistles
487
        daemon: bool | None = None,
488
        fingerprint: bool | None = None,
489
    ) -> EnumOption[_EnumT, _EnumT]: ...
490

491
    # N.B. This has an additional param: `enum_type`.
492
    @overload  # Case: default is `None`
493
    def __new__(
494
        cls,
495
        flag_name: str | None = None,
496
        *,
497
        enum_type: type[_EnumT],
498
        default: None,
499
        help: _HelpT,
500
        # Additional bells/whistles
501
        register_if: _RegisterIfFuncT | None = None,
502
        advanced: bool | None = None,
503
        default_help_repr: str | None = None,
504
        fromfile: bool | None = None,
505
        metavar: str | None = None,
506
        mutually_exclusive_group: str | None = None,
507
        removal_version: str | None = None,
508
        removal_hint: _HelpT | None = None,
509
        deprecation_start_version: str | None = None,
510
        # Internal bells/whistles
511
        daemon: bool | None = None,
512
        fingerprint: bool | None = None,
513
    ) -> EnumOption[_EnumT, None]: ...
514

515
    def __new__(
1✔
516
        cls,
517
        flag_name=None,
518
        *,
519
        enum_type=None,
520
        default,
521
        help,
522
        # Additional bells/whistles
523
        register_if=None,
524
        advanced=None,
525
        default_help_repr=None,
526
        fromfile=None,
527
        metavar=None,
528
        mutually_exclusive_group=None,
529
        removal_version=None,
530
        removal_hint=None,
531
        deprecation_start_version=None,
532
        # Internal bells/whistles
533
        daemon=None,
534
        fingerprint=None,
535
    ):
536
        instance = super().__new__(
1✔
537
            cls,
538
            flag_name,
539
            default=default,
540
            help=help,
541
            register_if=register_if,
542
            advanced=advanced,
543
            default_help_repr=default_help_repr,
544
            fromfile=fromfile,
545
            metavar=metavar,
546
            mutually_exclusive_group=mutually_exclusive_group,
547
            removal_version=removal_version,
548
            removal_hint=removal_hint,
549
            deprecation_start_version=deprecation_start_version,
550
            daemon=daemon,
551
            fingerprint=fingerprint,
552
        )
553
        instance._enum_type = enum_type
1✔
554
        return instance
1✔
555

556
    def get_option_type(self, subsystem_cls):
1✔
UNCOV
557
        enum_type = self._enum_type
×
UNCOV
558
        default = _eval_maybe_dynamic(self._default, subsystem_cls)
×
UNCOV
559
        if enum_type is None:
×
UNCOV
560
            if default is None:
×
561
                raise ValueError(
×
562
                    "`enum_type` must be provided to the constructor if `default` isn't provided."
563
                )
UNCOV
564
            return type(default)
×
UNCOV
565
        elif default is not None and not isinstance(default, enum_type):
×
566
            raise ValueError(
×
567
                f"Expected the default value to be of type '{enum_type}', got '{type(default)}'"
568
            )
UNCOV
569
        return enum_type
×
570

571

572
class EnumListOption(_ListOptionBase[_OptT], Generic[_OptT]):
1✔
573
    """An homogenous list of Enum options.
574

575
    - If you provide a static `default` parameter, the `enum_type` parameter will be inferred from
576
        the type of the first element of the default.
577
    - If you provide a dynamic `default` or provide no default, you must also provide `enum_type`.
578

579
    E.g. In all 3 cases the property type is `list[MyEnum]`
580
        EnumListOption(..., enum_type=MyEnum)
581
        EnumListOption(..., default=[MyEnum.Value])
582
        EnumListOption(..., enum_type=MyEnum default=lambda cls: cls.default)
583
    """
584

585
    @overload  # Case: static default
586
    def __new__(
587
        cls,
588
        flag_name: str | None = None,
589
        *,
590
        default: list[_EnumT],
591
        help: _HelpT,
592
        # Additional bells/whistles
593
        register_if: _RegisterIfFuncT | None = None,
594
        advanced: bool | None = None,
595
        default_help_repr: str | None = None,
596
        fromfile: bool | None = None,
597
        metavar: str | None = None,
598
        mutually_exclusive_group: str | None = None,
599
        removal_version: str | None = None,
600
        removal_hint: _HelpT | None = None,
601
        deprecation_start_version: str | None = None,
602
        # Internal bells/whistles
603
        daemon: bool | None = None,
604
        fingerprint: bool | None = None,
605
    ) -> EnumListOption[_EnumT]: ...
606

607
    # N.B. This has an additional param: `enum_type`.
608
    @overload  # Case: dynamic default
609
    def __new__(
610
        cls,
611
        flag_name: str | None = None,
612
        *,
613
        enum_type: type[_EnumT],
614
        default: _DynamicDefaultT,
615
        help: _HelpT,
616
        # Additional bells/whistles
617
        register_if: _RegisterIfFuncT | None = None,
618
        advanced: bool | None = None,
619
        default_help_repr: str | None = None,
620
        fromfile: bool | None = None,
621
        metavar: str | None = None,
622
        mutually_exclusive_group: str | None = None,
623
        removal_version: str | None = None,
624
        removal_hint: _HelpT | None = None,
625
        deprecation_start_version: str | None = None,
626
        # Internal bells/whistles
627
        daemon: bool | None = None,
628
        fingerprint: bool | None = None,
629
    ) -> EnumListOption[_EnumT]: ...
630

631
    # N.B. This has an additional param: `enum_type`.
632
    @overload  # Case: implicit default
633
    def __new__(
634
        cls,
635
        flag_name: str | None = None,
636
        *,
637
        enum_type: type[_EnumT],
638
        help: _HelpT,
639
        # Additional bells/whistles
640
        register_if: _RegisterIfFuncT | None = None,
641
        advanced: bool | None = None,
642
        default_help_repr: str | None = None,
643
        fromfile: bool | None = None,
644
        metavar: str | None = None,
645
        mutually_exclusive_group: str | None = None,
646
        removal_version: str | None = None,
647
        removal_hint: _HelpT | None = None,
648
        deprecation_start_version: str | None = None,
649
        # Internal bells/whistles
650
        daemon: bool | None = None,
651
        fingerprint: bool | None = None,
652
    ) -> EnumListOption[_EnumT]: ...
653

654
    def __new__(
1✔
655
        cls,
656
        flag_name=None,
657
        *,
658
        enum_type=None,
659
        default=[],
660
        help,
661
        # Additional bells/whistles
662
        register_if=None,
663
        advanced=None,
664
        default_help_repr=None,
665
        fromfile=None,
666
        metavar=None,
667
        mutually_exclusive_group=None,
668
        removal_version=None,
669
        removal_hint=None,
670
        deprecation_start_version=None,
671
        # Internal bells/whistles
672
        daemon=None,
673
        fingerprint=None,
674
    ):
675
        instance = super().__new__(
1✔
676
            cls,
677
            flag_name,
678
            default=default,
679
            help=help,
680
            register_if=register_if,
681
            advanced=advanced,
682
            default_help_repr=default_help_repr,
683
            fromfile=fromfile,
684
            metavar=metavar,
685
            mutually_exclusive_group=mutually_exclusive_group,
686
            removal_version=removal_version,
687
            removal_hint=removal_hint,
688
            deprecation_start_version=deprecation_start_version,
689
            daemon=daemon,
690
            fingerprint=fingerprint,
691
        )
692
        instance._enum_type = enum_type
1✔
693
        return instance
1✔
694

695
    def get_member_type(self, subsystem_cls):
1✔
UNCOV
696
        enum_type = self._enum_type
×
UNCOV
697
        default = _eval_maybe_dynamic(self._default, subsystem_cls)
×
UNCOV
698
        if enum_type is None:
×
UNCOV
699
            if not default:
×
700
                raise ValueError(
×
701
                    softwrap(
702
                        """
703
                        `enum_type` must be provided to the constructor if `default` isn't provided
704
                        or is empty.
705
                        """
706
                    )
707
                )
UNCOV
708
            return type(default[0])
×
UNCOV
709
        return enum_type
×
710

711

712
# -----------------------------------------------------------------------------------------------
713
# Dict Concrete Option Classes
714
# -----------------------------------------------------------------------------------------------
715
_ValueT = TypeVar("_ValueT")
1✔
716

717

718
class DictOption(_OptionBase["dict[str, _ValueT]", "dict[str, _ValueT]"], Generic[_ValueT]):
1✔
719
    """A dictionary option mapping strings to client-provided `_ValueT`.
720

721
    If you provide a `default` parameter, the `_ValueT` type parameter will be inferred from the
722
    type of the values in the default. Otherwise, you'll need to provide `_ValueT` if you want a
723
    non-`Any` type.
724

725
    E.g.
726
        # Explicit
727
        DictOption[str](...)  # property type is `dict[str, str]`
728
        DictOption[Any](..., default=dict(key="val"))  # property type is `dict[str, Any]`
729
        # Implicit
730
        DictOption(...)  # property type is `dict[str, Any]`
731
        DictOption(..., default={"key": "val"})  # property type is `dict[str, str]`
732
        DictOption(..., default={"key": 1})  # property type is `dict[str, int]`
733
        DictOption(..., default={"key1": 1, "key2": "str"})  # property type is `dict[str, Any]`
734

735
    NOTE: The property returns a mutable object. Care should be used to not mutate the object.
736
    NOTE: Dictionary values are simply returned as parsed, and are not guaranteed to be of the
737
    `_ValueT` specified.
738
    """
739

740
    option_type: Any = dict
1✔
741

742
    def __new__(
1✔
743
        cls,
744
        flag_name: str | None = None,
745
        *,
746
        default: _MaybeDynamicT[dict[str, _ValueT]] = {},
747
        help,
748
        # Additional bells/whistles
749
        register_if: _RegisterIfFuncT | None = None,
750
        advanced: bool | None = None,
751
        default_help_repr: str | None = None,
752
        fromfile: bool | None = None,
753
        metavar: str | None = None,
754
        mutually_exclusive_group: str | None = None,
755
        removal_version: str | None = None,
756
        removal_hint: _HelpT | None = None,
757
        deprecation_start_version: str | None = None,
758
        # Internal bells/whistles
759
        daemon: bool | None = None,
760
        fingerprint: bool | None = None,
761
    ):
762
        return super().__new__(
1✔
763
            cls,
764
            flag_name,
765
            default=default,
766
            help=help,
767
            register_if=register_if,
768
            advanced=advanced,
769
            daemon=daemon,
770
            default_help_repr=default_help_repr,
771
            fingerprint=fingerprint,
772
            fromfile=fromfile,
773
            metavar=metavar,
774
            mutually_exclusive_group=mutually_exclusive_group,
775
            removal_hint=removal_hint,
776
            removal_version=removal_version,
777
            deprecation_start_version=deprecation_start_version,
778
        )
779

780
    def _convert_(self, val: Any) -> dict[str, _ValueT]:
1✔
UNCOV
781
        return cast("dict[str, _ValueT]", val)
×
782

783

784
# -----------------------------------------------------------------------------------------------
785
# "Specialized" Concrete Option Classes
786
# -----------------------------------------------------------------------------------------------
787

788

789
class SkipOption(BoolOption[bool]):
1✔
790
    """A --skip option (for an invocable tool)."""
791

792
    def __new__(cls, goal: str, *other_goals: str):
1✔
UNCOV
793
        goals = (goal,) + other_goals
×
UNCOV
794
        invocation_str = " and ".join([f"`{bin_name()} {goal}`" for goal in goals])
×
UNCOV
795
        return super().__new__(
×
796
            cls,
797
            default=False,
798
            help=lambda subsystem_cls: (
799
                f"If true, don't use {subsystem_cls.name} when running {invocation_str}."
800
            ),
801
        )
802

803

804
class ArgsListOption(ShellStrListOption):
1✔
805
    """An option for arguments passed to some other tool."""
806

807
    def __new__(
1✔
808
        cls,
809
        *,
810
        example: str,
811
        extra_help: str = "",
812
        tool_name: str | None = None,
813
        # This should be set when callers can alternatively use "--" followed by the arguments,
814
        # instead of having to provide "--[scope]-args='--arg1 --arg2'".
815
        passthrough: bool | None = None,
816
        default: _MaybeDynamicT[list[_ListMemberT]] | None = None,
817
    ):
818
        if extra_help:
1✔
819
            extra_help = "\n\n" + extra_help
1✔
820
        instance = super().__new__(
1✔
821
            cls,
822
            help=(
823
                lambda subsystem_cls: softwrap(
824
                    f"""
825
                    Arguments to pass directly to {tool_name or subsystem_cls.name},
826
                    e.g. `--{subsystem_cls.options_scope}-args='{example}'`.{extra_help}
827
                    """
828
                )
829
            ),
830
            default=default,  # type: ignore[arg-type]
831
        )
832
        if passthrough is not None:
1✔
833
            instance._extra_kwargs["passthrough"] = passthrough
1✔
834
        return instance
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc