Skip to content

Commit

Permalink
Additional cleanup
Browse files Browse the repository at this point in the history
The typing in utils is a broken mess but should be fixable
  • Loading branch information
Qalthos committed Nov 22, 2024
1 parent 07ea9a8 commit 90a4b16
Show file tree
Hide file tree
Showing 8 changed files with 59 additions and 47 deletions.
9 changes: 5 additions & 4 deletions src/molecule/dependency/ansible_galaxy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

if TYPE_CHECKING:
from molecule.config import Config
from molecule.types import DependencyOptions


class AnsibleGalaxy(Base):
Expand Down Expand Up @@ -127,17 +128,17 @@ def default_env(self) -> dict[str, str]:
"""
env: dict[str, str] = {}
for invoker in self.invocations:
env = util.merge_dicts(env, invoker.default_env) # type: ignore[type-var]
env = util.merge_dicts(env, invoker.default_env)
return env

@property
def default_options(self) -> dict[str, str | bool]:
def default_options(self) -> DependencyOptions:
"""Default options across all invokers.
Returns:
Merged dictionary of default options for all invokers.
"""
opts: dict[str, str] = {}
opts: DependencyOptions = {}
for invoker in self.invocations:
opts = util.merge_dicts(opts, invoker.default_options) # type: ignore[type-var]
opts = util.merge_dicts(opts, invoker.default_options)
return opts
36 changes: 20 additions & 16 deletions src/molecule/dependency/ansible_galaxy/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import logging
import os

from pathlib import Path
from typing import TYPE_CHECKING

from molecule import util
Expand All @@ -33,6 +34,7 @@

if TYPE_CHECKING:
from molecule.config import Config
from molecule.types import DependencyOptions


LOG = logging.getLogger(__name__)
Expand All @@ -43,9 +45,11 @@ 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.
"""

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

def __init__(self, config: Config) -> None:
"""Construct AnsibleGalaxy.
Expand All @@ -54,7 +58,7 @@ def __init__(self, config: Config) -> None:
config: Molecule Config instance.
"""
super().__init__(config)
self._sh_command = None
self._sh_command = []

self.command = "ansible-galaxy"

Expand All @@ -68,13 +72,13 @@ def requirements_file(self) -> str: # cover
"""

@property
def default_options(self) -> dict[str, str | bool]:
def default_options(self) -> DependencyOptions:
"""Default options for this dependency.
Returns:
Default options for this dependency.
"""
d: dict[str, str | bool] = {
d: DependencyOptions = {
"force": False,
}
if self._config.debug:
Expand All @@ -84,9 +88,9 @@ def default_options(self) -> dict[str, str | bool]:

def filter_options(
self,
opts: dict[str, str | bool],
opts: DependencyOptions,
keys: tuple[str, ...],
) -> dict[str, str | bool]:
) -> DependencyOptions:
"""Filter certain keys from a dictionary.
Removes all the values of ``keys`` from the dictionary ``opts``, if
Expand All @@ -103,26 +107,26 @@ def filter_options(
c = copy.copy(opts)
for key in keys:
if key in c:
del c[key]
del c[key] # type: ignore[misc]
return c

# NOTE(retr0h): Override the base classes' options() to handle
# ``ansible-galaxy`` one-off.
@property
def options(self) -> dict[str, str | bool]:
def options(self) -> DependencyOptions:
"""Computed options for this dependency.
Returns:
Merged and filtered options for this dependency.
"""
o = self._config.config["dependency"]["options"]
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)
opts = util.merge_dicts(self.default_options, opts)
return self.filter_options(opts, self.FILTER_OPTS)

@property
def default_env(self) -> dict[str, str]:
Expand All @@ -132,7 +136,7 @@ def default_env(self) -> dict[str, str]:
Default environment variables for this dependency.
"""
env = dict(os.environ)
return util.merge_dicts(env, self._config.env) # type: ignore[type-var]
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."""
Expand All @@ -141,7 +145,7 @@ def bake(self) -> None:

