"""``ChoiceConf()`` for reporters."""
from collections.abc import Hashable
from typing import Any, Iterable, Sequence
from simplebench.enums import FlagType, Format, Section, Target
from simplebench.exceptions import SimpleBenchTypeError
from simplebench.reporters.choice.exceptions import _ChoiceConfErrorTag
from simplebench.reporters.protocols import ChoiceProtocol
from simplebench.reporters.reporter.options import ReporterOptions
from simplebench.validators import (
validate_bool,
validate_iterable_of_type,
validate_sequence_of_str,
validate_string,
validate_type,
)
[docs]
class ChoiceConf(Hashable, ChoiceProtocol):
"""Definition of a :class:`~.Choice` configuration for reporters.
A :class:`~.ChoiceConf` represents a specific configuration of an implied :class:`~.Choice`
that can be registered with a :class:`~simplebench.reporters.reporter.Reporter` subclass.
It defines the sections to include in the report, the output targets, the output format,
and various other options related to reporting.
The :class:`~.ChoiceConf` class provides a structured way to define different
reporting options within the SimpleBench framework so that
users can select from predefined configurations and developers can
easily add new reporting options to the framework.
A :class:`~.ChoiceConf` instance is immutable after creation to ensure consistency
in reporting configurations.
The sections, targets, and formats are descriptive only; they do not
enforce any behavior on the associated :class:`~simplebench.reporters.reporter.Reporter`
subclass. It is the responsibility of the :class:`~simplebench.reporters.reporter.Reporter`
subclass to implement the behavior corresponding to the specified sections, targets,
and formats.
The :class:`~.ChoiceConf` is intended to be used in defining configurations for
reporters without depending on being embedded in the
:meth:`~simplebench.reporters.reporter.Reporter.__init__`
method for definition. This allows for more flexible and reusable
reporter configurations. The live counterpart to :class:`~.ChoiceConf` is the
:class:`~.Choice` class, which is used at runtime with actual :class:`~.Choice` instances
created by :class:`~simplebench.reporters.reporter.Reporter` from
:class:`~.ChoiceConf` definitions.
:param flags: A set of command-line flags associated with the choice.
:type flags: set[str]
:param flag_type: The type of command-line flag (e.g., boolean, target_list, etc.).
:type flag_type: :class:`~simplebench.enums.FlagType`
:param name: A unique name for the choice.
:type name: str
:param description: A brief description of the choice.
:type description: str
:param sections: A set of :class:`~simplebench.enums.Section` enums to include in the report.
:type sections: set[:class:`~simplebench.enums.Section`]
:param targets: A set of :class:`~simplebench.enums.Target` enums for output.
:type targets: set[:class:`~simplebench.enums.Target`]
:param default_targets: A set of :class:`~simplebench.enums.Target` enums representing the
default targets for the choice. If ``None``, no default targets are
specified and the reporter's defaults will be used when generating
reports.
:type default_targets: set[:class:`~simplebench.enums.Target`] | None
:param output_format: A :class:`~simplebench.enums.Format` instance describing the output
format.
:type output_format: :class:`~simplebench.enums.Format`
:param subdir: An optional subdirectory for output files. If ``None``, reports default to
the reporter's subdir.
:type subdir: str | None
:param file_suffix: An optional file suffix for output files. If ``None``, reports default
to the reporter's file_suffix.
:type file_suffix: str | None
:param file_unique: Whether to make output file names unique. If ``None``, reports default
to the reporter's file_unique.
:type file_unique: bool | None
:param file_append: Whether to append to existing output files. If ``None``, reports default
to the reporter's file_append.
:type file_append: bool | None
:param options: An optional
:class:`~simplebench.reporters.reporter.options.ReporterOptions`
instance for additional configurations specific to a specific reporter.
If ``None``, reports default to the reporter's default options.
:type options: :class:`~simplebench.reporters.reporter.options.ReporterOptions` | None
:param extra: Any additional metadata associated with the choice.
:type extra: Any | None
"""
def __init__(self, *,
flags: Sequence[str],
flag_type: FlagType,
name: str,
description: str,
sections: Iterable[Section],
output_format: Format,
targets: Iterable[Target],
default_targets: Iterable[Target] | None = None,
subdir: str | None = None,
file_suffix: str | None = None,
file_unique: bool | None = None,
file_append: bool | None = None,
options: ReporterOptions | None = None,
extra: Any = None) -> None:
"""Construct a :class:`~.ChoiceConf` instance.
:param flags: An iterable of command-line flags associated with the choice.
:type flags: Iterable[str]
:param flag_type: The type of command-line flag (e.g., boolean, target_list, etc.).
:type flag_type: :class:`~simplebench.enums.FlagType`
:param name: A unique name for the choice.
:type name: str
:param description: A brief description of the choice.
:type description: str
:param sections: An iterable of :class:`~simplebench.enums.Section` enums to include
in the report. It must be non-empty, but
:attr:`~simplebench.enums.Section.NULL` may be included to indicate
no sections are specifically selected. The reporter is expected to
include listed sections in its report.
:type sections: Iterable[:class:`~simplebench.enums.Section`]
:param output_format: A :class:`~simplebench.enums.Format` instance describing the
output format.
:type output_format: :class:`~simplebench.enums.Format`
:param targets: An iterable of :class:`~simplebench.enums.Target` enums for output.
It must be non-empty. If multiple targets are specified, the reporter
is expected to handle outputting to all specified targets when this
choice is selected.
:type targets: Iterable[:class:`~simplebench.enums.Target`]
:param default_targets: An optional iterable of default
:class:`~simplebench.enums.Target` enums.
The enums represent the default targets for the choice.
If ``None``, no default targets are specified and the
reporter's defaults will be used when generating reports.
:type default_targets: Iterable[:class:`~simplebench.enums.Target`] | None
:param subdir: An optional subdirectory for output files. If ``None``, defaults to the
reporter's default subdir. It may only consist of alphanumeric
characters (a-z, A-Z, 0-9) or be an empty string (to indicate no
subdirectory). It cannot be longer than 64 characters and may be left
as ``None`` to use the reporter's default.
:type subdir: str | None
:param file_suffix: An optional file suffix for output files. If ``None``, defaults to
the reporter's default file_suffix when generating reports.
It may only consist of alphanumeric characters (a-z, A-Z, 0-9),
and be no longer than 10 characters. It may be left as ``None``
to use the reporter's default.
:type file_suffix: str | None
:param file_unique: Whether to make output file names unique by appending a unique
identifier. Mutually exclusive with `file_append`; both cannot be
``True`` or ``False`` at the same time. If ``None``, defaults to
the reporter's default file_unique setting.
:type file_unique: bool | None
:param file_append: Whether to append to existing output files instead of overwriting
them. Mutually exclusive with `file_unique`; both cannot be
``True`` or ``False`` at the same time. If ``None``, defaults to
the reporter's default file_append setting.
:type file_append: bool | None
:param options: An optional
:class:`~simplebench.reporters.reporter.options.ReporterOptions`
instance for additional configuration specific to a reporter.
The option must be of the same type as that specified by the
``options_type`` property of the associated
:class:`~simplebench.reporters.reporter.Reporter` subclass.
:type options: :class:`~simplebench.reporters.reporter.options.ReporterOptions` | None
:param extra: Any additional metadata associated with the choice. This can be used to
store custom information relevant to the choice and the core
benchmarking framework does not interpret or enforce any structure on
this data. :class:`~simplebench.reporters.reporter.Reporter` subclasses
may choose to utilize this field for their own purposes.
:type extra: Any
:raises SimpleBenchTypeError: If any argument is of an incorrect type.
:raises SimpleBenchValueError: If any argument has an invalid value (e.g., empty
strings or empty sequences).
"""
self._flags: frozenset[str] = frozenset(validate_sequence_of_str(
flags, "flags",
_ChoiceConfErrorTag.FLAGS_INVALID_ARG_TYPE,
_ChoiceConfErrorTag.FLAGS_INVALID_ARGS_VALUE,
allow_empty=False, allow_blank=False, allow_whitespace=False))
"""Flags associated with the choice. These are used for command-line selection.
They must be unique across all choices for all reporters. This is enforced
by the ReporterManager when choices are registered.
The flags should be in the format used on the command line,
typically starting with '--' for long options.
Example: ['--json', '--json-full']
The description property of the Choice is used to provide
help text for the flags when generating command-line help.
"""
self._flag_type = validate_type(
flag_type, FlagType, "flag_type",
_ChoiceConfErrorTag.FLAG_TYPE_INVALID_ARG_TYPE)
"""The type of command-line flag (e.g., FlagType.BOOLEAN, FlagType.TARGET_LIST, etc.)
(private backing field for attribute)"""
self._name: str = validate_string(
name, "name",
_ChoiceConfErrorTag.NAME_INVALID_ARG_TYPE,
_ChoiceConfErrorTag.NAME_INVALID_ARG_VALUE,
allow_empty=False, allow_blank=False)
"""Name of the choice (private backing field for attribute)"""
self._description: str = validate_string(
description, "description",
_ChoiceConfErrorTag.DESCRIPTION_INVALID_ARG_TYPE,
_ChoiceConfErrorTag.DESCRIPTION_INVALID_ARG_VALUE,
allow_empty=False, allow_blank=False)
"""Description of the choice (private backing field for attribute)"""
self._sections: frozenset[Section] = frozenset(validate_iterable_of_type(
sections, Section, "sections",
_ChoiceConfErrorTag.SECTIONS_INVALID_ARG_TYPE,
_ChoiceConfErrorTag.SECTIONS_INVALID_ARG_VALUE,
allow_empty=False))
"""Sections included in the choice (private backing field for attribute)"""
self._targets: frozenset[Target] = frozenset(validate_iterable_of_type(
targets, Target, "targets",
_ChoiceConfErrorTag.TARGETS_INVALID_ARG_TYPE,
_ChoiceConfErrorTag.TARGETS_INVALID_ARG_VALUE,
allow_empty=False))
"""Output targets for the choice (private backing field for attribute)"""
self._default_targets: frozenset[Target] = frozenset()
"""Default output targets for the choice (private backing field for attribute)"""
if default_targets is not None:
self._default_targets = frozenset(validate_iterable_of_type(
default_targets, Target, "default_targets",
_ChoiceConfErrorTag.DEFAULT_TARGETS_INVALID_ARG_TYPE,
_ChoiceConfErrorTag.DEFAULT_TARGETS_INVALID_ARG_VALUE,
allow_empty=True))
self._subdir: str | None = None
"""An optional subdirectory for output files (private backing field for attribute)"""
if subdir is not None:
self._subdir = validate_string(
subdir, "subdir",
_ChoiceConfErrorTag.SUBDIR_INVALID_ARG_TYPE,
_ChoiceConfErrorTag.SUBDIR_INVALID_ARG_VALUE,
allow_empty=True,
alphanumeric_only=True)
if len(self._subdir) > 64:
raise SimpleBenchTypeError(
"subdir cannot be longer than 64 characters",
tag=_ChoiceConfErrorTag.SUBDIR_TOO_LONG)
self._file_suffix: str | None = None
"""An optional file suffix for output files (private backing field for attribute)"""
if file_suffix is not None:
file_suffix = validate_string(
file_suffix, "file_suffix",
_ChoiceConfErrorTag.FILE_SUFFIX_INVALID_ARG_TYPE,
_ChoiceConfErrorTag.FILE_SUFFIX_INVALID_ARG_VALUE,
allow_empty=True, allow_blank=False, alphanumeric_only=True)
if len(file_suffix) > 10:
raise SimpleBenchTypeError(
"file_suffix cannot be longer than 10 characters",
tag=_ChoiceConfErrorTag.FILE_SUFFIX_TOO_LONG)
self._file_suffix = file_suffix
self._file_unique: bool | None = validate_bool(
file_unique, 'file_unique',
_ChoiceConfErrorTag.FILE_UNIQUE_INVALID_ARG_TYPE,
allow_none=True)
"""Whether to make output file names unique (private backing field for attribute)"""
self._file_append: bool | None = validate_bool(
file_append, 'file_append',
_ChoiceConfErrorTag.FILE_APPEND_INVALID_ARG_TYPE,
allow_none=True)
"""Whether to append to existing output files (private backing field for attribute)"""
# Ensure that if one is None and the other is not, we set the None one
# to the opposite of the other to maintain mutual exclusivity
# and to prevent prioritization logic from having to handle the case
# where only one is None.
if self._file_unique is None and self._file_append is not None:
self._file_unique = not self._file_append
elif self._file_append is None and self._file_unique is not None:
self._file_append = not self._file_unique
if (self._file_unique is not None and
self._file_append is not None and
self._file_unique == self._file_append):
raise SimpleBenchTypeError(
"file_unique and file_append are mutually exclusive; "
"both cannot be True or False at the same time",
tag=_ChoiceConfErrorTag.FILE_UNIQUE_FILE_APPEND_MUTUALLY_EXCLUSIVE)
self._output_format: Format = validate_type(
output_format, Format, "output_format",
_ChoiceConfErrorTag.OUTPUT_FORMAT_INVALID_ARG_TYPE)
"""Output format for the choice (private backing field for attribute)"""
self._options: ReporterOptions | None = None
"""An optional ReporterOptions for additional configuration of the reporter
(private backing field for attribute)"""
if options is not None:
self._options = validate_type(
options, ReporterOptions, "options",
_ChoiceConfErrorTag.OPTIONS_INVALID_ARG_TYPE)
self._extra: Any = extra
"""Additional metadata associated with the choice (private backing field for attribute)"""
@property
def flags(self) -> frozenset[str]:
"""Flags associated with the choice. These are used for command-line selection.
They must be unique across all choices for all reporters. This is enforced
by the :class:`~simplebench.reporters.reporter_manager.ReporterManager` when choices
are registered.
The flags should be in the format used on the command line,
typically starting with ``--`` for long options.
.. code-block:: python
['--json', '--json-full']
The :attr:`~.description` property of the :class:`~.Choice` is used to provide
help text for the flags when generating command-line help.
"""
return self._flags
@property
def flag_type(self) -> FlagType:
"""The type of command-line flag (e.g., :attr:`~simplebench.enums.FlagType.BOOLEAN`,
:attr:`~simplebench.enums.FlagType.TARGET_LIST`, etc.)."""
return self._flag_type
@property
def name(self) -> str:
"""Name of the choice."""
return self._name
@property
def description(self) -> str:
"""Description of the choice.
This is used as help text for the command-line flags associated with the choice."""
return self._description
@property
def sections(self) -> frozenset[Section]:
"""Sections included in the choice.
These are the sections that the associated
:class:`~simplebench.reporters.reporter.Reporter` subclass
is expected to include in its report when this choice is selected."""
return self._sections
@property
def targets(self) -> frozenset[Target]:
"""Output targets for the choice.
These are the output targets that the associated
:class:`~simplebench.reporters.reporter.Reporter` subclass
is expected to use when this choice is selected."""
return self._targets
@property
def default_targets(self) -> frozenset[Target] | None:
"""Default output targets for the choice.
These are the default output targets that the associated
:class:`~simplebench.reporters.reporter.Reporter` subclass
should use when this choice is selected, if no specific target
is provided by the user."""
return self._default_targets
@property
def subdir(self) -> str | None:
"""An optional subdirectory for output files.
If specified, the associated :class:`~simplebench.reporters.reporter.Reporter` subclass
should use this subdirectory for output files when this choice
is selected. If an empty string, no subdirectory
should be used. If ``None``, the reporter's default subdir
should be used."""
return self._subdir
@property
def file_suffix(self) -> str | None:
"""An optional file suffix for output files.
If specified, the associated :class:`~simplebench.reporters.reporter.Reporter` subclass
should use this file suffix for output files when this choice
is selected. If ``None``, the reporter's default file_suffix
should be used."""
return self._file_suffix
@property
def file_unique(self) -> bool | None:
"""Whether to make output file names unique.
If specified, the associated :class:`~simplebench.reporters.reporter.Reporter` subclass
should use this setting for output files when this choice
is selected. If ``None``, the reporter's default file_unique
setting should be used."""
return self._file_unique
@property
def file_append(self) -> bool | None:
"""Whether to append to existing output files.
If specified, the associated :class:`~simplebench.reporters.reporter.Reporter` subclass
should use this setting for output files when this choice
is selected. If ``None``, the reporter's default file_append
setting should be used."""
return self._file_append
@property
def output_format(self) -> Format:
"""Output format for the choice.
This is the output format that the associated
:class:`~simplebench.reporters.reporter.Reporter` subclass
is expected to use when this choice is selected.
:return: The output format.
:rtype: :class:`~simplebench.enums.Format`
"""
return self._output_format
@property
def options(self) -> ReporterOptions | None:
"""An optional :class:`~simplebench.reporters.reporter.options.ReporterOptions`
instance for additional configuration."""
return self._options
@property
def extra(self) -> Any:
"""Any additional metadata associated with the choice."""
return self._extra
def __hash__(self) -> int:
"""Compute a hash value for the :class:`~.ChoiceConf` instance.
:return: The computed hash value.
:rtype: int
"""
return hash((
self.flags,
self.flag_type,
self.name,
self.description,
self.sections,
self.targets,
self.default_targets,
self.subdir,
self.file_suffix,
self.file_unique,
self.file_append,
self.output_format,
self.options,
self.extra
))
def __eq__(self, other: object) -> bool:
"""Check equality between two :class:`~.ChoiceConf` instances.
:param other: The other :class:`~.ChoiceConf` instance to compare against.
:type other: object
:return: ``True`` if the :class:`~.ChoiceConf` instances are equal, ``False`` otherwise.
:rtype: bool
"""
if not isinstance(other, ChoiceConf):
return False
return (self.flags == other.flags and
self.flag_type == other.flag_type and
self.name == other.name and
self.description == other.description and
self.sections == other.sections and
self.targets == other.targets and
self.default_targets == other.default_targets and
self.subdir == other.subdir and
self.file_suffix == other.file_suffix and
self.file_unique == other.file_unique and
self.file_append == other.file_append and
self.output_format == other.output_format and
self.options == other.options and
self.extra == other.extra)