diff --git a/.gitignore b/.gitignore index 1d9d28a794..324729501c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # tmt-specific /tmp/ +docs/autodocs/*.rst docs/_build docs/spec docs/stories diff --git a/docs/Makefile b/docs/Makefile index 504bb5f40c..681faec784 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -19,7 +19,7 @@ ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . -.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext autodocs help: @echo "Please use \`make ' where is one of" @@ -27,10 +27,15 @@ help: @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " text to make text files" @echo " man to make manual pages" + @echo " autodocs to refresh autodocs generated files" clean: rm -rf $(BUILDDIR)/* +autodocs: + rm -f autodocs/*.rst + pushd ../ && sphinx-apidoc --force --implicit-namespaces --no-toc -o docs/autodocs tmt && popd + html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo diff --git a/docs/autodocs/.gitkeep b/docs/autodocs/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/classes.rst b/docs/classes.rst index 86bc6f2c63..11665a3777 100644 --- a/docs/classes.rst +++ b/docs/classes.rst @@ -424,5 +424,6 @@ Essential Classes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. automodule:: tmt + :noindex: :members: :undoc-members: diff --git a/docs/conf.py b/docs/conf.py index 949422c863..83167c0e46 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -50,6 +50,7 @@ # ones. extensions = [ 'sphinx.ext.autodoc', + 'sphinx.ext.autodoc.typehints', 'sphinx_rtd_theme', ] @@ -126,6 +127,14 @@ # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = False +# Autodocs & type hints +autodoc_default_flags = ['members', 'undoc-members', 'show-inheritance', 'private-members'] +autoclass_content = "both" + +autodoc_typehints_format = 'short' +autodoc_typehints_description_target = 'all' +# This one works, but it's a bit uglier than the default value (`signature`). +# autodoc_typehints = 'description' # -- Options for HTML output ---------------------------------------------- diff --git a/docs/contribute.rst b/docs/contribute.rst index 3286169dee..3379692167 100644 --- a/docs/contribute.rst +++ b/docs/contribute.rst @@ -384,3 +384,13 @@ __ https://bodhi.fedoraproject.org/releases/ __ https://github.com/teemtee/tmt/releases/ __ https://pypi.org/project/tmt/ __ https://copr.fedorainfracloud.org/coprs/psss/tmt/builds/ + + +Code documentation +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. toctree:: + :maxdepth: 1 + + Overview of important and interesting classes + Documentation generated from sources diff --git a/docs/index.rst b/docs/index.rst index 556f90b8b2..78cd9ac85d 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -33,5 +33,4 @@ Table of Contents Stories Questions Contribute - Classes Plugins diff --git a/tmt/log.py b/tmt/log.py index 7066aba4b1..4599678025 100644 --- a/tmt/log.py +++ b/tmt/log.py @@ -414,7 +414,7 @@ def __init__( """ Create a ``Logger`` instance with given verbosity levels. - :param actual_logger: a :py:class:`logging.Logger` instance, the _raw logger_ + :param actual_logger: a :py:class:`logging.Logger` instance, the raw logger to use for logging. :param base_shift: shift applied to all messages processed by this logger. :param labels_padding: if set, rendered labels would be padded to this diff --git a/tmt/queue.py b/tmt/queue.py index 971e9dcd64..9cb0c8b81a 100644 --- a/tmt/queue.py +++ b/tmt/queue.py @@ -10,7 +10,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from tmt.steps.provision import Guest + import tmt.steps.provision TaskT = TypeVar('TaskT', bound='_Task') @@ -25,7 +25,7 @@ class TaskOutcome(Generic[TaskT]): executed, where and what was the result. """ - #: A :py:`_Task` instace the outcome relates to. + #: A :py:class:`_Task` instace the outcome relates to. task: TaskT #: A logger to use for logging events related to the outcome. @@ -33,7 +33,7 @@ class TaskOutcome(Generic[TaskT]): #: Guest on which the phase was executed. May be unset, some tasks #: may handle multiguest actions on their own. - guest: Optional['Guest'] + guest: Optional['tmt.steps.provision.Guest'] #: If set, an exception was raised by the running task, and the exception #: is saved in this field. @@ -45,7 +45,7 @@ class _Task: """ A base class for tasks to be executed on one or more guests """ #: A list of guests to execute the task on. - guests: List['Guest'] + guests: List['tmt.steps.provision.Guest'] #: A logger to use for logging events related to the task. It serves as #: a root logger for new loggers queue may spawn for each guest. @@ -115,7 +115,7 @@ def go(self) -> Generator[TaskOutcome['Self'], None, None]: class Task(_Task): """ A task that should run on multiple guests at the same time """ - def run_on_guest(self, guest: 'Guest', logger: Logger) -> None: + def run_on_guest(self, guest: 'tmt.steps.provision.Guest', logger: Logger) -> None: raise NotImplementedError def prepare_loggers( @@ -157,7 +157,7 @@ def go(self) -> Generator[TaskOutcome['Self'], None, None]: old_loggers: Dict[str, Logger] = {} with ThreadPoolExecutor(max_workers=len(self.guests)) as executor: - futures: Dict[Future[None], Guest] = {} + futures: Dict[Future[None], 'tmt.steps.provision.Guest'] = {} for guest in self.guests: # Swap guest's logger for the one we prepared, with labels diff --git a/tmt/steps/__init__.py b/tmt/steps/__init__.py index 82ffcdf894..5820d39255 100644 --- a/tmt/steps/__init__.py +++ b/tmt/steps/__init__.py @@ -31,8 +31,7 @@ import tmt.cli import tmt.steps.discover import tmt.steps.execute - from tmt.base import Plan - from tmt.steps.provision import Guest + import tmt.steps.provision DEFAULT_PLUGIN_METHOD_ORDER: int = 50 @@ -59,7 +58,7 @@ def __init__( super().__init__(**kwargs) self.order: int = order - def enabled_on_guest(self, guest: 'Guest') -> bool: + def enabled_on_guest(self, guest: 'tmt.steps.provision.Guest') -> bool: """ Phases are enabled across all guests by default """ return True @@ -215,7 +214,7 @@ class Step(tmt.utils.Common): def __init__( self, *, - plan: 'Plan', + plan: 'tmt.base.Plan', data: Optional[RawStepDataArgument] = None, name: Optional[str] = None, workdir: tmt.utils.WorkdirArgumentType = None, @@ -226,7 +225,7 @@ def __init__( super().__init__(name=name, parent=plan, workdir=workdir, logger=logger) # Initialize data - self.plan: 'Plan' = plan + self.plan: 'tmt.base.Plan' = plan self._status: Optional[str] = None self._phases: List[Phase] = [] @@ -925,7 +924,7 @@ def _emit_key(key: str) -> None: for key in keys: _emit_key(key) - def enabled_on_guest(self, guest: 'Guest') -> bool: + def enabled_on_guest(self, guest: 'tmt.steps.provision.Guest') -> bool: """ Check if the plugin is enabled on the specific guest """ # FIXME: cast() - typeless "dispatcher" method @@ -1054,7 +1053,7 @@ class Plugin(BasePlugin): def go( self, *, - guest: 'Guest', + guest: 'tmt.steps.provision.Guest', environment: Optional[tmt.utils.EnvironmentType] = None, logger: tmt.log.Logger) -> None: """ Perform actions shared among plugins when beginning their tasks """ @@ -1380,7 +1379,7 @@ def prepare_environment(self) -> EnvironmentType: def prepare_guest_environment( self, - guest: 'Guest') -> EnvironmentType: + guest: 'tmt.steps.provision.Guest') -> EnvironmentType: """ Prepare environment variables related to phase and a particular guest. """ @@ -1397,7 +1396,7 @@ def run(self, logger: tmt.log.Logger) -> None: self.phase.go() - def run_on_guest(self, guest: 'Guest', logger: tmt.log.Logger) -> None: + def run_on_guest(self, guest: 'tmt.steps.provision.Guest', logger: tmt.log.Logger) -> None: assert isinstance(self.phase, Plugin) # narrow type self.phase.go( @@ -1421,7 +1420,7 @@ def enqueue( self, *, phase: Union[Action, Plugin], - guests: List['Guest']) -> None: + guests: List['tmt.steps.provision.Guest']) -> None: """ Add a phase to queue. @@ -1450,7 +1449,7 @@ class PushTask(Task): def name(self) -> str: return 'push' - def run_on_guest(self, guest: 'Guest', logger: tmt.log.Logger) -> None: + def run_on_guest(self, guest: 'tmt.steps.provision.Guest', logger: tmt.log.Logger) -> None: guest.push() @@ -1464,7 +1463,7 @@ class PullTask(Task): def name(self) -> str: return 'pull' - def run_on_guest(self, guest: 'Guest', logger: tmt.log.Logger) -> None: + def run_on_guest(self, guest: 'tmt.steps.provision.Guest', logger: tmt.log.Logger) -> None: guest.pull(source=self.source) diff --git a/tmt/steps/discover/fmf.py b/tmt/steps/discover/fmf.py index b49366ce93..cd810e15a4 100644 --- a/tmt/steps/discover/fmf.py +++ b/tmt/steps/discover/fmf.py @@ -122,13 +122,13 @@ class DiscoverFmf(tmt.steps.discover.DiscoverPlugin): Related config options (all optional): * dist-git-merge - set to True if you want to copy in extracted - sources to the local repo + sources to the local repo * dist-git-init - set to True and 'fmf init' will be called inside - extracted sources (at dist-git-extract or top directory) + extracted sources (at dist-git-extract or top directory) * dist-git-extract - directory (glob supported) to copy from - extracted sources (defaults to inner fmf-root) + extracted sources (defaults to inner fmf-root) * dist-git-remove-fmf-root - set to True to remove fmf root from - extracted sources + extracted sources Selecting tests containing specified link is possible using 'link' option accepting RELATION:TARGET format of values. Regular diff --git a/tmt/steps/discover/shell.py b/tmt/steps/discover/shell.py index 6cc9b85e05..802e240506 100644 --- a/tmt/steps/discover/shell.py +++ b/tmt/steps/discover/shell.py @@ -191,39 +191,45 @@ class DiscoverShell(tmt.steps.discover.DiscoverPlugin): in the plan as a list of dictionaries containing test name, actual test script and optionally a path to the test. Example config: - discover: - how: shell - tests: - - name: /help/main - test: tmt --help - - name: /help/test - test: tmt test --help - - name: /help/smoke - test: ./smoke.sh - path: /tests/shell + .. code-block:: yaml + + discover: + how: shell + tests: + - name: /help/main + test: tmt --help + - name: /help/test + test: tmt test --help + - name: /help/smoke + test: ./smoke.sh + path: /tests/shell For DistGit repo one can extract source tarball and use its code. It is extracted to TMT_SOURCE_DIR however no patches are applied (only source tarball is extracted). - discover: - how: shell - dist-git-source: true - tests: - - name: /upstream - test: cd $TMT_SOURCE_DIR/*/tests && make test + .. code-block:: yaml + + discover: + how: shell + dist-git-source: true + tests: + - name: /upstream + test: cd $TMT_SOURCE_DIR/*/tests && make test To clone a remote repository and use it as a source specify `url`. It accepts also `ref` to checkout provided reference. Dynamic reference feature is supported as well. - discover: - how: shell - url: https://github.com/teemtee/tmt.git - ref: "1.18.0" - tests: - - name: first test - test: ./script-from-the-repo.sh + .. code-block:: yaml + + discover: + how: shell + url: https://github.com/teemtee/tmt.git + ref: "1.18.0" + tests: + - name: first test + test: ./script-from-the-repo.sh """ _data_class = DiscoverShellData diff --git a/tmt/steps/finish/ansible.py b/tmt/steps/finish/ansible.py index c30cb238b7..dfc0809895 100644 --- a/tmt/steps/finish/ansible.py +++ b/tmt/steps/finish/ansible.py @@ -11,12 +11,16 @@ class FinishAnsible(tmt.steps.finish.FinishPlugin, PrepareAnsible): Single playbook config: + .. code-block:: yaml + finish: how: ansible playbook: ansible/packages.yml Multiple playbooks config: + .. code-block:: yaml + finish: how: ansible playbook: diff --git a/tmt/steps/finish/shell.py b/tmt/steps/finish/shell.py index f69154a127..0011e6d9ae 100644 --- a/tmt/steps/finish/shell.py +++ b/tmt/steps/finish/shell.py @@ -55,11 +55,13 @@ class FinishShell(tmt.steps.finish.FinishPlugin): Example config: - finish: - how: shell - script: - - upload-logs.sh || true - - rm -rf /tmp/temporary-files + .. code-block:: yaml + + finish: + how: shell + script: + - upload-logs.sh || true + - rm -rf /tmp/temporary-files Use the 'order' attribute to select in which order finishing tasks should happen if there are multiple configs. Default order is '50'. diff --git a/tmt/steps/prepare/__init__.py b/tmt/steps/prepare/__init__.py index b7829b625d..ce48abfac9 100644 --- a/tmt/steps/prepare/__init__.py +++ b/tmt/steps/prepare/__init__.py @@ -22,7 +22,6 @@ if TYPE_CHECKING: import tmt.base import tmt.cli - from tmt.base import Plan @dataclasses.dataclass @@ -105,7 +104,7 @@ class Prepare(tmt.steps.Step): def __init__( self, *, - plan: 'Plan', + plan: 'tmt.base.Plan', data: tmt.steps.RawStepDataArgument, logger: tmt.log.Logger) -> None: """ Initialize prepare step data """ diff --git a/tmt/steps/prepare/ansible.py b/tmt/steps/prepare/ansible.py index ceda96a281..aeed134c76 100644 --- a/tmt/steps/prepare/ansible.py +++ b/tmt/steps/prepare/ansible.py @@ -63,12 +63,16 @@ class PrepareAnsible(tmt.steps.prepare.PreparePlugin): Single playbook config: + .. code-block:: yaml + prepare: how: ansible playbook: ansible/packages.yml Multiple playbooks config: + .. code-block:: yaml + prepare: how: ansible playbook: @@ -80,6 +84,8 @@ class PrepareAnsible(tmt.steps.prepare.PreparePlugin): Remote playbooks can be referenced as well as local ones, and both kinds can be intermixed: + .. code-block:: yaml + prepare: how: ansible playbook: diff --git a/tmt/steps/prepare/install.py b/tmt/steps/prepare/install.py index e07e853b8d..6284778df0 100644 --- a/tmt/steps/prepare/install.py +++ b/tmt/steps/prepare/install.py @@ -529,6 +529,8 @@ class PrepareInstall(tmt.steps.prepare.PreparePlugin): Example config: + .. code-block:: yaml + prepare: how: install copr: psss/tmt @@ -542,6 +544,8 @@ class PrepareInstall(tmt.steps.prepare.PreparePlugin): In addition to package name you can also use one or more paths to local rpm files to be installed: + .. code-block:: yaml + prepare: how: install package: @@ -551,6 +555,8 @@ class PrepareInstall(tmt.steps.prepare.PreparePlugin): Use 'directory' to install all packages from given folder and 'exclude' to skip selected packages: + .. code-block:: yaml + prepare: how: install directory: tmp/RPMS/noarch @@ -565,8 +571,8 @@ class PrepareInstall(tmt.steps.prepare.PreparePlugin): requested packages. The current limitations of the rpm-ostree implementation are: - Cannot install new version of already installed local rpm. - No support for installing debuginfo packages at this time. + * Cannot install new version of already installed local rpm. + * No support for installing debuginfo packages at this time. """ _data_class = PrepareInstallData diff --git a/tmt/steps/provision/artemis.py b/tmt/steps/provision/artemis.py index 8055c4a96b..71bc9fde80 100644 --- a/tmt/steps/provision/artemis.py +++ b/tmt/steps/provision/artemis.py @@ -448,6 +448,8 @@ class ProvisionArtemis(tmt.steps.provision.ProvisionPlugin): Full configuration example: + .. code-block:: yaml + provision: how: artemis diff --git a/tmt/utils.py b/tmt/utils.py index 40b5d00a40..75609e5f7b 100644 --- a/tmt/utils.py +++ b/tmt/utils.py @@ -2791,6 +2791,14 @@ def send( # type: ignore[override] self, request: requests.PreparedRequest, **kwargs: Any) -> requests.Response: + """ + Send request. + + All arguments are passed to superclass after enforcing the timeout. + + :param request: the request to send. + """ + kwargs.setdefault('timeout', self.timeout) return super().send(request, **kwargs) @@ -3696,7 +3704,7 @@ def __init__( """ Updatable message suitable for progress-bar-like reporting. - .. code:block:: python3 + .. code-block:: python3 with updatable_message('foo') as message: while ...: @@ -4074,7 +4082,7 @@ def wait( * decide the condition has been fulfilled. This is a successfull outcome, ``check`` shall then simply return, and waiting ends. Or, * decide more time is needed. This is not a successfull outcome, ``check`` - shall then raise :py:clas:`WaitingIncomplete` exception, and ``wait()`` + shall then raise :py:class:`WaitingIncomplete` exception, and ``wait()`` will try again later. :param parent: "owner" of the wait process. Used for its logging capability.