diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py index c8abce3a1a..d942206d1e 100644 --- a/tests/unit/test_utils.py +++ b/tests/unit/test_utils.py @@ -1539,7 +1539,7 @@ def test_locate_key_origin_empty_defined(id_tree_empty: fmf.Tree) -> None: (tmt.utils.Environment.from_dict({'FOO': 'BAR'}), None, 'FOO\033[0m: BAR'), # fmf context ( - tmt.utils.FmfContext({'foo': ['bar', 'baz']}), + tmt.utils.FmfContext(foo=['bar', 'baz']), None, """ foo\033[0m: diff --git a/tmt/base/plan.py b/tmt/base/plan.py index 37aecb46a1..68c27b5ce0 100644 --- a/tmt/base/plan.py +++ b/tmt/base/plan.py @@ -601,7 +601,7 @@ def fmf_context(self) -> tmt.utils.FmfContext: """ return FmfContext( - {**self.context, **self._fmf_context_from_importing, **self._fmf_context_from_cli} + **self.context, **self._fmf_context_from_importing, **self._fmf_context_from_cli ) @property @@ -617,10 +617,8 @@ def _inheritable_fmf_context(self) -> FmfContext: """ return FmfContext( - { - **self.context, - **self._fmf_context_from_importing, - } + **self.context, + **self._fmf_context_from_importing, ) @property @@ -1516,15 +1514,13 @@ def _convert_node(node: fmf.Tree) -> 'Plan': # For final context inheritance, respect inherit_context setting if reference.inherit_context: alteration_fmf_context = FmfContext( - { - **imported_fmf_context, - **self._inheritable_fmf_context, - **self._noninheritable_fmf_context, - } + **imported_fmf_context, + **self._inheritable_fmf_context, + **self._noninheritable_fmf_context, ) else: alteration_fmf_context = FmfContext( - {**imported_fmf_context, **self._noninheritable_fmf_context} + **imported_fmf_context, **self._noninheritable_fmf_context ) # Adjust the imported tree, to let any `adjust` rules defined in it take diff --git a/tmt/result.py b/tmt/result.py index 12d69ee964..93d4613284 100644 --- a/tmt/result.py +++ b/tmt/result.py @@ -333,7 +333,7 @@ class Result(BaseResult): context: tmt.utils.FmfContext = field( default_factory=tmt.utils.FmfContext, serialize=lambda context: context.to_spec(), - unserialize=lambda serialized: tmt.utils.FmfContext(serialized), + unserialize=lambda serialized: tmt.utils.FmfContext(**serialized), ) ids: ResultIds = field(default_factory=cast(Callable[[], ResultIds], dict)) guest: ResultGuestData = field( diff --git a/tmt/utils/__init__.py b/tmt/utils/__init__.py index 5e9ec8acf4..edae91999c 100644 --- a/tmt/utils/__init__.py +++ b/tmt/utils/__init__.py @@ -28,7 +28,7 @@ import urllib.parse import warnings from collections import Counter -from collections.abc import Iterable, Iterator +from collections.abc import Iterable, Iterator, Mapping from math import ceil from re import Pattern from threading import RLock, Thread @@ -330,7 +330,7 @@ def effective_workdir_root(workdir_root_option: Optional[Path] = None) -> Path: return WORKDIR_ROOT -class FmfContext(dict[str, list[str]]): +class FmfContext(Mapping[str, list[str]]): """ Represents an fmf context. @@ -338,8 +338,19 @@ class FmfContext(dict[str, list[str]]): and https://fmf.readthedocs.io/en/latest/context.html. """ - def __init__(self, data: Optional[dict[str, list[str]]] = None) -> None: - super().__init__(data or {}) + _data: dict[str, list[str]] + + def __init__(self, **kwargs: list[str]) -> None: + self._data = kwargs + + def __getitem__(self, key: str) -> list[str]: + return self._data[key] + + def __len__(self) -> int: + return len(self._data) + + def __iter__(self) -> Iterator[str]: + yield from self._data @classmethod def _normalize_command_line(cls, spec: list[str], logger: tmt.log.Logger) -> 'FmfContext': @@ -360,7 +371,7 @@ def _normalize_command_line(cls, spec: list[str], logger: tmt.log.Logger) -> 'Fm f"Use 'KEY=VALUE' format or remove the dimension entirely." ) raw_fmf_context[key] = value.split(',') - return FmfContext(raw_fmf_context) + return FmfContext(**raw_fmf_context) @classmethod def _normalize_fmf( @@ -378,15 +389,14 @@ def _normalize_fmf( - ppc64 """ - normalized: FmfContext = FmfContext() - - for dimension, values in spec.items(): - if isinstance(values, list): - normalized[str(dimension)] = [str(v) for v in values] - else: - normalized[str(dimension)] = [str(values)] - - return normalized + return FmfContext( + **{ + str(dimension): [str(v) for v in values] + if isinstance(values, list) + else [str(values)] + for dimension, values in spec.items() + } + ) @classmethod def from_spec(cls, key_address: str, spec: Any, logger: tmt.log.Logger) -> 'FmfContext': @@ -423,7 +433,7 @@ def from_serialized(cls, serialized: dict[str, list[str]]) -> 'FmfContext': Convert from a serialized form. """ - return FmfContext(serialized) + return FmfContext(**serialized) #: A type of environment variable name. @@ -4573,7 +4583,7 @@ def format_value( def format( key: str, - value: Union[None, float, bool, str, list[Any], dict[Any, Any]] = None, + value: Union[None, float, bool, str, list[Any], Mapping[Any, Any]] = None, indent: int = 24, window_size: int = OUTPUT_WIDTH, wrap: FormatWrap = 'auto',