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

Add docstrings and type hints to dependency plugins #4331

Merged
merged 10 commits into from
Dec 4, 2024
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 @@
[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

Check warning on line 120 in src/molecule/dependency/ansible_galaxy/__init__.py

View check run for this annotation

Codecov / codecov/patch

src/molecule/dependency/ansible_galaxy/__init__.py#L120

Added line #L120 was not covered by tests
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] = {}

Check warning on line 130 in src/molecule/dependency/ansible_galaxy/__init__.py

View check run for this annotation

Codecov / codecov/patch

src/molecule/dependency/ansible_galaxy/__init__.py#L130

Added line #L130 was not covered by tests
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

Check warning on line 133 in src/molecule/dependency/ansible_galaxy/__init__.py

View check run for this annotation

Codecov / codecov/patch

src/molecule/dependency/ansible_galaxy/__init__.py#L132-L133

Added lines #L132 - L133 were not covered by tests

@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] = {}

Check warning on line 142 in src/molecule/dependency/ansible_galaxy/__init__.py

View check run for this annotation

Codecov / codecov/patch

src/molecule/dependency/ansible_galaxy/__init__.py#L142

Added line #L142 was not covered by tests
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)

Check warning on line 144 in src/molecule/dependency/ansible_galaxy/__init__.py

View check run for this annotation

Codecov / codecov/patch

src/molecule/dependency/ansible_galaxy/__init__.py#L144

Added line #L144 was not covered by tests
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
Loading