diff --git a/copier/errors.py b/copier/errors.py index cb3bdf830..2b62378bf 100644 --- a/copier/errors.py +++ b/copier/errors.py @@ -9,7 +9,8 @@ from .types import PathSeq if TYPE_CHECKING: # always false - from .user_data import AnswersMap, Question, Template + from .template import Template + from .user_data import AnswersMap, Question # Errors diff --git a/copier/main.py b/copier/main.py index c07e5e1e0..85715204f 100644 --- a/copier/main.py +++ b/copier/main.py @@ -9,7 +9,7 @@ from itertools import chain from pathlib import Path from shutil import rmtree -from typing import Callable, List, Mapping, Optional, Sequence +from typing import Callable, Iterable, List, Mapping, Optional, Sequence from unicodedata import normalize import pathspec @@ -40,7 +40,8 @@ try: from functools import cached_property except ImportError: - from backports.cached_property import cached_property + # HACK https://github.com/python/mypy/issues/1153#issuecomment-558556828 + from backports.cached_property import cached_property # type: ignore @dataclass @@ -130,7 +131,7 @@ class Worker: """ src_path: Optional[str] = None - dst_path: Path = field(default=".") + dst_path: Path = field(default=Path(".")) answers_file: Optional[RelativePath] = None vcs_ref: OptStr = None data: AnyByStrDict = field(default_factory=dict) @@ -208,7 +209,7 @@ def _render_context(self) -> Mapping: _folder_name=self.subproject.local_abspath.name, ) - def _path_matcher(self, patterns: StrSeq) -> Callable[[Path], bool]: + def _path_matcher(self, patterns: Iterable[str]) -> Callable[[Path], bool]: """Produce a function that matches against specified patterns.""" # TODO Is normalization really needed? normalized_patterns = (normalize("NFD", pattern) for pattern in patterns) @@ -345,7 +346,7 @@ def answers(self) -> AnswersMap: question.get_default() if self.defaults else unsafe_prompt( - question.get_questionary_structure(), answers=result.combined + [question.get_questionary_structure()], answers=result.combined )[question.var_name] ) except KeyboardInterrupt as err: diff --git a/copier/subproject.py b/copier/subproject.py index c4d400978..73fd22634 100644 --- a/copier/subproject.py +++ b/copier/subproject.py @@ -3,6 +3,7 @@ A *subproject* is a project that gets rendered and/or updated with Copier. """ +import sys from pathlib import Path from typing import Optional @@ -15,9 +16,10 @@ from .types import AbsolutePath, AnyByStrDict, VCSTypes from .vcs import is_in_git_repo -try: +# HACK https://github.com/python/mypy/issues/8520#issuecomment-772081075 +if sys.version_info >= (3, 8): from functools import cached_property -except ImportError: +else: from backports.cached_property import cached_property diff --git a/copier/template.py b/copier/template.py index 7652c5b68..75b3f9968 100644 --- a/copier/template.py +++ b/copier/template.py @@ -30,13 +30,10 @@ try: from functools import cached_property except ImportError: - from backports.cached_property import cached_property - -try: - from typing import Literal -except ImportError: - from typing_extensions import Literal + # HACK https://github.com/python/mypy/issues/1153#issuecomment-558556828 + from backports.cached_property import cached_property # type: ignore +from .types import Literal # Default list of files in the template to exclude from the rendered project DEFAULT_EXCLUDE: Tuple[str, ...] = ( diff --git a/copier/tools.py b/copier/tools.py index ea90d27bf..be415510f 100644 --- a/copier/tools.py +++ b/copier/tools.py @@ -9,13 +9,14 @@ import warnings from contextlib import suppress from pathlib import Path -from typing import Any, Callable, Optional, TextIO, Union +from types import TracebackType +from typing import Any, Callable, Optional, TextIO, Tuple, Union import colorama from packaging.version import Version from pydantic import StrictBool -from .types import ExcInfo, IntSeq +from .types import IntSeq try: from importlib.metadata import version @@ -128,7 +129,9 @@ def force_str_end(original_str: str, end: str = "\n") -> str: return original_str -def handle_remove_readonly(func: Callable, path: str, exc: ExcInfo) -> None: +def handle_remove_readonly( + func: Callable, path: str, exc: Tuple[BaseException, OSError, TracebackType] +) -> None: """Handle errors when trying to remove read-only files through `shutil.rmtree`. This handler makes sure the given file is writable, then re-execute the given removal function. diff --git a/copier/types.py b/copier/types.py index 8b18a6649..99dfd1c9c 100644 --- a/copier/types.py +++ b/copier/types.py @@ -1,7 +1,7 @@ """Complex types, annotations, validators.""" +import sys from pathlib import Path -from types import TracebackType from typing import ( TYPE_CHECKING, Any, @@ -18,9 +18,10 @@ from pydantic.validators import path_validator -try: +# HACK https://github.com/python/mypy/issues/8520#issuecomment-772081075 +if sys.version_info >= (3, 8): from typing import Literal -except ImportError: +else: from typing_extensions import Literal if TYPE_CHECKING: @@ -54,7 +55,6 @@ Filters = Dict[str, Callable] LoaderPaths = Union[str, Iterable[str]] VCSTypes = Literal["git"] -ExcInfo = Tuple[BaseException, Exception, TracebackType] class AllowArbitraryTypes: diff --git a/copier/user_data.py b/copier/user_data.py index 9c295bf21..4d2fe9a3f 100644 --- a/copier/user_data.py +++ b/copier/user_data.py @@ -1,6 +1,7 @@ """Functions used to load user data.""" import datetime import json +import sys import warnings from collections import ChainMap from dataclasses import field @@ -30,9 +31,10 @@ from .tools import cast_str_to_bool, force_str_end from .types import AllowArbitraryTypes, AnyByStrDict, OptStr, OptStrOrPath, StrOrPath -try: +# HACK https://github.com/python/mypy/issues/8520#issuecomment-772081075 +if sys.version_info >= (3, 8): from functools import cached_property -except ImportError: +else: from backports.cached_property import cached_property if TYPE_CHECKING: @@ -249,7 +251,7 @@ def get_default_rendered(self) -> Union[bool, str, Choice, None]: return json.dumps(default, indent=2 if self.get_multiline() else None) if self.get_type_name() == "yaml": return yaml.safe_dump( - default, default_flow_style=not self.get_multiline(), width=float("inf") + default, default_flow_style=not self.get_multiline(), width=2147483647 ).strip() # All other data has to be str return str(default) diff --git a/mypy.ini b/mypy.ini deleted file mode 100644 index 0f2fd850e..000000000 --- a/mypy.ini +++ /dev/null @@ -1,3 +0,0 @@ -[mypy] -warn_no_return = False -ignore_missing_imports = True diff --git a/poetry.lock b/poetry.lock index d4bd49f0b..e5ce2dada 100644 --- a/poetry.lock +++ b/poetry.lock @@ -960,6 +960,22 @@ category = "dev" optional = false python-versions = "*" +[[package]] +name = "types-backports" +version = "0.1.3" +description = "Typing stubs for backports" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "types-pyyaml" +version = "6.0.1" +description = "Typing stubs for PyYAML" +category = "dev" +optional = false +python-versions = "*" + [[package]] name = "typing-extensions" version = "4.0.1" @@ -1038,7 +1054,7 @@ docs = ["mkdocs-material", "mkdocs-mermaid2-plugin", "mkdocstrings"] [metadata] lock-version = "1.1" python-versions = ">=3.7,<4.0" -content-hash = "89dd72d65d3cb72ae43ecb4b9b22d4d466d703811276a16179382d8e9d8b45de" +content-hash = "84b47831c5fcded1731e27119d7b7133470c89070769aeaae57b9a42adffe6ab" [metadata.files] astunparse = [ @@ -1622,6 +1638,14 @@ typed-ast = [ {file = "typed_ast-1.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:9c6d1a54552b5330bc657b7ef0eae25d00ba7ffe85d9ea8ae6540d2197a3788c"}, {file = "typed_ast-1.4.3.tar.gz", hash = "sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65"}, ] +types-backports = [ + {file = "types-backports-0.1.3.tar.gz", hash = "sha256:f4b7206c073df88d6200891e3d27506185fd60cda66fb289737b2fa92c0010cf"}, + {file = "types_backports-0.1.3-py2.py3-none-any.whl", hash = "sha256:dafcd61848081503e738a7768872d1dd6c018401b4d2a1cfb608ea87ec9864b9"}, +] +types-pyyaml = [ + {file = "types-PyYAML-6.0.1.tar.gz", hash = "sha256:2e27b0118ca4248a646101c5c318dc02e4ca2866d6bc42e84045dbb851555a76"}, + {file = "types_PyYAML-6.0.1-py3-none-any.whl", hash = "sha256:d5b318269652e809b5c30a5fe666c50159ab80bfd41cd6bafe655bf20b29fcba"}, +] typing-extensions = [ {file = "typing_extensions-4.0.1-py3-none-any.whl", hash = "sha256:7f001e5ac290a0c0401508864c7ec868be4e701886d5b573a9528ed3973d9d3b"}, {file = "typing_extensions-4.0.1.tar.gz", hash = "sha256:4ca091dea149f945ec56afb48dae714f21e8692ef22a395223bcd328961b6a0e"}, diff --git a/pyproject.toml b/pyproject.toml index c541600c6..577d54bf4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,7 +40,7 @@ mkdocstrings = { version = ">=0.16.2,<0.18.0", optional = true } packaging = "^21.0" # packaging is needed when installing from PyPI pathspec = "^0.9.0" plumbum = "^1.6.9" -pydantic = "^1.7.2" +pydantic = "^1.9.0" Pygments = "^2.7.1" PyYAML = "^5.3.1" pyyaml-include = "^1.2" @@ -68,6 +68,8 @@ pre-commit = "^2.16.0" pytest = "^6.1.1" pytest-cov = "^3.0.0" pytest-xdist = "^2.5.0" +types-PyYAML = "^6.0.1" +types-backports = "^0.1.3" [tool.poe.tasks.clean] script = "devtasks:clean" @@ -116,6 +118,11 @@ line_length = 88 multi_line_output = 3 # black interop use_parentheses = true +[tool.mypy] +ignore_missing_imports = true +plugins = ["pydantic.mypy"] +warn_no_return = false + [build-system] requires = ["poetry_core>=1.0.0", "poetry-dynamic-versioning"] build-backend = "poetry.core.masonry.api" diff --git a/tests/test_templated_prompt.py b/tests/test_templated_prompt.py index 4ae7adf18..02518c90a 100644 --- a/tests/test_templated_prompt.py +++ b/tests/test_templated_prompt.py @@ -17,7 +17,6 @@ build_file_tree, ) -envops = {} main_default = "copier" main_question = { "main": {"default": main_default},