Skip to content

Commit

Permalink
add ruff 'flake8-future-annotations' (FA) lint group
Browse files Browse the repository at this point in the history
  • Loading branch information
daniel.eades committed Feb 19, 2024
1 parent 6b878c8 commit fa91c72
Show file tree
Hide file tree
Showing 16 changed files with 104 additions and 86 deletions.
25 changes: 11 additions & 14 deletions copier/main.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Main functions and classes, used to generate or update projects."""
from __future__ import annotations

import os
import platform
Expand All @@ -15,13 +16,9 @@
from typing import (
Callable,
Iterable,
List,
Literal,
Mapping,
Optional,
Sequence,
Set,
Union,
get_args,
)
from unicodedata import normalize
Expand Down Expand Up @@ -169,9 +166,9 @@ class Worker:
See [unsafe][]
"""

src_path: Optional[str] = None
src_path: str | None = None
dst_path: Path = Path(".")
answers_file: Optional[RelativePath] = None
answers_file: RelativePath | None = None
vcs_ref: OptStr = None
data: AnyByStrDict = field(default_factory=dict)
exclude: StrSeq = ()
Expand All @@ -189,7 +186,7 @@ class Worker:
skip_answered: bool = False

answers: AnswersMap = field(default_factory=AnswersMap, init=False)
_cleanup_hooks: List[Callable] = field(default_factory=list, init=False)
_cleanup_hooks: list[Callable] = field(default_factory=list, init=False)