self._sh_command = [
self.command,
*self.COMMANDS, # type: ignore[attr-defined] # pylint: disable=no-member
*self.COMMANDS,
*util.dict2args(options),
*verbose_flag,
]
Expand All @@ -163,7 +167,7 @@ def execute(self, action_args: list[str] | None = None) -> None: # noqa: ARG002
LOG.warning(msg)
return

if self._sh_command is None:
if not self._sh_command:
self.bake()

self._setup()
Expand All @@ -173,4 +177,4 @@ def _setup(self) -> None:
"""Prepare the system for using ``ansible-galaxy`` and returns None."""

def _has_requirements_file(self) -> bool:
return os.path.isfile(self.requirements_file) # noqa: PTH113
return Path(self.requirements_file).is_file()
10 changes: 6 additions & 4 deletions src/molecule/dependency/ansible_galaxy/collections.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
from __future__ import annotations

import logging
import os

from pathlib import Path
from typing import TYPE_CHECKING

from molecule import util
Expand Down Expand Up @@ -35,9 +35,11 @@ def default_options(self) -> DependencyOptions:
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",
),
),
},
)
Expand Down
10 changes: 6 additions & 4 deletions src/molecule/dependency/ansible_galaxy/roles.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
from __future__ import annotations

import logging
import os

from pathlib import Path
from typing import TYPE_CHECKING

