Skip to content

Commit

Permalink
Add docstrings and type hints to init command. (#4314)
Browse files Browse the repository at this point in the history
  • Loading branch information
Qalthos authored Oct 31, 2024
1 parent b0faa8a commit cb654fe
Show file tree
Hide file tree
Showing 7 changed files with 86 additions and 50 deletions.
16 changes: 0 additions & 16 deletions .config/pydoclint-baseline.txt
Original file line number Diff line number Diff line change
@@ -1,19 +1,3 @@
src/molecule/command/init/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.)
--------------------
src/molecule/command/init/scenario.py
DOC101: Method `Scenario.__init__`: Docstring contains fewer arguments than in function signature.
DOC103: Method `Scenario.__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: [command_args: dict[str, str]].
DOC101: Method `Scenario.execute`: Docstring contains fewer arguments than in function signature.
DOC106: Method `Scenario.execute`: The option `--arg-type-hints-in-signature` is `True` but there are no argument type hints in the signature
DOC107: Method `Scenario.execute`: The option `--arg-type-hints-in-signature` is `True` but not all args in the signature have type hints
DOC103: Method `Scenario.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: ].
DOC101: Function `scenario`: Docstring contains fewer arguments than in function signature.
DOC106: Function `scenario`: The option `--arg-type-hints-in-signature` is `True` but there are no argument type hints in the signature
DOC107: Function `scenario`: The option `--arg-type-hints-in-signature` is `True` but not all args in the signature have type hints
DOC103: Function `scenario`: 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: [ctx: , dependency_name: , driver_name: , provisioner_name: , scenario_name: ].
--------------------
src/molecule/command/reset.py
DOC101: Function `reset`: Docstring contains fewer arguments than in function signature.
DOC106: Function `reset`: 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/init/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
# D104 # noqa: D104, ERA001
# noqa: D104
17 changes: 12 additions & 5 deletions src/molecule/command/init/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,28 @@

import abc
import logging
import os

from pathlib import Path

from molecule import util


LOG = logging.getLogger(__name__)


class Base:
class Base(abc.ABC):
"""Init Command Base Class."""

__metaclass__ = abc.ABCMeta
@abc.abstractmethod
def execute(self, action_args: list[str] | None = None) -> None:
"""Abstract method to execute the command.
Args:
action_args: An optional list of arguments to pass to the action.
"""

def _validate_template_dir(self, template_dir): # type: ignore[no-untyped-def] # noqa: ANN001, ANN202
if not os.path.isdir(template_dir): # noqa: PTH112
def _validate_template_dir(self, template_dir: str) -> None:
if not Path(template_dir).is_dir():
util.sysexit_with_message(
"The specified template directory (" + str(template_dir) + ") does not exist",
)
4 changes: 2 additions & 2 deletions src/molecule/command/init/init.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@
LOG = logging.getLogger(__name__)


@base.click_group_ex() # type: ignore # noqa: PGH003
def init(): # pragma: no cover # noqa: ANN201
@base.click_group_ex()
def init() -> None: # pragma: no cover
"""Initialize a new scenario."""


Expand Down
83 changes: 63 additions & 20 deletions src/molecule/command/init/scenario.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
import os
import sys

from pathlib import Path
from typing import TYPE_CHECKING

import click

from molecule import api, config, util
Expand All @@ -33,6 +36,27 @@
from molecule.config import DEFAULT_DRIVER, MOLECULE_EMBEDDED_DATA_DIR


if TYPE_CHECKING:
from typing import TypedDict

class CommandArgs(TypedDict):
"""Argument dictionary to pass to init-scenario playbook.
Attributes:
dependency_name: Name of the dependency to initialize.
driver_name: Name of the driver to initialize.
provisioner_name: Name of the provisioner to initialize.
scenario_name: Name of the scenario to initialize.
subcommand: Name of subcommand to initialize.
"""

dependency_name: str
driver_name: str
provisioner_name: str
scenario_name: str
subcommand: str


LOG = logging.getLogger(__name__)


