Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move dataclass to dedicated module #3195

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 13 additions & 11 deletions docs/scripts/generate-plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from typing import Any

import tmt.checks
import tmt.container
import tmt.log
import tmt.plugins
import tmt.steps
Expand All @@ -17,7 +18,8 @@
import tmt.steps.provision
import tmt.steps.report
import tmt.utils
from tmt.utils import ContainerClass, Path
from tmt.container import ContainerClass
from tmt.utils import Path
from tmt.utils.templates import render_template_file

REVIEWED_PLUGINS: tuple[str, ...] = (
Expand All @@ -37,7 +39,7 @@
def _is_ignored(
container: ContainerClass,
field: dataclasses.Field[Any],
metadata: tmt.utils.FieldMetadata) -> bool:
metadata: tmt.container.FieldMetadata) -> bool:
""" Check whether a given field is to be ignored in documentation """

if field.name in ('how', '_OPTIONLESS_FIELDS'):
Expand All @@ -52,7 +54,7 @@ def _is_ignored(
def _is_inherited(
container: ContainerClass,
field: dataclasses.Field[Any],
metadata: tmt.utils.FieldMetadata) -> bool:
metadata: tmt.container.FieldMetadata) -> bool:
""" Check whether a given field is inherited from step data base class """

# TODO: for now, it's a list, but inspecting the actual tree of classes
Expand All @@ -65,8 +67,8 @@ def container_ignored_fields(container: ContainerClass) -> list[str]:

field_names: list[str] = []

for field in tmt.utils.container_fields(container):
_, _, _, _, metadata = tmt.utils.container_field(container, field.name)
for field in tmt.container.container_fields(container):
_, _, _, _, metadata = tmt.container.container_field(container, field.name)

if _is_ignored(container, field, metadata):
field_names.append(field.name)
Expand All @@ -79,8 +81,8 @@ def container_inherited_fields(container: ContainerClass) -> list[str]:

field_names: list[str] = []

for field in tmt.utils.container_fields(container):
_, _, _, _, metadata = tmt.utils.container_field(container, field.name)
for field in tmt.container.container_fields(container):
_, _, _, _, metadata = tmt.container.container_field(container, field.name)

if _is_inherited(container, field, metadata):
field_names.append(field.name)
Expand All @@ -93,8 +95,8 @@ def container_intrinsic_fields(container: ContainerClass) -> list[str]:

field_names: list[str] = []

for field in tmt.utils.container_fields(container):
_, _, _, _, metadata = tmt.utils.container_field(container, field.name)
for field in tmt.container.container_fields(container):
_, _, _, _, metadata = tmt.container.container_field(container, field.name)

if _is_ignored(container, field, metadata):
continue
Expand Down Expand Up @@ -192,8 +194,8 @@ def main() -> None:
PLUGINS=plugin_generator,
REVIEWED_PLUGINS=REVIEWED_PLUGINS,
is_enum=is_enum,
container_fields=tmt.utils.container_fields,
container_field=tmt.utils.container_field,
container_fields=tmt.container.container_fields,
container_field=tmt.container.container_field,
container_ignored_fields=container_ignored_fields,
container_inherited_fields=container_inherited_fields,
container_intrinsic_fields=container_intrinsic_fields))
Expand Down
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,8 @@ builtins-ignorelist = ["help", "format", "input", "filter", "copyright", "max"]
"os.path".msg = "Use tmt._compat.pathlib.Path and pathlib instead."
# Banning builtins is not yet supported: https://github.com/astral-sh/ruff/issues/10079
# "builtins.open".msg = "Use Path.{write_text,append_text,read_text,write_bytes,read_bytes} instead."
"dataclasses.dataclass".msg = "Are you sure you don't want tmt.container.container instead."
"dataclasses.field".msg = "Are you sure you don't want tmt.container.field instead."

[tool.ruff.lint.isort]
known-first-party = ["tmt"]
Expand Down
2 changes: 1 addition & 1 deletion tests/unit/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ def test_systemd():
assert 'Tier two functional tests' in result.output


@dataclasses.dataclass
@dataclasses.dataclass # noqa: TID251
class DecideColorizationTestcase:
""" A single test case for :py:func:`tmt.log.decide_colorization` """

Expand Down
7 changes: 3 additions & 4 deletions tests/unit/test_dataclasses.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import dataclasses
from typing import Any, Optional

import pytest

import tmt.log
import tmt.utils
from tmt.container import container, field
from tmt.utils import (
SerializableContainer,
dataclass_normalize_field,
field,
)


