Skip to content

Commit

Permalink
Add docstrings and type hints to dependency plugins (#4331)
Browse files Browse the repository at this point in the history
  • Loading branch information
Qalthos authored Dec 4, 2024
1 parent b6c542e commit 716c975
Show file tree
Hide file tree
Showing 13 changed files with 296 additions and 165 deletions.
44 changes: 0 additions & 44 deletions .config/pydoclint-baseline.txt
Original file line number Diff line number Diff line change
@@ -1,47 +1,3 @@
src/molecule/dependency/ansible_galaxy/__init__.py
DOC101: Method `AnsibleGalaxy.__init__`: Docstring contains fewer arguments than in function signature.
DOC106: Method `AnsibleGalaxy.__init__`: The option `--arg-type-hints-in-signature` is `True` but there are no argument type hints in the signature
DOC107: Method `AnsibleGalaxy.__init__`: The option `--arg-type-hints-in-signature` is `True` but not all args in the signature have type hints
DOC103: Method `AnsibleGalaxy.__init__`: Docstring arguments are different from function arguments. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Arguments in the function signature but not in the docstring: [config: ].
--------------------
src/molecule/dependency/ansible_galaxy/base.py
DOC601: Class `AnsibleGalaxyBase`: Class docstring contains fewer class attributes than actual class attributes. (Please read https://jsh9.github.io/pydoclint/checking_class_attributes.html on how to correctly document class attributes.)
DOC603: Class `AnsibleGalaxyBase`: Class docstring attributes are different from actual class attributes. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Attributes in the class definition but not in the docstring: [__metaclass__: ]. (Please read https://jsh9.github.io/pydoclint/checking_class_attributes.html on how to correctly document class attributes.)
DOC101: Method `AnsibleGalaxyBase.__init__`: Docstring contains fewer arguments than in function signature.
DOC106: Method `AnsibleGalaxyBase.__init__`: The option `--arg-type-hints-in-signature` is `True` but there are no argument type hints in the signature
DOC107: Method `AnsibleGalaxyBase.__init__`: The option `--arg-type-hints-in-signature` is `True` but not all args in the signature have type hints
DOC103: Method `AnsibleGalaxyBase.__init__`: Docstring arguments are different from function arguments. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Arguments in the function signature but not in the docstring: [config: ].
DOC101: Method `AnsibleGalaxyBase.filter_options`: Docstring contains fewer arguments than in function signature.
DOC106: Method `AnsibleGalaxyBase.filter_options`: The option `--arg-type-hints-in-signature` is `True` but there are no argument type hints in the signature
DOC107: Method `AnsibleGalaxyBase.filter_options`: The option `--arg-type-hints-in-signature` is `True` but not all args in the signature have type hints
DOC103: Method `AnsibleGalaxyBase.filter_options`: Docstring arguments are different from function arguments. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Arguments in the function signature but not in the docstring: [keys: , opts: ].
--------------------
src/molecule/dependency/ansible_galaxy/collections.py
DOC601: Class `Collections`: Class docstring contains fewer class attributes than actual class attributes. (Please read https://jsh9.github.io/pydoclint/checking_class_attributes.html on how to correctly document class attributes.)
DOC603: Class `Collections`: Class docstring attributes are different from actual class attributes. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Attributes in the class definition but not in the docstring: [COMMANDS: , FILTER_OPTS: ]. (Please read https://jsh9.github.io/pydoclint/checking_class_attributes.html on how to correctly document class attributes.)
--------------------
src/molecule/dependency/ansible_galaxy/roles.py
DOC601: Class `Roles`: Class docstring contains fewer class attributes than actual class attributes. (Please read https://jsh9.github.io/pydoclint/checking_class_attributes.html on how to correctly document class attributes.)
DOC603: Class `Roles`: Class docstring attributes are different from actual class attributes. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Attributes in the class definition but not in the docstring: [COMMANDS: , FILTER_OPTS: ]. (Please read https://jsh9.github.io/pydoclint/checking_class_attributes.html on how to correctly document class attributes.)
--------------------
src/molecule/dependency/base.py
DOC601: Class `Base`: Class docstring contains fewer class attributes than actual class attributes. (Please read https://jsh9.github.io/pydoclint/checking_class_attributes.html on how to correctly document class attributes.)
DOC603: Class `Base`: Class docstring attributes are different from actual class attributes. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Attributes in the class definition but not in the docstring: [__metaclass__: ]. (Please read https://jsh9.github.io/pydoclint/checking_class_attributes.html on how to correctly document class attributes.)
DOC106: Method `Base.__init__`: The option `--arg-type-hints-in-signature` is `True` but there are no argument type hints in the signature
DOC107: Method `Base.__init__`: The option `--arg-type-hints-in-signature` is `True` but not all args in the signature have type hints
DOC201: Method `Base.execute_with_retries` does not have a return section in docstring
DOC101: Method `Base.execute`: Docstring contains fewer arguments than in function signature.
DOC106: Method `Base.execute`: The option `--arg-type-hints-in-signature` is `True` but there are no argument type hints in the signature
DOC107: Method `Base.execute`: The option `--arg-type-hints-in-signature` is `True` but not all args in the signature have type hints
DOC103: Method `Base.execute`: Docstring arguments are different from function arguments. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Arguments in the function signature but not in the docstring: [action_args: ].
DOC202: Method `Base.default_options` has a return section in docstring, but there are no return statements or annotations
--------------------
src/molecule/dependency/shell.py
DOC101: Method `Shell.__init__`: Docstring contains fewer arguments than in function signature.
DOC106: Method `Shell.__init__`: The option `--arg-type-hints-in-signature` is `True` but there are no argument type hints in the signature
DOC107: Method `Shell.__init__`: The option `--arg-type-hints-in-signature` is `True` but not all args in the signature have type hints
DOC103: Method `Shell.__init__`: Docstring arguments are different from function arguments. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Arguments in the function signature but not in the docstring: [config: ].
--------------------
tests/conftest.py
DOC101: Function `reset_pytest_vars`: Docstring contains fewer arguments than in function signature.
DOC106: Function `reset_pytest_vars`: The option `--arg-type-hints-in-signature` is `True` but there are no argument type hints in the signature
Expand Down
2 changes: 1 addition & 1 deletion src/molecule/command/dependency.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def execute(self, action_args: list[str] | None = None) -> None: # noqa: ARG002
action_args: Arguments for this command. Unused.
"""
if self._config.dependency:
self._config.dependency.execute() # type: ignore[no-untyped-call]
self._config.dependency.execute()


@base.click_command_ex()
Expand Down
2 changes: 1 addition & 1 deletion src/molecule/dependency/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
# D104 # noqa: D104, ERA001
# noqa: D104
53 changes: 40 additions & 13 deletions src/molecule/dependency/ansible_galaxy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,20 @@

from __future__ import annotations

from typing import TYPE_CHECKING

from molecule import util
from molecule.dependency.ansible_galaxy.collections import Collections
from molecule.dependency.ansible_galaxy.roles import Roles
from molecule.dependency.base import Base


if TYPE_CHECKING:
from collections.abc import MutableMapping

from molecule.config import Config


class AnsibleGalaxy(Base):
"""Galaxy is the default dependency manager.
Expand Down Expand Up @@ -88,31 +96,50 @@ class AnsibleGalaxy(Base):
[ANSIBLE_HOME]: https://docs.ansible.com/ansible/latest/reference_appendices/config.html#ansible-home
"""

def __init__(self, config) -> None: # type: ignore[no-untyped-def] # noqa: ANN001
"""Construct AnsibleGalaxy."""
def __init__(self, config: Config) -> None:
"""Construct AnsibleGalaxy.
Args:
config: Molecule Config instance.
"""
super().__init__(config)
self.invocations = [Roles(config), Collections(config)]

def execute(self, action_args=None): # type: ignore[no-untyped-def] # noqa: ANN001, ANN201, ARG002, D102
def execute(self, action_args: list[str] | None = None) -> None: # noqa: ARG002
"""Execute all Ansible Galaxy dependencies.
Args:
action_args: Arguments for invokers. Unused.
"""
for invoker in self.invocations:
invoker.execute() # type: ignore[no-untyped-call]
invoker.execute()

def _has_requirements_file(self): # type: ignore[no-untyped-def] # noqa: ANN202
def _has_requirements_file(self) -> bool:
has_file = False
for invoker in self.invocations:
has_file = has_file or invoker._has_requirements_file() # type: ignore[no-untyped-call] # noqa: SLF001
has_file = has_file or invoker._has_requirements_file() # noqa: SLF001
return has_file

@property
def default_env(self): # type: ignore[no-untyped-def] # noqa: ANN201, D102
e = {} # type: ignore[var-annotated]
def default_env(self) -> dict[str, str]:
"""Default environment variables across all invokers.
Returns:
Merged dictionary of default env vars for all invokers.
"""
env: dict[str, str] = {}
for invoker in self.invocations:
e = util.merge(e, invoker.default_env) # type: ignore[attr-defined] # pylint: disable=no-member
return e
env = util.merge_dicts(env, invoker.default_env)
return env

@property
def default_options(self): # type: ignore[no-untyped-def] # noqa: ANN201, D102
opts = {} # type: ignore[var-annotated]
def default_options(self) -> MutableMapping[str, str | bool]:
"""Default options across all invokers.
Returns:
Merged dictionary of default options for all invokers.
"""
opts: MutableMapping[str, str | bool] = {}
for invoker in self.invocations:
opts = util.merge(opts, invoker.default_opts) # type: ignore[attr-defined] # pylint: disable=no-member
opts = util.merge_dicts(opts, invoker.default_options)
return opts
104 changes: 75 additions & 29 deletions src/molecule/dependency/ansible_galaxy/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,19 @@
import logging
import os

from pathlib import Path
from typing import TYPE_CHECKING

from molecule import util
from molecule.dependency import base


if TYPE_CHECKING:
from collections.abc import MutableMapping

from molecule.config import Config


LOG = logging.getLogger(__name__)


Expand All @@ -37,41 +46,62 @@ class AnsibleGalaxyBase(base.Base):
Attributes:
FILTER_OPTS: Keys to remove from the dictionary returned by options().
COMMANDS: Arguments to send to ansible-galaxy to install the appropriate type of content.
"""

__metaclass__ = abc.ABCMeta
FILTER_OPTS: tuple[str, ...] = ()
COMMANDS: tuple[str, ...] = ()

FILTER_OPTS = ()
def __init__(self, config: Config) -> None:
"""Construct AnsibleGalaxy.
def __init__(self, config) -> None: # type: ignore[no-untyped-def] # noqa: ANN001
"""Construct AnsibleGalaxy."""
Args:
config: Molecule Config instance.
"""
super().__init__(config)
self._sh_command = None
self._sh_command = []

self.command = "ansible-galaxy"

@property
@abc.abstractmethod
def requirements_file(self): # type: ignore[no-untyped-def] # cover # noqa: ANN201, D102
pass
def requirements_file(self) -> str: # cover
"""Path to requirements file.
Returns:
Path to the requirements file for this dependency.
"""

@property
def default_options(self): # type: ignore[no-untyped-def] # noqa: ANN201, D102
d = {
def default_options(self) -> MutableMapping[str, str | bool]:
"""Default options for this dependency.
Returns:
Default options for this dependency.
"""
d: MutableMapping[str, str | bool] = {
"force": False,
}
if self._config.debug:
d["vvv"] = True

return d

def filter_options(self, opts, keys): # type: ignore[no-untyped-def] # noqa: ANN001, ANN201
def filter_options(
self,
opts: MutableMapping[str, str | bool],
keys: tuple[str, ...],
) -> MutableMapping[str, str | bool]:
"""Filter certain keys from a dictionary.
Removes all the values of ``keys`` from the dictionary ``opts``, if
they are present. Returns the resulting dictionary. Does not modify the
existing one.
Args:
opts: Options dictionary.
keys: Key names to exclude from opts.
Returns:
A copy of ``opts`` without the value of keys
"""
Expand All @@ -84,52 +114,68 @@ def filter_options(self, opts, keys): # type: ignore[no-untyped-def] # noqa: A
# NOTE(retr0h): Override the base classes' options() to handle
# ``ansible-galaxy`` one-off.
@property
def options(self): # type: ignore[no-untyped-def] # noqa: ANN201, D102
o = self._config.config["dependency"]["options"]
def options(self) -> MutableMapping[str, str | bool]:
"""Computed options for this dependency.
Returns:
Merged and filtered options for this dependency.
"""
opts = self._config.config["dependency"]["options"]
# NOTE(retr0h): Remove verbose options added by the user while in
# debug.
if self._config.debug:
o = util.filter_verbose_permutation(o)
opts = util.filter_verbose_permutation(opts)

o = util.merge_dicts(self.default_options, o)
return self.filter_options(o, self.FILTER_OPTS) # type: ignore[no-untyped-call]
opts = util.merge_dicts(self.default_options, opts)
return self.filter_options(opts, self.FILTER_OPTS)

@property
def default_env(self): # type: ignore[no-untyped-def] # noqa: ANN201, D102
return util.merge_dicts(os.environ, self._config.env)
def default_env(self) -> dict[str, str]:
"""Default environment variables for this dependency.
def bake(self): # type: ignore[no-untyped-def] # noqa: ANN201
Returns:
Default environment variables for this dependency.
"""
env = dict(os.environ)
return util.merge_dicts(env, self._config.env)

def bake(self) -> None:
"""Bake an ``ansible-galaxy`` command so it's ready to execute and returns None."""
options = self.options
verbose_flag = util.verbose_flag(options)

self._sh_command = [
self.command,
*self.COMMANDS, # type: ignore[attr-defined] # pylint: disable=no-member
*self.COMMANDS,
*util.dict2args(options),
*verbose_flag,
]

def execute(self, action_args=None): # type: ignore[no-untyped-def] # noqa: ANN001, ANN201, ARG002, D102
def execute(self, action_args: list[str] | None = None) -> None: # noqa: ARG002
"""Execute dependency.
Args:
action_args: Arguments for this dependency. Unused.
"""
if not self.enabled:
msg = "Skipping, dependency is disabled."
LOG.warning(msg)
return
super().execute() # type: ignore[no-untyped-call]
super().execute()

if not self._has_requirements_file(): # type: ignore[no-untyped-call]
if not self._has_requirements_file():
msg = "Skipping, missing the requirements file."
LOG.warning(msg)
return

if self._sh_command is None:
self.bake() # type: ignore[no-untyped-call]
if not self._sh_command:
self.bake()

self._setup() # type: ignore[no-untyped-call]
self.execute_with_retries() # type: ignore[no-untyped-call]
self._setup()
self.execute_with_retries()

def _setup(self): # type: ignore[no-untyped-def] # noqa: ANN202
def _setup(self) -> None:
"""Prepare the system for using ``ansible-galaxy`` and returns None."""

def _has_requirements_file(self): # type: ignore[no-untyped-def] # noqa: ANN202
return os.path.isfile(self.requirements_file) # noqa: PTH113
def _has_requirements_file(self) -> bool:
return Path(self.requirements_file).is_file()
43 changes: 31 additions & 12 deletions src/molecule/dependency/ansible_galaxy/collections.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,40 +3,59 @@
from __future__ import annotations

import logging
import os

from pathlib import Path
from typing import TYPE_CHECKING, cast

from molecule import util
from molecule.dependency.ansible_galaxy.base import AnsibleGalaxyBase


if TYPE_CHECKING:
from collections.abc import MutableMapping


LOG = logging.getLogger(__name__)


class Collections(AnsibleGalaxyBase):
"""Collection-specific Ansible Galaxy dependency handling."""
"""Collection-specific Ansible Galaxy dependency handling.
FILTER_OPTS = ("role-file",) # type: ignore # noqa: PGH003
Attributes:
FILTER_OPTS: Keys to remove from the dictionary returned by options().
COMMANDS: Arguments to send to ansible-galaxy to install the appropriate type of content.
"""

FILTER_OPTS = ("role-file",)
COMMANDS = ("collection", "install")

@property
def default_options(self): # type: ignore[no-untyped-def] # noqa: ANN201, D102
def default_options(self) -> MutableMapping[str, str | bool]:
"""Default options for this dependency.
Returns:
Default options for this dependency.
"""
general = super().default_options
specific = util.merge_dicts(
general,
{
"requirements-file": os.path.join( # noqa: PTH118
self._config.scenario.directory,
"collections.yml",
"requirements-file": str(
Path(
self._config.scenario.directory,
"collections.yml",
),
),
},
)

return specific # noqa: RET504

@property
def default_env(self): # type: ignore[no-untyped-def] # noqa: ANN201, D102
return super().default_env
def requirements_file(self) -> str:
"""Path to requirements file.
@property
def requirements_file(self): # type: ignore[no-untyped-def] # noqa: ANN201, D102
return self.options["requirements-file"]
Returns:
Path to the requirements file for this dependency.
"""
return cast(str, self.options["requirements-file"])
Loading

0 comments on commit 716c975

Please sign in to comment.