Expand All @@ -59,20 +83,28 @@ class Scenario(base.Base):
Initialize a new scenario using a embedded template.
"""

def __init__(self, command_args: dict[str, str]) -> None:
"""Construct Scenario."""
def __init__(self, command_args: CommandArgs) -> None:
"""Construct Scenario.
Args:
command_args: Arguments to pass to init-scenario playbook.
"""
self._command_args = command_args

def execute(self, action_args=None): # type: ignore[no-untyped-def] # noqa: ANN001, ANN201, ARG002
"""Execute the actions necessary to perform a `molecule init scenario` and returns None."""
def execute(self, action_args: list[str] | None = None) -> None: # noqa: ARG002
"""Execute the actions necessary to perform a `molecule init scenario`.
Args:
action_args: Arguments for this command. Unused.
"""
scenario_name = self._command_args["scenario_name"]

msg = f"Initializing new scenario {scenario_name}..."
LOG.info(msg)
molecule_directory = config.molecule_directory(os.getcwd()) # noqa: PTH109
scenario_directory = os.path.join(molecule_directory, scenario_name) # noqa: PTH118
molecule_directory = Path(config.molecule_directory(Path.cwd()))
scenario_directory = molecule_directory / scenario_name

if os.path.isdir(scenario_directory): # noqa: PTH112
if scenario_directory.is_dir():
msg = f"The directory molecule/{scenario_name} exists. Cannot create new scenario."
util.sysexit_with_message(msg)

Expand All @@ -97,14 +129,18 @@ def execute(self, action_args=None): # type: ignore[no-untyped-def] # noqa: AN
LOG.info(msg)


def _role_exists(ctx, param, value: str): # type: ignore[no-untyped-def] # pragma: no cover # noqa: ANN001, ANN202, ARG001
def _role_exists(
ctx: click.Context, # noqa: ARG001
param: str | None, # noqa: ARG001
value: str,
) -> str: # pragma: no cover
# if role name was not mentioned we assume that current directory is the
# one hosting the role and determining the role name.
if not value:
value = os.path.basename(os.getcwd()) # noqa: PTH109, PTH119
value = Path.cwd().name

role_directory = os.path.join(os.pardir, value) # noqa: PTH118
if not os.path.exists(role_directory): # noqa: PTH110
role_directory = Path.cwd().parent / value
if not role_directory.exists():
msg = f"The role '{value}' not found. Please choose the proper role name."
util.sysexit_with_message(msg)
return value
Expand Down Expand Up @@ -136,18 +172,25 @@ def _role_exists(ctx, param, value: str): # type: ignore[no-untyped-def] # prag
default=command_base.MOLECULE_DEFAULT_SCENARIO_NAME,
required=False,
)
def scenario( # type: ignore[no-untyped-def] # noqa: ANN201
ctx, # noqa: ANN001, ARG001
dependency_name, # noqa: ANN001
driver_name, # noqa: ANN001
provisioner_name, # noqa: ANN001
scenario_name, # noqa: ANN001
): # pragma: no cover
def scenario(
ctx: click.Context, # noqa: ARG001
dependency_name: str,
driver_name: str,
provisioner_name: str,
scenario_name: str,
) -> None: # pragma: no cover
"""Initialize a new scenario for use with Molecule.
If name is not specified the 'default' value will be used.
Args:
ctx: Click context object holding commandline arguments.
dependency_name: Name of dependency to initialize.
driver_name: Name of driver to use.
provisioner_name: Name of provisioner to use.
scenario_name: Name of scenario to initialize.
"""
command_args = {
command_args: CommandArgs = {
"dependency_name": dependency_name,
"driver_name": driver_name,
"provisioner_name": provisioner_name,
Expand All @@ -156,4 +199,4 @@ def scenario( # type: ignore[no-untyped-def] # noqa: ANN201
}

s = Scenario(command_args)
s.execute() # type: ignore[no-untyped-call]
s.execute()
6 changes: 4 additions & 2 deletions src/molecule/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -523,7 +523,7 @@ def _validate(self) -> None:
util.sysexit_with_message(msg)


def molecule_directory(path: str) -> str:
def molecule_directory(path: str | Path) -> str:
"""Return directory of the current scenario.
Args:
Expand All @@ -532,7 +532,9 @@ def molecule_directory(path: str) -> str:
Returns:
The current scenario's directory.
"""
return os.path.join(path, MOLECULE_DIRECTORY) # noqa: PTH118
if isinstance(path, str):
path = Path(path)
return str(path / MOLECULE_DIRECTORY)


def molecule_file(path: str) -> str:
Expand Down
8 changes: 4 additions & 4 deletions tests/unit/command/init/test_scenario.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def fixture_command_args() -> dict[str, str]:


@pytest.fixture(name="instance")
def fixture_instance(command_args: dict[str, str]) -> scenario.Scenario:
def fixture_instance(command_args: scenario.CommandArgs) -> scenario.Scenario:
"""Provide a scenario instance.
Args:
Expand All @@ -70,7 +70,7 @@ def test_scenario_execute(
test_cache_path: Path to the cache directory for the test.
"""
monkeypatch.chdir(test_cache_path)
instance.execute() # type: ignore[no-untyped-call]
instance.execute()

msg = "Initializing new scenario test-scenario..."
patched_logger_info.assert_any_call(msg)
Expand All @@ -97,10 +97,10 @@ def test_execute_scenario_exists(
test_cache_path: Path to the cache directory for the test.
"""
monkeypatch.chdir(test_cache_path)
instance.execute() # type: ignore[no-untyped-call]
instance.execute()

with pytest.raises(SystemExit) as e:
instance.execute() # type: ignore[no-untyped-call]
instance.execute()

assert e.value.code == 1

Expand Down

0 comments on commit cb654fe

Please sign in to comment.