Skip to content

Commit

Permalink
Add type hints for cookiecutter.cli and check it with mypy (#2051)
Browse files Browse the repository at this point in the history
* shrink mypy whitelist for cookiecutter.cli
* add typing-extensions to docs requirements
  • Loading branch information
danieleades committed Apr 2, 2024
1 parent 7abf35f commit 46b41f3
Show file tree
Hide file tree
Showing 6 changed files with 43 additions and 35 deletions.
1 change: 1 addition & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ repos:
hooks:
- id: mypy
additional_dependencies:
- click
- types-python-slugify
- types-PyYAML
- types-requests
Expand Down
58 changes: 36 additions & 22 deletions cookiecutter/cli.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
"""Main `cookiecutter` CLI."""

import collections
from __future__ import annotations

import json
import os
import sys
from collections import OrderedDict
from typing import TYPE_CHECKING, Any

if TYPE_CHECKING:
from collections.abc import Iterable

from click import Context, Parameter
from typing_extensions import Literal


import click

Expand Down Expand Up @@ -32,7 +42,9 @@ def version_msg() -> str:
return f"Cookiecutter {__version__} from {location} (Python {python_version})"


def validate_extra_context(ctx, param, value):
def validate_extra_context(
_ctx: Context, _param: Parameter, value: Iterable[str]
) -> OrderedDict[str, str] | None:
"""Validate extra context."""
for string in value:
if '=' not in string:
Expand All @@ -43,13 +55,15 @@ def validate_extra_context(ctx, param, value):

# Convert tuple -- e.g.: ('program_name=foobar', 'startsecs=66')
# to dict -- e.g.: {'program_name': 'foobar', 'startsecs': '66'}
return collections.OrderedDict(s.split('=', 1) for s in value) or None
return OrderedDict(s.split('=', 1) for s in value) or None


def list_installed_templates(default_config, passed_config_file) -> None:
def list_installed_templates(
default_config: bool | dict[str, Any], passed_config_file: str | None
) -> None:
"""List installed (locally cloned) templates. Use cookiecutter --list-installed."""
config = get_user_config(passed_config_file, default_config)
cookiecutter_folder = config.get('cookiecutters_dir')
cookiecutter_folder: str = config['cookiecutters_dir']
if not os.path.exists(cookiecutter_folder):
click.echo(
f"Error: Cannot list installed templates. "
Expand Down Expand Up @@ -154,23 +168,23 @@ def list_installed_templates(default_config, passed_config_file) -> None:
help='Do not delete project folder on failure',
)
def main(
template,
extra_context,
no_input,
checkout,
verbose,
replay,
overwrite_if_exists,
output_dir,
config_file,
default_config,
debug_file,
directory,
skip_if_file_exists,
accept_hooks,
replay_file,
list_installed,
keep_project_on_failure,
template: str,
extra_context: dict[str, Any],
no_input: bool,
checkout: str,
verbose: bool,
replay: bool | str,
overwrite_if_exists: bool,
output_dir: str,
config_file: str | None,
default_config: bool,
debug_file: str | None,
directory: str,
skip_if_file_exists: bool,
accept_hooks: Literal['yes', 'ask', 'no'],
replay_file: str | None,
list_installed: bool,
keep_project_on_failure: bool,
) -> None:
"""Create a project from a Cookiecutter project template (TEMPLATE).
Expand Down
6 changes: 4 additions & 2 deletions cookiecutter/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
library rather than a script.
"""

from __future__ import annotations

import logging
import os
import sys
Expand All @@ -28,7 +30,7 @@ def cookiecutter(
checkout=None,
no_input=False,
extra_context=None,
replay=None,
replay: bool | str | None = None,
overwrite_if_exists=False,
output_dir='.',
config_file=None,
Expand Down Expand Up @@ -198,7 +200,7 @@ def cookiecutter(


class _patch_import_path_for_repo: # noqa: N801
def __init__(self, repo_dir: "os.PathLike[str]") -> None:
def __init__(self, repo_dir: os.PathLike[str]) -> None:
self._repo_dir = f"{repo_dir}" if isinstance(repo_dir, Path) else repo_dir
self._path = None

Expand Down
2 changes: 1 addition & 1 deletion cookiecutter/vcs.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from typing import TYPE_CHECKING

if TYPE_CHECKING:
from typing import Literal
from typing_extensions import Literal

from cookiecutter.exceptions import (
RepositoryCloneFailed,
Expand Down
1 change: 1 addition & 0 deletions docs/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ sphinx-autobuild>=2021.3.14
Sphinx>=4.5.0
sphinxcontrib-apidoc>=0.3.0
sphinx-autodoc-typehints>=1.18.2
typing-extensions
10 changes: 0 additions & 10 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -109,23 +109,13 @@ no_implicit_reexport = true

[[tool.mypy.overrides]]
module = [
"cookiecutter.cli",
"cookiecutter.exceptions",
]
disallow_untyped_defs = false

[[tool.mypy.overrides]]
module = ["cookiecutter.cli"]
disallow_untyped_calls = false

[[tool.mypy.overrides]]
module = ["cookiecutter.cli"]
disable_error_code = ["arg-type"]

[[tool.mypy.overrides]]
module = [
"cookiecutter.config",
"cookiecutter.cli",
"cookiecutter.environment",
"cookiecutter.extensions",
"cookiecutter.main",
Expand Down

0 comments on commit 46b41f3

Please sign in to comment.