diff --git a/.packit.yaml b/.packit.yaml index 9f3dc0e629..baa9124324 100644 --- a/.packit.yaml +++ b/.packit.yaml @@ -50,7 +50,7 @@ _: - &copr-under-packit job: copr_build additional_repos: - - copr://@teemtee/stable + - copr://packit/teemtee-fmf-293 # Copr jobs under the teemtee project - &copr-under-teemtee diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8aee266f0e..07df9b7723 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -254,16 +254,16 @@ repos: - '--rst-directives' - 'versionadded,versionchanged' - - repo: https://github.com/astral-sh/uv-pre-commit - rev: 0.10.9 - hooks: - # Keep pyproject.toml and uv.lock in sync - - id: uv-lock - # Keep pylock.toml in sync - - id: uv-export - args: - - "--frozen" - - "--format=pylock.toml" - - "--output-file=pylock.toml" - - "--all-extras" - - "--quiet" +# - repo: https://github.com/astral-sh/uv-pre-commit +# rev: 0.10.9 +# hooks: +# # Keep pyproject.toml and uv.lock in sync +# - id: uv-lock +# # Keep pylock.toml in sync +# - id: uv-export +# args: +# - "--frozen" +# - "--format=pylock.toml" +# - "--output-file=pylock.toml" +# - "--all-extras" +# - "--quiet" diff --git a/pyproject.toml b/pyproject.toml index d6a72e8c4d..9f5554b06a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,7 +38,7 @@ dependencies = [ "importlib-resources; python_version < '3.12'", # MultiplexedPath is broken on earlier versions "click>=8.0.3", "docutils>=0.16", - "fmf>=1.7.0", + "fmf>=1.8.0", "jinja2>=2.11.3", "packaging>=20.9", "pint>=0.16.1", diff --git a/tmt/base/core.py b/tmt/base/core.py index 3015530c9e..9fdad567af 100644 --- a/tmt/base/core.py +++ b/tmt/base/core.py @@ -28,7 +28,6 @@ import fmf import fmf.base -import fmf.context import fmf.utils import jsonschema from click import confirm, echo @@ -60,6 +59,7 @@ container_fields, field, ) +from tmt.context import TmtContext from tmt.lint import LinterOutcome, LinterReturn from tmt.result import ResultInterpret from tmt.utils import ( @@ -2387,8 +2387,7 @@ def tree(self) -> fmf.Tree: raise tmt.utils.GeneralError("Invalid yaml syntax.") from error # Adjust metadata for current fmf context self._tree.adjust( - fmf.context.Context(**self.fmf_context), - case_sensitive=False, + TmtContext(**self.fmf_context), decision_callback=create_adjust_callback(self._logger), additional_rules=self._additional_rules, ) @@ -3532,8 +3531,7 @@ def resolve_dynamic_ref( if not plan: raise tmt.utils.FileError("Cannot get plan fmf context to evaluate dynamic ref.") reference_tree.adjust( - fmf.context.Context(**plan.fmf_context), - case_sensitive=False, + TmtContext(**plan.fmf_context), decision_callback=create_adjust_callback(logger), ) # Also temporarily build a plan so that env and context variables are expanded diff --git a/tmt/base/plan.py b/tmt/base/plan.py index 37aecb46a1..5a0a07a914 100644 --- a/tmt/base/plan.py +++ b/tmt/base/plan.py @@ -9,7 +9,6 @@ from typing import TYPE_CHECKING, Any, ClassVar, Optional, Union, cast import fmf -import fmf.context import fmf.utils from click import echo from fmf.utils import listed # pyright: ignore[reportUnknownVariableType] @@ -47,6 +46,7 @@ ) from tmt.base.run import Run from tmt.container import SpecBasedContainer, container, field +from tmt.context import TmtContext from tmt.lint import LinterOutcome, LinterReturn from tmt.utils import ( Command, @@ -1530,8 +1530,7 @@ def _convert_node(node: fmf.Tree) -> 'Plan': # Adjust the imported tree, to let any `adjust` rules defined in it take # action, including the adjust-plans rules. node.adjust( - fmf.context.Context(**alteration_fmf_context), - case_sensitive=False, + TmtContext(**alteration_fmf_context), additional_rules=reference.adjust_plans, ) diff --git a/tmt/cli/about.py b/tmt/cli/about.py index 5e8df6ffb8..c0be3cc991 100644 --- a/tmt/cli/about.py +++ b/tmt/cli/about.py @@ -39,6 +39,7 @@ def _render_plugins_list_rest(logger: tmt.log.Logger) -> str: r'prepare.feature': 'prepare/feature plugins', r'prepare.install': 'prepare/install plugins', r'prepare.artifact.providers': 'prepare/artifact provider plugins', + r'context': 'Fmf context plugins', r'step\.([a-z]+)': '{{ MATCH.group(1).capitalize() }} step plugins', } diff --git a/tmt/context/__init__.py b/tmt/context/__init__.py new file mode 100644 index 0000000000..2c9406080c --- /dev/null +++ b/tmt/context/__init__.py @@ -0,0 +1,31 @@ +from typing import Generic, TypeVar + +from fmf.context import Context, ContextDimension, DefaultContextDimension + +import tmt.utils +from tmt.plugins import PluginRegistry + +T = TypeVar("T") + + +class ContextError(tmt.utils.GeneralError): + """ + Error trying to create a context + """ + + +class TmtDefaultContextDimension(DefaultContextDimension): + case_sensitive = False + + +class TmtContextDimension(ContextDimension[T], Generic[T]): + _default_dimension_cls = TmtDefaultContextDimension + _registrar = {} + + +class TmtContext(Context): + _context_dimensions = TmtContextDimension + + +_CONTEXT_REGISTRY = PluginRegistry('context') +provides_context = _CONTEXT_REGISTRY.create_decorator() diff --git a/tmt/export/nitrate.py b/tmt/export/nitrate.py index 5d041e42a2..a87751bc2c 100644 --- a/tmt/export/nitrate.py +++ b/tmt/export/nitrate.py @@ -14,11 +14,12 @@ cast, ) -import fmf.context +import fmf.utils from click import echo import tmt.export import tmt.utils +from tmt.context import TmtContext from tmt.utils import ConvertError, Path from tmt.utils.structured_field import StructuredField from tmt.utils.themes import style @@ -289,9 +290,9 @@ def enabled_for_environment(test: 'tmt.base.core.Test', tcms_notes: str) -> bool return True try: - context = fmf.context.Context(**context_dict) + context = TmtContext(**context_dict) test_node = test.node.copy() - test_node.adjust(context, case_sensitive=False) + test_node.adjust(context) return tmt.Test(node=test_node, logger=test._logger).enabled except BaseException as exception: log.debug(f"Failed to process adjust: {exception}") diff --git a/tmt/plugins/__init__.py b/tmt/plugins/__init__.py index 384e2b2e06..e8ff483672 100644 --- a/tmt/plugins/__init__.py +++ b/tmt/plugins/__init__.py @@ -82,6 +82,7 @@ def _discover_packages() -> list[tuple[str, Path]]: ('tmt.steps.prepare.feature', Path('steps/prepare/feature')), ('tmt.steps.prepare.artifact.providers', Path('steps/prepare/artifact/providers')), ('tmt.plugins.plan_shapers', Path('plugins/plan_shapers')), + ('tmt.context', Path('context')), ] diff --git a/tmt/steps/__init__.py b/tmt/steps/__init__.py index 051fe092d6..b88445b0a3 100644 --- a/tmt/steps/__init__.py +++ b/tmt/steps/__init__.py @@ -56,6 +56,7 @@ option_to_key, simple_field, ) +from tmt.context import TmtContext from tmt.options import ClickOptionDecoratorType, option from tmt.result import ResultOutcome from tmt.utils import ( @@ -2305,7 +2306,7 @@ def enabled_by_when(self) -> bool: Check if the plugin is enabled by 'when' keyword """ - fmf_context = fmf.context.Context(**self.step.plan.fmf_context) + fmf_context = TmtContext(**self.step.plan.fmf_context) when_rules = self.get('when', []) if not when_rules: # No 'when' -> enabled everywhere diff --git a/tmt/trying.py b/tmt/trying.py index d3f9d5f099..4de33d1d78 100644 --- a/tmt/trying.py +++ b/tmt/trying.py @@ -12,7 +12,6 @@ from typing import Any, Callable, ClassVar, Optional, Union, cast import fmf -import fmf.context import fmf.utils import tmt @@ -28,6 +27,7 @@ import tmt.utils from tmt import Plan from tmt.base.run import RunData +from tmt.context import TmtContext from tmt.steps.prepare import PreparePlugin from tmt.utils import Command, GeneralError, MetadataError, Path from tmt.utils.themes import style @@ -305,8 +305,7 @@ def get_default_plans(self, run: tmt.base.run.Run) -> list[Plan]: # each user plan in separation and merge them to the main tree. for user_plan in user_plans: user_plan.adjust( - fmf.context.Context(**self.tree.fmf_context), - case_sensitive=False, + TmtContext(**self.tree.fmf_context), decision_callback=tmt.base.core.create_adjust_callback(self._logger), additional_rules=self.tree._additional_rules, )