Skip to content

Commit

Permalink
refactor: drop support for Python 3.7 (copier-org#1252)
Browse files Browse the repository at this point in the history
  • Loading branch information
sisp authored Jul 18, 2023
1 parent a27366b commit a82c0ad
Show file tree
Hide file tree
Showing 17 changed files with 49 additions and 220 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ jobs:
- macos-latest
- ubuntu-latest
- windows-latest
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
python-version: ["3.8", "3.9", "3.10", "3.11"]
runs-on: ${{ matrix.os }}
steps:
# HACK https://github.com/actions/cache/issues/315
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ A library and CLI app for rendering project templates.

## Installation

1. Install Python 3.7 or newer (3.8 or newer if you're on Windows).
1. Install Python 3.8 or newer.
1. Install Git 2.27 or newer.
1. To use as a CLI app: `pipx install copier`
1. To use as a library: `pip install copier` or `conda install -c conda-forge copier`
Expand Down
44 changes: 14 additions & 30 deletions copier/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,22 @@
from contextlib import suppress
from dataclasses import asdict, field, replace
from filecmp import dircmp
from functools import partial
from functools import cached_property, partial
from itertools import chain
from pathlib import Path
from shutil import rmtree
from typing import Callable, Iterable, Mapping, Optional, Sequence, Set, Union
from tempfile import TemporaryDirectory
from typing import (
Callable,
Iterable,
Literal,
Mapping,
Optional,
Sequence,
Set,
Union,
get_args,
)
from unicodedata import normalize

from jinja2.loaders import FileSystemLoader
Expand All @@ -34,45 +45,18 @@
)
from .subproject import Subproject
from .template import Task, Template
from .tools import OS, Style, TemporaryDirectory, printf, readlink
from .tools import OS, Style, printf, readlink
from .types import (
MISSING,
AnyByStrDict,
JSONSerializable,
Literal,
OptStr,
RelativePath,
StrOrPath,
StrSeq,
)
from .user_data import DEFAULT_DATA, AnswersMap, Question

# HACK https://github.com/python/mypy/issues/8520#issuecomment-772081075
if sys.version_info >= (3, 8):
from functools import cached_property
else:
from backports.cached_property import cached_property

# Backport of `shutil.copytree` for python 3.7 to accept `dirs_exist_ok` argument
if sys.version_info >= (3, 8):
from shutil import copytree
else:
from distutils.dir_util import copy_tree

def copytree(src: Path, dst: Path, dirs_exist_ok: bool = False):
"""Backport of `shutil.copytree` with `dirs_exist_ok` argument.
Can be remove once python 3.7 dropped.
"""
copy_tree(str(src), str(dst))


# HACK https://github.com/python/mypy/issues/8520#issuecomment-772081075
if sys.version_info >= (3, 8):
from typing import get_args
else:
from typing_extensions import get_args


@dataclass(config=ConfigDict(extra="forbid"))
class Worker:
Expand Down
8 changes: 1 addition & 7 deletions copier/subproject.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
A *subproject* is a project that gets rendered and/or updated with Copier.
"""

import sys
from functools import cached_property
from pathlib import Path
from typing import Optional

Expand All @@ -16,12 +16,6 @@
from .types import AbsolutePath, AnyByStrDict, VCSTypes
from .vcs import is_in_git_repo

# HACK https://github.com/python/mypy/issues/8520#issuecomment-772081075
if sys.version_info >= (3, 8):
from functools import cached_property
else:
from backports.cached_property import cached_property


@dataclass
class Subproject:
Expand Down
12 changes: 2 additions & 10 deletions copier/template.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
"""Tools related to template management."""
import re
import sys
from collections import ChainMap, defaultdict
from contextlib import suppress
from dataclasses import field
from functools import cached_property
from pathlib import Path
from shutil import rmtree
from typing import List, Mapping, Optional, Sequence, Set, Tuple
from typing import List, Literal, Mapping, Optional, Sequence, Set, Tuple
from warnings import warn

import dunamai
Expand All @@ -30,14 +30,6 @@
from .types import AnyByStrDict, Env, OptStr, StrSeq, Union, VCSTypes
from .vcs import checkout_latest_tag, clone, get_repo

# HACK https://github.com/python/mypy/issues/8520#issuecomment-772081075
if sys.version_info >= (3, 8):
from functools import cached_property
else:
from backports.cached_property import cached_property

from .types import Literal

# Default list of files in the template to exclude from the rendered project
DEFAULT_EXCLUDE: Tuple[str, ...] = (
"copier.yaml",
Expand Down
48 changes: 6 additions & 42 deletions copier/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,19 @@
import errno
import os
import platform
import shutil
import stat
import sys
import tempfile
import warnings
from contextlib import suppress
from importlib.metadata import version
from pathlib import Path
from types import TracebackType
from typing import Any, Callable, Optional, TextIO, Tuple, Union, cast
from typing import Any, Callable, Literal, Optional, TextIO, Tuple, Union, cast

import colorama
from packaging.version import Version
from pydantic import StrictBool

from .types import IntSeq, Literal

# TODO Remove condition when dropping python 3.8 support
if sys.version_info < (3, 8):
from importlib_metadata import version
else:
from importlib.metadata import version
from .types import IntSeq

colorama.init()

Expand Down Expand Up @@ -142,7 +134,8 @@ def handle_remove_readonly(
) -> 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.
On Windows, `shutil.rmtree` does not handle read-only files very well. This handler
makes sure the given file is writable, then re-execute the given removal function.
Arguments:
func: An OS-dependant function used to remove a file.
Expand All @@ -157,41 +150,12 @@ def handle_remove_readonly(
raise


# See https://github.com/copier-org/copier/issues/345
class TemporaryDirectory(tempfile.TemporaryDirectory):
"""A custom version of `tempfile.TemporaryDirectory` that handles read-only files better.
On Windows, before Python 3.8, `shutil.rmtree` does not handle read-only files very well.
This custom class makes use of a [special error handler][copier.tools.handle_remove_readonly]
to make sure that a temporary directory containing read-only files (typically created
when git-cloning a repository) is properly cleaned-up (i.e. removed) after using it
in a context manager.
"""

@classmethod
def _cleanup(cls, name, warn_message):
cls._robust_cleanup(name)
warnings.warn(warn_message, ResourceWarning)

def cleanup(self):
"""Remove directory safely."""
if self._finalizer.detach():
self._robust_cleanup(self.name)

@staticmethod
def _robust_cleanup(name):
shutil.rmtree(name, ignore_errors=False, onerror=handle_remove_readonly)


def readlink(link: Path) -> Path:
"""A custom version of os.readlink/pathlib.Path.readlink.
pathlib.Path.readlink is what we ideally would want to use, but it is only available on python>=3.9.
os.readlink doesn't support Path and bytes on Windows for python<3.8
"""
if sys.version_info >= (3, 9):
return link.readlink()
elif sys.version_info >= (3, 8) or os.name != "nt":
return Path(os.readlink(link))
else:
return Path(os.readlink(str(link)))
return Path(os.readlink(link))
18 changes: 11 additions & 7 deletions copier/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,20 @@

import sys
from pathlib import Path
from typing import Any, Dict, Mapping, NewType, Optional, Sequence, TypeVar, Union
from typing import (
Any,
Dict,
Literal,
Mapping,
NewType,
Optional,
Sequence,
TypeVar,
Union,
)

from pydantic import AfterValidator

# HACK https://github.com/python/mypy/issues/8520#issuecomment-772081075
if sys.version_info >= (3, 8):
from typing import Literal
else:
from typing_extensions import Literal

if sys.version_info >= (3, 9):
from typing import Annotated
else:
Expand Down
23 changes: 2 additions & 21 deletions copier/user_data.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,14 @@
"""Functions used to load user data."""
import json
import sys
import warnings
from collections import ChainMap
from dataclasses import field
from datetime import datetime
from functools import cached_property
from hashlib import sha512
from os import urandom
from pathlib import Path
from typing import (
TYPE_CHECKING,
Any,
Callable,
Dict,
Mapping,
Optional,
Sequence,
Set,
Union,
)
from typing import Any, Callable, Dict, Mapping, Optional, Sequence, Set, Union

import yaml
from jinja2 import UndefinedError
Expand All @@ -35,15 +25,6 @@
from .tools import cast_str_to_bool, force_str_end
from .types import MISSING, AnyByStrDict, MissingType, OptStr, OptStrOrPath, StrOrPath

# HACK https://github.com/python/mypy/issues/8520#issuecomment-772081075
if sys.version_info >= (3, 8):
from functools import cached_property
else:
from backports.cached_property import cached_property

if TYPE_CHECKING:
pass


# TODO Remove these two functions as well as DEFAULT_DATA in a future release
def _now():
Expand Down
3 changes: 1 addition & 2 deletions copier/vcs.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import sys
from contextlib import suppress
from pathlib import Path
from tempfile import mkdtemp
from tempfile import TemporaryDirectory, mkdtemp
from warnings import warn

from packaging import version
Expand All @@ -13,7 +13,6 @@
from plumbum.cmd import git

from .errors import DirtyLocalWarning, ShallowCloneWarning
from .tools import TemporaryDirectory
from .types import OptBool, OptStr, StrOrPath

GIT_PREFIX = ("git@", "git://", "git+", "https://github.com/", "https://gitlab.com/")
Expand Down
Loading

0 comments on commit a82c0ad

Please sign in to comment.