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 init command. #4314

Merged
merged 2 commits into from
Oct 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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():

Check warning on line 46 in src/molecule/command/init/base.py

View check run for this annotation

Codecov / codecov/patch

src/molecule/command/init/base.py#L46

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