from molecule import util
Expand Down Expand Up @@ -35,9 +35,11 @@ def default_options(self) -> DependencyOptions:
specific = util.merge_dicts(
general,
{
"role-file": os.path.join( # noqa: PTH118
self._config.scenario.directory,
"requirements.yml",
"role-file": str(
Path(
self._config.scenario.directory,
"requirements.yml",
),
),
},
)
Expand Down
17 changes: 8 additions & 9 deletions src/molecule/dependency/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@

if TYPE_CHECKING:
from molecule.config import Config
from molecule.types import DependencyOptions


LOG = logging.getLogger(__name__)
Expand All @@ -58,14 +59,12 @@ def __init__(self, config: Config) -> None:
config: An instance of a Molecule config.
"""
self._config = config
self._sh_command: str | list[str] | None = None
self._sh_command: list[str] = []

def execute_with_retries(self) -> None:
"""Run dependency downloads with retry and timed back-off."""
exception = None

try:
util.run_command(self._sh_command, debug=self._config.debug, check=True) # type: ignore[arg-type]
util.run_command(self._sh_command, debug=self._config.debug, check=True)
msg = "Dependency completed successfully."
LOG.info(msg)
return # noqa: TRY300
Expand All @@ -82,15 +81,15 @@ def execute_with_retries(self) -> None:
self.SLEEP += self.BACKOFF

try:
util.run_command(self._sh_command, debug=self._config.debug, check=True) # type: ignore[arg-type]
util.run_command(self._sh_command, debug=self._config.debug, check=True)
msg = "Dependency completed successfully."
LOG.info(msg)
return # noqa: TRY300
except CalledProcessError as _exception:
exception = _exception

LOG.error(str(exception))
util.sysexit(exception.returncode) # type: ignore[union-attr]
util.sysexit(exception.returncode)

@abc.abstractmethod
def execute(
Expand All @@ -107,7 +106,7 @@ def execute(

@property
@abc.abstractmethod
def default_options(self) -> dict[str, str | bool]: # pragma: no cover
def default_options(self) -> DependencyOptions: # pragma: no cover
"""Get default CLI arguments provided to ``cmd``.
Returns:
Expand All @@ -125,7 +124,7 @@ def default_env(self) -> dict[str, str]: # pragma: no cover
# dict[str, str] should fit the typevar of merge_dicts, and all types are the same, yet
# it still complains.
env = dict(os.environ)
return util.merge_dicts(env, self._config.env) # type: ignore[type-var]
return util.merge_dicts(env, self._config.env)

@property
def name(self) -> str:
Expand All @@ -145,7 +144,7 @@ def enabled(self) -> bool:
return self._config.config["dependency"]["enabled"]

@property
def options(self) -> dict[str, str | bool]:
def options(self) -> DependencyOptions:
"""Computed dependency options.
Returns:
Expand Down
9 changes: 5 additions & 4 deletions src/molecule/dependency/shell.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@

if TYPE_CHECKING:
from molecule.config import Config
from molecule.types import DependencyOptions


LOG = logging.getLogger(__name__)
Expand Down Expand Up @@ -81,7 +82,7 @@ def __init__(self, config: Config) -> None:
config: Molecule Config instance.
"""
super().__init__(config)
self._sh_command: str | None = None
self._sh_command: list[str] = []

@property
def command(self) -> str:
Expand All @@ -93,7 +94,7 @@ def command(self) -> str:
return self._config.config["dependency"]["command"] or ""

@property
def default_options(self) -> dict[str, str | bool]:
def default_options(self) -> DependencyOptions:
"""Get default options for shell dependencies (none).
Returns:
Expand All @@ -103,7 +104,7 @@ def default_options(self) -> dict[str, str | bool]:

def bake(self) -> None:
"""Bake a ``shell`` command so it's ready to execute."""
self._sh_command = self.command
self._sh_command = [self.command]

def execute(self, action_args: list[str] | None = None) -> None: # noqa: ARG002
"""Execute the dependency solver.
Expand All @@ -117,7 +118,7 @@ def execute(self, action_args: list[str] | None = None) -> None: # noqa: ARG002
return
super().execute()

if self._sh_command is None:
if not self._sh_command:
self.bake()
self.execute_with_retries()

Expand Down
3 changes: 2 additions & 1 deletion src/molecule/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@
from typing import Any


# We have to use the alternate form here because dashes are invalid in python identifiers
DependencyOptions = TypedDict(
"DependencyOptions",
{"force": bool, "requirements-file": str, "role-file": str, "vvv": bool},
{"force": bool, "requirements-file": str, "role-file": str, "verbose": bool, "vvv": bool},
total=False,
)

Expand Down
12 changes: 7 additions & 5 deletions src/molecule/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,12 @@

from ansible_compat.types import JSON

from molecule.types import CommandArgs, ConfigData, PlatformData
from molecule.types import CommandArgs, ConfigData, DependencyOptions, PlatformData

NestedDict = MutableMapping[str, JSON]
_T = TypeVar("_T", bound=NestedDict)
GenericOptions = dict[str, str | bool]
Options = TypeVar("Options", bound=(GenericOptions | DependencyOptions))


LOG = logging.getLogger(__name__)
Expand Down Expand Up @@ -357,7 +359,7 @@ def instance_with_scenario_name(instance_name: str, scenario_name: str) -> str:
return f"{instance_name}-{scenario_name}"


def verbose_flag(options: MutableMapping[str, Any]) -> list[str]:
def verbose_flag(options: Options) -> list[str]:
"""Return computed verbosity flag.
Args:
Expand All @@ -380,7 +382,7 @@ def verbose_flag(options: MutableMapping[str, Any]) -> list[str]:
return flags


def filter_verbose_permutation(options: dict[str, Any]) -> dict[str, Any]:
def filter_verbose_permutation(options: Options) -> Options:
"""Clean verbose information.
Args:
Expand Down Expand Up @@ -557,7 +559,7 @@ def boolean(value: bool | AnyStr, *, strict: bool = True) -> bool:
)


def dict2args(data: dict[str, str | bool]) -> list[str]:
def dict2args(data: Options) -> list[str]:
"""Convert a dictionary of options to command like arguments.
Args:
Expand All @@ -566,7 +568,7 @@ def dict2args(data: dict[str, str | bool]) -> list[str]:
Returns:
A list of command-like flags represented by the dictionary.
"""
result = []
result: list[str] = []
# keep sorting in order to achieve a predictable behavior
for k, v in sorted(data.items()):
if v is not False:
Expand Down

0 comments on commit 90a4b16

Please sign in to comment.