def __enter__(self):
"""Allow using worker as a context manager."""
Expand All @@ -215,7 +212,7 @@ def _check_unsafe(self, mode: Literal["copy", "update"]) -> None:
"""Check whether a template uses unsafe features."""
if self.unsafe:
return
features: Set[str] = set()
features: set[str] = set()
if self.template.jinja_extensions:
features.add("jinja_extensions")
if self.template.tasks:
Expand Down Expand Up @@ -353,7 +350,7 @@ def _render_allowed(
dst_relpath: Path,
is_dir: bool = False,
is_symlink: bool = False,
expected_contents: Union[bytes, Path] = b"",
expected_contents: bytes | Path = b"",
) -> bool:
"""Determine if a file or directory can be rendered.
Expand All @@ -373,7 +370,7 @@ def _render_allowed(
dst_abspath = Path(self.subproject.local_abspath, dst_relpath)
previous_is_symlink = dst_abspath.is_symlink()
try:
previous_content: Union[bytes, Path]
previous_content: bytes | Path
if previous_is_symlink:
previous_content = readlink(dst_abspath)
else:
Expand Down Expand Up @@ -646,7 +643,7 @@ def _render_folder(self, src_abspath: Path) -> None:
else:
self._render_file(file)

def _render_path(self, relpath: Path) -> Optional[Path]:
def _render_path(self, relpath: Path) -> Path | None:
"""Render one relative path.
Args:
Expand Down Expand Up @@ -1010,7 +1007,7 @@ def _git_initialize_repo(self):
def run_copy(
src_path: str,
dst_path: StrOrPath = ".",
data: Optional[AnyByStrDict] = None,
data: AnyByStrDict | None = None,
**kwargs,
) -> Worker:
"""Copy a template to a destination, from zero.
Expand All @@ -1027,7 +1024,7 @@ def run_copy(


def run_recopy(
dst_path: StrOrPath = ".", data: Optional[AnyByStrDict] = None, **kwargs
dst_path: StrOrPath = ".", data: AnyByStrDict | None = None, **kwargs
) -> Worker:
"""Update a subproject from its template, discarding subproject evolution.
Expand All @@ -1044,7 +1041,7 @@ def run_recopy(

def run_update(
dst_path: StrOrPath = ".",
data: Optional[AnyByStrDict] = None,
data: AnyByStrDict | None = None,
**kwargs,
) -> Worker:
"""Update a subproject, from its template.
Expand Down
9 changes: 5 additions & 4 deletions copier/subproject.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@
A *subproject* is a project that gets rendered and/or updated with Copier.
"""
from __future__ import annotations

from dataclasses import field
from functools import cached_property
from pathlib import Path
from typing import Callable, List, Optional
from typing import Callable

import yaml
from plumbum.machines import local
Expand All @@ -32,7 +33,7 @@ class Subproject:
local_abspath: AbsolutePath
answers_relpath: Path = Path(".copier-answers.yml")

_cleanup_hooks: List[Callable] = field(default_factory=list, init=False)
_cleanup_hooks: list[Callable] = field(default_factory=list, init=False)

def is_dirty(self) -> bool:
"""Indicate if the local template root is dirty.
Expand Down Expand Up @@ -69,7 +70,7 @@ def last_answers(self) -> AnyByStrDict:
}

@cached_property
def template(self) -> Optional[Template]:
def template(self) -> Template | None:
"""Template, as it was used the last time."""
last_url = self.last_answers.get("_src_path")
last_ref = self.last_answers.get("_commit")
Expand All @@ -79,7 +80,7 @@ def template(self) -> Optional[Template]:
return result

@cached_property
def vcs(self) -> Optional[VCSTypes]:
def vcs(self) -> VCSTypes | None:
"""VCS type of the subproject."""
if is_in_git_repo(self.local_abspath):
return "git"
26 changes: 14 additions & 12 deletions copier/template.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
"""Tools related to template management."""
from __future__ import annotations

import re
import sys
from collections import ChainMap, defaultdict
Expand All @@ -7,7 +9,7 @@
from functools import cached_property
from pathlib import Path
from shutil import rmtree
from typing import List, Literal, Mapping, Optional, Sequence, Set, Tuple
from typing import Literal, Mapping, Sequence
from warnings import warn

import dunamai
Expand All @@ -31,7 +33,7 @@
from .vcs import checkout_latest_tag, clone, get_git, get_repo

# Default list of files in the template to exclude from the rendered project
DEFAULT_EXCLUDE: Tuple[str, ...] = (
DEFAULT_EXCLUDE: tuple[str, ...] = (
"copier.yaml",
"copier.yml",
"~*",
Expand All @@ -45,7 +47,7 @@
DEFAULT_TEMPLATES_SUFFIX = ".jinja"


def filter_config(data: AnyByStrDict) -> Tuple[AnyByStrDict, AnyByStrDict]:
def filter_config(data: AnyByStrDict) -> tuple[AnyByStrDict, AnyByStrDict]:
"""Separates config and questions data."""
config_data: AnyByStrDict = {}
questions_data = {}
Expand Down Expand Up @@ -212,7 +214,7 @@ def _cleanup(self) -> None:
onerror=handle_remove_readonly,
)

def _temp_clone(self) -> Optional[Path]:
def _temp_clone(self) -> Path | None:
"""Get the path to the temporary clone of the template.
If the template hasn't yet been cloned, or if it was a local template,
Expand Down Expand Up @@ -297,7 +299,7 @@ def envops(self) -> Mapping:
return result

@cached_property
def exclude(self) -> Tuple[str, ...]:
def exclude(self) -> tuple[str, ...]:
"""Get exclusions specified in the template, or default ones.
See [exclude][].
Expand All @@ -310,7 +312,7 @@ def exclude(self) -> Tuple[str, ...]:
)

@cached_property
def jinja_extensions(self) -> Tuple[str, ...]:
def jinja_extensions(self) -> tuple[str, ...]:
"""Get Jinja2 extensions specified in the template, or `()`.
See [jinja_extensions][].
Expand Down Expand Up @@ -350,7 +352,7 @@ def metadata(self) -> AnyByStrDict:
return result

def migration_tasks(
self, stage: Literal["before", "after"], from_template: "Template"
self, stage: Literal["before", "after"], from_template: Template
) -> Sequence[Task]:
"""Get migration objects that match current version spec.
Expand All @@ -362,7 +364,7 @@ def migration_tasks(
stage: A valid stage name to find tasks for.
from_template: Original template, from which we are migrating.
"""
result: List[Task] = []
result: list[Task] = []
if not (self.version and from_template.version):
return result
extra_env: Env = {
Expand All @@ -386,7 +388,7 @@ def migration_tasks(
return result

@cached_property
def min_copier_version(self) -> Optional[Version]:
def min_copier_version(self) -> Version | None:
"""Get minimal copier version for the template and validates it.
See [min_copier_version][].
Expand All @@ -409,7 +411,7 @@ def questions_data(self) -> AnyByStrDict:
return result

@cached_property
def secret_questions(self) -> Set[str]:
def secret_questions(self) -> set[str]:
"""Get names of secret questions from the template.
These questions shouldn't be saved into the answers file.
Expand Down Expand Up @@ -503,7 +505,7 @@ def url_expanded(self) -> str:
return get_repo(self.url) or self.url

@cached_property
def version(self) -> Optional[Version]:
def version(self) -> Version | None:
"""PEP440-compliant version object."""
if self.vcs != "git" or not self.commit:
return None
Expand Down Expand Up @@ -531,7 +533,7 @@ def version(self) -> Optional[Version]:
return None

@cached_property
def vcs(self) -> Optional[VCSTypes]:
def vcs(self) -> VCSTypes | None:
"""Get VCS system used by the template, if any."""
if get_repo(self.url):
return "git"
13 changes: 7 additions & 6 deletions copier/tools.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Some utility functions."""
from __future__ import annotations

import errno
import os
Expand All @@ -12,7 +13,7 @@
from importlib.metadata import version
from pathlib import Path
from types import TracebackType
from typing import Any, Callable, Literal, Optional, TextIO, Tuple, Type, Union, cast
from typing import Any, Callable, Literal, TextIO, cast

import colorama
from packaging.version import Version
Expand All @@ -36,7 +37,7 @@ class Style:
INDENT = " " * 2
HLINE = "-" * 42

OS: Optional[Literal["linux", "macos", "windows"]] = cast(
OS: Literal["linux", "macos", "windows"] | None = cast(
Any,
{
"Linux": "linux",
Expand All @@ -63,11 +64,11 @@ def copier_version() -> Version:
def printf(
action: str,
msg: Any = "",
style: Optional[IntSeq] = None,
style: IntSeq | None = None,
indent: int = 10,
quiet: Union[bool, StrictBool] = False,
quiet: bool | StrictBool = False,
file_: TextIO = sys.stdout,
) -> Optional[str]:
) -> str | None:
"""Print string with common format."""
if quiet:
return None # HACK: Satisfy MyPy
Expand Down Expand Up @@ -152,7 +153,7 @@ def handle_remove_readonly(
func: Callable,
path: str,
# TODO: Change this union to simply `BaseException` when Python 3.11 support is dropped
exc: Union[BaseException, Tuple[Type[BaseException], BaseException, TracebackType]],
exc: BaseException | tuple[type[BaseException], BaseException, TracebackType],
) -> None:
"""Handle errors when trying to remove read-only files through `shutil.rmtree`.
Expand Down
16 changes: 9 additions & 7 deletions copier/user_data.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
"""Functions used to load user data."""
from __future__ import annotations

import json
import warnings
from collections import ChainMap
Expand All @@ -8,7 +10,7 @@
from hashlib import sha512
from os import urandom
from pathlib import Path
from typing import Any, Callable, Dict, Mapping, Optional, Sequence, Set, Union
from typing import Any, Callable, Mapping, Sequence

import yaml
from jinja2 import UndefinedError
Expand Down Expand Up @@ -84,7 +86,7 @@ class AnswersMap:
"""

# Private
hidden: Set[str] = field(default_factory=set, init=False)
hidden: set[str] = field(default_factory=set, init=False)

# Public
user: AnyByStrDict = field(default_factory=dict)
Expand Down Expand Up @@ -176,16 +178,16 @@ class Question:
var_name: str
answers: AnswersMap
jinja_env: SandboxedEnvironment
choices: Union[Sequence[Any], Dict[Any, Any]] = field(default_factory=list)
choices: Sequence[Any] | dict[Any, Any] = field(default_factory=list)
multiselect: bool = False
default: Any = MISSING
help: str = ""
multiline: Union[str, bool] = False
multiline: str | bool = False
placeholder: str = ""
secret: bool = False
type: str = Field(default="", validate_default=True)
validator: str = ""
when: Union[str, bool] = True
when: str | bool = True

@field_validator("var_name")
@classmethod
Expand Down Expand Up @@ -246,7 +248,7 @@ def get_default(self) -> Any:
result = self.cast_answer(result)
return result

def get_default_rendered(self) -> Union[bool, str, Choice, None, MissingType]:
def get_default_rendered(self) -> bool | str | Choice | None | MissingType:
"""Get default answer rendered for the questionary lib.
The questionary lib expects some specific data types, and returns
Expand Down Expand Up @@ -416,7 +418,7 @@ def get_when(self) -> bool:
return cast_to_bool(self.render_value(self.when))

def render_value(
self, value: Any, extra_answers: Optional[AnyByStrDict] = None
self, value: Any, extra_answers: AnyByStrDict | None = None
) -> str:
"""Render a single templated value using Jinja.
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ style = "pep440"
vcs = "git"

[tool.ruff.lint]
extend-select = ["B", "D", "E", "F", "I", "PGH", "UP"]
extend-select = ["B", "D", "E", "F", "FA", "I", "PGH", "UP"]
extend-ignore = ['B028', "B904", "D105", "D107", "E501"]

[tool.ruff.lint.per-file-ignores]
Expand Down
6 changes: 4 additions & 2 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from __future__ import annotations

import platform
import sys
from typing import Iterator, Optional, Tuple
from typing import Iterator

import pytest
from coverage.tracer import CTracer
Expand All @@ -21,7 +23,7 @@ def spawn() -> Spawn:
"pexpect fails on Windows",
)

def _spawn(cmd: Tuple[str, ...], *, timeout: Optional[int] = None) -> PopenSpawn:
def _spawn(cmd: tuple[str, ...], *, timeout: int | None = None) -> PopenSpawn:
# Disable subprocess timeout if debugging (except coverage), for commodity
# See https://stackoverflow.com/a/67065084/1468388
tracer = getattr(sys, "gettrace", lambda: None)()
Expand Down
Loading

0 comments on commit fa91c72

Please sign in to comment.