Skip to content

Commit

Permalink
run mypy on tests directory (#2059)
Browse files Browse the repository at this point in the history
* run mypy on tests directory

* remove redundant type checks

* remove redundant type checks

* remove redundant type checks

* fix rebase

---------

Co-authored-by: daniel.eades <daniel.eades@seebyte.com>
Co-authored-by: Jens W. Klein <jk@kleinundpartner.at>
  • Loading branch information
3 people committed Apr 18, 2024
1 parent cd851dd commit 464c6b5
Show file tree
Hide file tree
Showing 22 changed files with 59 additions and 67 deletions.
1 change: 1 addition & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ repos:
- rich
- jinja2
- click
- pytest
- python-slugify
- types-PyYAML
- types-requests
Expand Down
2 changes: 1 addition & 1 deletion cookiecutter/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@

from cookiecutter.cli import main

if __name__ == "__main__": # pragma: no cover
if __name__ == "__main__":
main(prog_name="cookiecutter")
6 changes: 3 additions & 3 deletions cookiecutter/generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ def render_and_create_dir(
def _run_hook_from_repo_dir(
repo_dir: str,
hook_name: str,
project_dir: str,
project_dir: Path | str,
context: dict[str, Any],
delete_project_on_failure: bool,
) -> None:
Expand All @@ -322,9 +322,9 @@ def _run_hook_from_repo_dir(


def generate_files(
repo_dir: str,
repo_dir: Path | str,
context: dict[str, Any] | None = None,
output_dir: str = '.',
output_dir: Path | str = '.',
overwrite_if_exists: bool = False,
skip_if_file_exists: bool = False,
accept_hooks: bool = True,
Expand Down
10 changes: 5 additions & 5 deletions cookiecutter/hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ def find_hook(hook_name: str, hooks_dir: str = 'hooks') -> list[str] | None:
return scripts


def run_script(script_path: str, cwd: str = '.') -> None:
def run_script(script_path: str, cwd: Path | str = '.') -> None:
"""Execute a script from a working directory.
:param script_path: Absolute path to the script to run.
Expand Down Expand Up @@ -109,7 +109,7 @@ def run_script(script_path: str, cwd: str = '.') -> None:


def run_script_with_context(
script_path: str, cwd: str, context: dict[str, Any]
script_path: Path | str, cwd: Path | str, context: dict[str, Any]
) -> None:
"""Execute a script after rendering it with Jinja.
Expand All @@ -131,7 +131,7 @@ def run_script_with_context(
run_script(temp.name, cwd)


def run_hook(hook_name: str, project_dir: str, context: dict[str, Any]) -> None:
def run_hook(hook_name: str, project_dir: Path | str, context: dict[str, Any]) -> None:
"""
Try to find and execute a hook from the specified project directory.
Expand All @@ -149,9 +149,9 @@ def run_hook(hook_name: str, project_dir: str, context: dict[str, Any]) -> None:


def run_hook_from_repo_dir(
repo_dir: str,
repo_dir: Path | str,
hook_name: str,
project_dir: str,
project_dir: Path | str,
context: dict[str, Any],
delete_project_on_failure: bool,
) -> None:
Expand Down
5 changes: 1 addition & 4 deletions cookiecutter/prompt.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ def read_repo_password(question: str) -> str:
return Prompt.ask(question, password=True)


def read_user_choice(var_name: str, options, prompts=None, prefix: str = ""):
def read_user_choice(var_name: str, options: list, prompts=None, prefix: str = ""):
"""Prompt the user to choose from several options for the given variable.
The first item will be returned if no input happens.
Expand All @@ -98,9 +98,6 @@ def read_user_choice(var_name: str, options, prompts=None, prefix: str = ""):
:param list options: Sequence of options that are available to select from
:return: Exactly one item of ``options`` that has been chosen by the user
"""
if not isinstance(options, list):
raise TypeError

if not options:
raise ValueError

Expand Down
9 changes: 0 additions & 9 deletions cookiecutter/replay.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,6 @@ def dump(replay_dir: Path | str, template_name: str, context: dict[str, Any]) ->
"""Write json data to file."""
make_sure_path_exists(replay_dir)

if not isinstance(template_name, str):
raise TypeError('Template name is required to be of type str')

if not isinstance(context, dict):
raise TypeError('Context is required to be of type dict')

if 'cookiecutter' not in context:
raise ValueError('Context is required to contain a cookiecutter key')

Expand All @@ -44,9 +38,6 @@ def dump(replay_dir: Path | str, template_name: str, context: dict[str, Any]) ->

def load(replay_dir: Path | str, template_name: str) -> dict[str, Any]:
"""Read json data from file."""
if not isinstance(template_name, str):
raise TypeError('Template name is required to be of type str')

replay_file = get_file_name(replay_dir, template_name)

with open(replay_file, encoding="utf-8") as infile:
Expand Down
13 changes: 12 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,8 @@ exclude = ["tests/hooks-abort-render/hooks/*"]
[tool.mypy]
strict = true
show_error_codes = true
files = "cookiecutter"
files = ["cookiecutter", "tests"]
exclude = "(?x)(/hooks/ | tests/test-output-folder/)"
no_implicit_reexport = true


Expand All @@ -114,6 +115,16 @@ module = [
]
ignore_errors = true

[[tool.mypy.overrides]]
module = [
"tests.*",
]
disable_error_code = ["no-untyped-def"]

[tool.coverage.report]
exclude_also = ["if TYPE_CHECKING:"]

[tool.coverage.run]
omit = [
"cookiecutter/__main__.py",
]
2 changes: 1 addition & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ def output_dir(tmp_path) -> str:


@pytest.fixture
def clone_dir(tmp_path) -> Path:
def clone_dir(tmp_path: Path) -> Path:
"""Simulate creation of a directory called `clone_dir` inside of `tmp_path`. \
Returns a str to said directory."""
clone_dir = tmp_path.joinpath("clone_dir")
Expand Down
12 changes: 0 additions & 12 deletions tests/replay/test_dump.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,18 +32,6 @@ def fin_remove_replay_file() -> None:
request.addfinalizer(fin_remove_replay_file)


def test_type_error_if_no_template_name(replay_test_dir, context) -> None:
"""Test that replay.dump raises if the template_name is not a valid str."""
with pytest.raises(TypeError):
replay.dump(replay_test_dir, None, context)


def test_type_error_if_not_dict_context(replay_test_dir, template_name) -> None:
"""Test that replay.dump raises if the context is not of type dict."""
with pytest.raises(TypeError):
replay.dump(replay_test_dir, template_name, 'not_a_dict')


def test_value_error_if_key_missing_in_context(replay_test_dir, template_name) -> None:
"""Test that replay.dump raises if the context does not contain a key \
named 'cookiecutter'."""
Expand Down
6 changes: 0 additions & 6 deletions tests/replay/test_load.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,6 @@ def replay_file(replay_test_dir, template_name):
return os.path.join(replay_test_dir, file_name)


def test_type_error_if_no_template_name(replay_test_dir) -> None:
"""Test that replay.load raises if the template_name is not a valid str."""
with pytest.raises(TypeError):
replay.load(replay_test_dir, None)


def test_value_error_if_key_missing_in_context(replay_test_dir) -> None:
"""Test that replay.load raises if the loaded context does not contain \
'cookiecutter'."""
Expand Down
2 changes: 1 addition & 1 deletion tests/repository/test_determine_repo_dir_clones_repo.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ def test_repository_url_with_no_context_file(mocker, template_url) -> None:
repository.determine_repo_dir(
template_url,
abbreviations={},
clone_to_dir=None,
clone_to_dir=".",
checkout=None,
no_input=True,
)
Expand Down
9 changes: 5 additions & 4 deletions tests/test-extensions/hello_extension/hello_extension.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
"""Provides custom extension, exposing a ``hello`` command."""

from jinja2 import nodes
from jinja2 import Environment, nodes
from jinja2.ext import Extension
from jinja2.parser import Parser


class HelloExtension(Extension):
"""Simple jinja2 extension for cookiecutter test purposes."""

tags = {'hello'}

def __init__(self, environment) -> None:
def __init__(self, environment: Environment) -> None:
"""Hello Extension Constructor."""
super().__init__(environment)

def _hello(self, name) -> str:
def _hello(self, name: str) -> str:
"""Do actual tag replace when invoked by parser."""
return f'Hello {name}!'

def parse(self, parser):
def parse(self, parser: Parser) -> nodes.Output:
"""Work when something match `tags` variable."""
lineno = next(parser.stream).lineno
node = parser.parse_expression()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Provides custom extension, exposing a ``foobar`` filter."""

from jinja2 import Environment
from jinja2.ext import Extension

from cookiecutter.utils import simple_filter
Expand All @@ -8,13 +9,13 @@
class FoobarExtension(Extension):
"""Simple jinja2 extension for cookiecutter test purposes."""

def __init__(self, environment) -> None:
def __init__(self, environment: Environment) -> None:
"""Foobar Extension Constructor."""
super().__init__(environment)
environment.filters['foobar'] = lambda v: v * 2


@simple_filter
def simplefilterextension(v):
def simplefilterextension(v: str) -> str:
"""Provide a simple function-based filter extension."""
return v.upper()
3 changes: 1 addition & 2 deletions tests/test_abort_generate_on_hook_error.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,10 @@ def test_hooks_raises_errors(tmp_path, abort_pre_gen, abort_post_gen) -> None:
}
}

with pytest.raises(exceptions.FailedHookException) as error:
with pytest.raises(exceptions.FailedHookException):
generate.generate_files(
repo_dir="tests/hooks-abort-render",
context=context,
output_dir=str(tmp_path),
)
assert error.value.code == 5
assert not tmp_path.joinpath("foobar").is_dir()
2 changes: 1 addition & 1 deletion tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from click.testing import CliRunner

from cookiecutter import utils
from cookiecutter.__main__ import main
from cookiecutter.cli import main
from cookiecutter.environment import StrictEnvironment
from cookiecutter.exceptions import UnknownExtension
from cookiecutter.main import cookiecutter
Expand Down
5 changes: 4 additions & 1 deletion tests/test_generate_context.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
"""Verify generate context behaviour and context overwrite priorities."""

from __future__ import annotations

import os
import re
from collections import OrderedDict
from typing import Any, Iterator

import pytest

Expand All @@ -11,7 +14,7 @@
from cookiecutter.prompt import YesNoPrompt


def context_data():
def context_data() -> Iterator[tuple[dict[str, Any], dict[str, Any]]]:
"""Generate pytest parametrization variables for test.
Return ('input_params, expected_context') tuples.
Expand Down
3 changes: 2 additions & 1 deletion tests/test_generate_files.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import pytest
from binaryornot.check import is_binary
from jinja2 import Environment

from cookiecutter import exceptions, generate

Expand Down Expand Up @@ -446,6 +447,6 @@ def test_raise_empty_dir_name(output_dir, undefined_context):
dirname='',
output_dir=output_dir,
context=undefined_context,
environment=None,
environment=Environment(autoescape=True),
)
assert not Path(output_dir).joinpath('testproject').exists()
4 changes: 3 additions & 1 deletion tests/test_hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from cookiecutter import exceptions, hooks, utils


def make_test_repo(name, multiple_hooks=False):
def make_test_repo(name: str, multiple_hooks: bool = False) -> str:
"""Create test repository for test setup methods."""
hook_dir = os.path.join(name, 'hooks')
template = os.path.join(name, 'input{{hooks}}')
Expand Down Expand Up @@ -90,10 +90,12 @@ def test_find_hook(self) -> None:
with utils.work_in(self.repo_path):
expected_pre = os.path.abspath('hooks/pre_gen_project.py')
actual_hook_path = hooks.find_hook('pre_gen_project')
assert actual_hook_path
assert expected_pre == actual_hook_path[0]

expected_post = os.path.abspath(f'hooks/{self.post_hook}')
actual_hook_path = hooks.find_hook('post_gen_project')
assert actual_hook_path
assert expected_post == actual_hook_path[0]

def test_no_hooks(self) -> None:
Expand Down
8 changes: 4 additions & 4 deletions tests/test_pre_prompt_hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ def _func(repo_dir: Path) -> None:
def test_run_pre_prompt_python_hook(remove_tmp_repo_dir) -> None:
"""Verify pre_prompt.py runs and creates a copy of cookiecutter.json."""
new_repo_dir = hooks.run_pre_prompt_hook(repo_dir='tests/test-pyhooks/')
assert new_repo_dir.exists()
bkp_config = new_repo_dir / "_cookiecutter.json"
assert new_repo_dir.exists() # type: ignore[union-attr]
bkp_config = new_repo_dir / "_cookiecutter.json" # type: ignore[operator]
assert bkp_config.exists()
remove_tmp_repo_dir(new_repo_dir)

Expand All @@ -45,7 +45,7 @@ def test_run_pre_prompt_python_hook_fail(monkeypatch) -> None:
def test_run_pre_prompt_shell_hook(remove_tmp_repo_dir) -> None:
"""Verify pre_prompt.sh runs and creates a copy of cookiecutter.json."""
new_repo_dir = hooks.run_pre_prompt_hook(repo_dir='tests/test-pyshellhooks/')
assert new_repo_dir.exists()
bkp_config = new_repo_dir / "_cookiecutter.json"
assert new_repo_dir.exists() # type: ignore[union-attr]
bkp_config = new_repo_dir / "_cookiecutter.json" # type: ignore[operator]
assert bkp_config.exists()
remove_tmp_repo_dir(new_repo_dir)
7 changes: 5 additions & 2 deletions tests/test_prompt.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
"""Tests for `cookiecutter.prompt` module."""

from __future__ import annotations

import json
import platform
import sys
from collections import OrderedDict
from pathlib import Path
from typing import Any

import click
import pytest
Expand Down Expand Up @@ -175,7 +178,7 @@ def test_prompt_for_config_dict(self, monkeypatch) -> None:
'cookiecutter.prompt.read_user_dict',
lambda _var, _default, _prompts, _prefix: {"key": "value", "integer": 37},
)
context = {'cookiecutter': {'details': {}}}
context: dict[str, Any] = {'cookiecutter': {'details': {}}}

cookiecutter_dict = prompt.prompt_for_config(context)
assert cookiecutter_dict == {'details': {'key': 'value', 'integer': 37}}
Expand Down Expand Up @@ -588,7 +591,7 @@ def test_undefined_variable(context) -> None:
["fake-nested-templates-old-style", "fake-package"],
],
)
def test_cookiecutter_nested_templates(template_dir: str, expected: str) -> None:
def test_cookiecutter_nested_templates(template_dir: str, expected: Path | str) -> None:
"""Test nested_templates generation."""
from cookiecutter import prompt

Expand Down
3 changes: 0 additions & 3 deletions tests/test_read_user_choice.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,5 @@ def test_raise_if_options_is_not_a_non_empty_list() -> None:
Test for choice type invocation.
"""
with pytest.raises(TypeError):
read_user_choice('foo', 'NOT A LIST')

with pytest.raises(ValueError):
read_user_choice('foo', [])

0 comments on commit 464c6b5

Please sign in to comment.