Expand All @@ -28,7 +27,7 @@ def _normalize_foo(key_address: str, raw_value: Any, logger: tmt.log.Logger) ->
raise tmt.utils.NormalizationError(key_address, raw_value, 'unset or an integer') \
from exc

@dataclasses.dataclass
@container
class DummyContainer(SerializableContainer):
foo: Optional[int] = field(
default=1,
Expand Down Expand Up @@ -57,7 +56,7 @@ class DummyContainer(SerializableContainer):


def test_field_custom_serialize():
@dataclasses.dataclass
@container
class DummyContainer(SerializableContainer):
foo: list[str] = field(
default_factory=list,
Expand Down
35 changes: 19 additions & 16 deletions tmt/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,14 @@
import tmt.utils.git
import tmt.utils.jira
from tmt.checks import Check
from tmt.container import (
SerializableContainer,
SpecBasedContainer,
container,
container_field,
container_fields,
field,
)
from tmt.lint import LinterOutcome, LinterReturn
from tmt.result import Result, ResultInterpret
from tmt.utils import (
Expand All @@ -64,14 +72,9 @@
EnvVarValue,
FmfContext,
Path,
SerializableContainer,
ShellScript,
SpecBasedContainer,
WorkdirArgumentType,
container_field,
container_fields,
dict_to_yaml,
field,
normalize_shell_script,
verdict,
)
Expand Down Expand Up @@ -137,7 +140,7 @@ class _RawFmfId(TypedDict, total=False):


# An internal fmf id representation.
@dataclasses.dataclass
@container
class FmfId(
SpecBasedContainer[_RawFmfId, _RawFmfId],
SerializableContainer,
Expand Down Expand Up @@ -411,7 +414,7 @@ class _RawDependencyFmfId(_RawFmfId):
type: Optional[str]


@dataclasses.dataclass
@container
class DependencyFmfId(
FmfId,
# Repeat the SpecBasedContainer, with more fitting in/out spec type.
Expand Down Expand Up @@ -500,7 +503,7 @@ class _RawDependencyFile(TypedDict):
pattern: Optional[list[str]]


@dataclasses.dataclass
@container
class DependencyFile(
SpecBasedContainer[_RawDependencyFile, _RawDependencyFile],
SerializableContainer,
Expand Down Expand Up @@ -649,7 +652,7 @@ def _normalize_link(key_address: str, value: _RawLinks, logger: tmt.log.Logger)
return Links(data=value)


@dataclasses.dataclass(repr=False)
@container(repr=False)
class Core(
tmt.utils.ValidateFmfMixin,
tmt.utils.LoadFmfKeysMixin,
Expand Down Expand Up @@ -1037,7 +1040,7 @@ def has_link(self, needle: 'LinkNeedle') -> bool:
Node = Core


@dataclasses.dataclass(repr=False)
@container(repr=False)
class Test(
Core,
tmt.export.Exportable['Test'],
Expand Down Expand Up @@ -1564,7 +1567,7 @@ def lint_require_type_field(self) -> LinterReturn:
yield LinterOutcome.FIXED, 'added type to requirements'


@dataclasses.dataclass(repr=False)
@dataclasses.dataclass(repr=False) # noqa: TID251
class LintableCollection(tmt.lint.Lintable['LintableCollection']):
""" Linting rules applied to a collection of Tests, Plans or Stories """

Expand Down Expand Up @@ -1658,7 +1661,7 @@ def expand_node_data(data: T, fmf_context: FmfContext) -> T:
return data


@dataclasses.dataclass(repr=False)
@container(repr=False)
class Plan(
Core,
tmt.export.Exportable['Plan'],
Expand Down Expand Up @@ -2618,7 +2621,7 @@ def __str__(self) -> str:
return self.value


@dataclasses.dataclass(repr=False)
@container(repr=False)
class Story(
Core,
tmt.export.Exportable['Story'],
Expand Down Expand Up @@ -3358,7 +3361,7 @@ def init(
logger=logger)


@dataclasses.dataclass
@container
class RunData(SerializableContainer):
root: Optional[str]
plans: Optional[list[str]]
Expand Down Expand Up @@ -4056,7 +4059,7 @@ def runs(self, id_: tuple[str, ...]) -> bool:
return successful


@dataclasses.dataclass
@dataclasses.dataclass # noqa: TID251
class LinkNeedle:
"""
A container to use for searching links.
Expand Down Expand Up @@ -4111,7 +4114,7 @@ def matches(self, link: 'Link') -> bool:
return False


@dataclasses.dataclass
@container
class Link(SpecBasedContainer[Any, _RawLinkRelation]):
"""
An internal "link" as defined by tmt specification.
Expand Down
13 changes: 7 additions & 6 deletions tmt/checks/__init__.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import dataclasses
import enum
import functools
from typing import TYPE_CHECKING, Any, Callable, Generic, Optional, TypedDict, TypeVar, cast

import tmt.log
import tmt.steps.provision
import tmt.utils
from tmt.plugins import PluginRegistry
from tmt.utils import (
NormalizeKeysMixin,
from tmt.container import (
SerializableContainer,
SpecBasedContainer,
container,
field,
key_to_option,
)
from tmt.plugins import PluginRegistry
from tmt.utils import NormalizeKeysMixin

if TYPE_CHECKING:
import tmt.base
Expand Down Expand Up @@ -100,7 +101,7 @@ def to_spec(self) -> str:
return self.value


@dataclasses.dataclass
@container
class Check(
SpecBasedContainer[_RawCheck, _RawCheck],
SerializableContainer,
Expand Down Expand Up @@ -143,7 +144,7 @@ def from_spec( # type: ignore[override]

def to_spec(self) -> _RawCheck:
spec = cast(_RawCheck, {
tmt.utils.key_to_option(key): value
key_to_option(key): value
for key, value in self.items()
})
spec["result"] = self.result.to_spec()
Expand Down
6 changes: 3 additions & 3 deletions tmt/checks/dmesg.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import dataclasses
import datetime
import re
from re import Pattern
Expand All @@ -9,9 +8,10 @@
import tmt.steps.provision
import tmt.utils
from tmt.checks import Check, CheckEvent, CheckPlugin, _RawCheck, provides_check
from tmt.container import container, field
from tmt.result import CheckResult, ResultOutcome
from tmt.steps.provision import GuestCapability
from tmt.utils import Path, field, format_timestamp, render_run_exception_streams
from tmt.utils import Path, format_timestamp, render_run_exception_streams

if TYPE_CHECKING:
import tmt.base
Expand All @@ -29,7 +29,7 @@
]


@dataclasses.dataclass
@container
class DmesgCheck(Check):
failure_pattern: list[Pattern[str]] = field(
default_factory=lambda: DEFAULT_FAILURE_PATTERNS[:],
Expand Down
7 changes: 4 additions & 3 deletions tmt/checks/watchdog.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@
import tmt.steps.provision.testcloud
import tmt.utils
from tmt.checks import Check, CheckPlugin, provides_check
from tmt.container import container, field
from tmt.result import CheckResult, ResultOutcome
from tmt.utils import Path, field, format_timestamp, render_run_exception_streams
from tmt.utils import Path, format_timestamp, render_run_exception_streams

if TYPE_CHECKING:
from tmt.steps.execute import TestInvocation
Expand Down Expand Up @@ -84,7 +85,7 @@ def report_progress(
f.write('\n')


@dataclasses.dataclass
@dataclasses.dataclass # noqa: TID251
class GuestContext:
""" Per-guest watchdog context """

Expand All @@ -101,7 +102,7 @@ class GuestContext:
keep_running: bool = True


@dataclasses.dataclass
@container
class WatchdogCheck(Check):
interval: int = field(
default=60,
Expand Down
8 changes: 4 additions & 4 deletions tmt/cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ class TmtExitCode(enum.IntEnum):
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


@dataclasses.dataclass
@dataclasses.dataclass # noqa: TID251
class ContextObject:
"""
Click Context Object container.
Expand All @@ -94,10 +94,10 @@ class ContextObject:
common: tmt.utils.Common
fmf_context: tmt.utils.FmfContext
tree: tmt.Tree
steps: set[str] = dataclasses.field(default_factory=set)
steps: set[str] = dataclasses.field(default_factory=set) # noqa: TID251
clean: Optional[tmt.Clean] = None
clean_logger: Optional[tmt.log.Logger] = None
clean_partials: collections.defaultdict[str, list[tmt.base.CleanCallback]] = dataclasses.field(
clean_partials: collections.defaultdict[str, list[tmt.base.CleanCallback]] = dataclasses.field( # noqa: TID251
default_factory=lambda: collections.defaultdict(list))
run: Optional[tmt.Run] = None

Expand Down Expand Up @@ -139,7 +139,7 @@ def pass_context(fn: 'Callable[Concatenate[Context, P], R]') -> 'Callable[P, R]'
return click.pass_context(fn) # type: ignore[arg-type]


@dataclasses.dataclass
@dataclasses.dataclass # noqa: TID251
class CliInvocation:
"""
A single CLI invocation of a tmt subcommand.
Expand Down
Loading
Loading