Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Warn major version difference #359

Merged
merged 1 commit into from
Feb 14, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 26 additions & 8 deletions copier/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,24 @@
from .types import PathSeq


class UserMessageError(Exception):
# Errors
class CopierError(Exception):
"""Base class for all other Copier errors."""


class UserMessageError(CopierError):
"""Exit the program giving a message to the user."""


class UnsupportedVersionError(UserMessageError):
class UnsupportedVersionError(UserMessageError, CopierError):
"""Copier version does not support template version."""


class ConfigFileError(ValueError):
class ConfigFileError(ValueError, CopierError):
"""Parent class defining problems with the config file."""


class InvalidConfigFileError(ConfigFileError):
class InvalidConfigFileError(ConfigFileError, CopierError):
"""Indicates that the config file is wrong."""

def __init__(self, conf_path: Path, quiet: bool):
Expand All @@ -29,7 +34,7 @@ def __init__(self, conf_path: Path, quiet: bool):
super().__init__(msg)


class MultipleConfigFilesError(ConfigFileError):
class MultipleConfigFilesError(ConfigFileError, CopierError):
"""Both copier.yml and copier.yaml found, and that's an error."""

def __init__(self, conf_paths: "PathSeq", quiet: bool):
Expand All @@ -38,19 +43,32 @@ def __init__(self, conf_paths: "PathSeq", quiet: bool):
super().__init__(msg)


class InvalidTypeError(TypeError):
class InvalidTypeError(TypeError, CopierError):
"""The question type is not among the supported ones."""


class PathNotAbsoluteError(_PathValueError):
class PathNotAbsoluteError(_PathValueError, CopierError):
"""The path is not absolute, but it should be."""

code = "path.not_absolute"
msg_template = '"{path}" is not an absolute path'


class PathNotRelativeError(_PathValueError):
class PathNotRelativeError(_PathValueError, CopierError):
"""The path is not relative, but it should be."""

code = "path.not_relative"
msg_template = '"{path}" is not a relative path'


# Warnings
class CopierWarning(Warning):
"""Base class for all other Copier warnings."""


class UnknownCopierVersionWarning(UserWarning, CopierWarning):
"""Cannot determine installed Copier version."""


class OldTemplateWarning(UserWarning, CopierWarning):
"""Template was designed for an older Copier version."""
36 changes: 28 additions & 8 deletions copier/template.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,18 @@
from contextlib import suppress
from pathlib import Path
from typing import List, Mapping, Optional, Sequence, Set, Tuple
from warnings import warn

from packaging import version
from packaging.version import parse
from packaging.version import Version, parse
from plumbum.cmd import git
from plumbum.machines import local
from pydantic.dataclasses import dataclass

from .errors import UnsupportedVersionError
from .errors import (
OldTemplateWarning,
UnknownCopierVersionWarning,
UnsupportedVersionError,
)
from .types import AnyByStrDict, OptStr, StrSeq, VCSTypes
from .user_data import load_config_data
from .vcs import checkout_latest_tag, clone, get_repo
Expand Down Expand Up @@ -53,22 +57,38 @@ def filter_config(data: AnyByStrDict) -> Tuple[AnyByStrDict, AnyByStrDict]:
return conf_data, questions_data


def verify_minimum_version(version_str: str) -> None:
"""Raise an error if the current Copier version is less than the given version."""
def verify_copier_version(version_str: str) -> None:
"""Raise an error if the current Copier version is less than the given version.

Args:
version_str:
Minimal copier version for the template.
"""
# Importing __version__ at the top of the module creates a circular import
# ("cannot import name '__version__' from partially initialized module 'copier'"),
# so instead we do a lazy import here
from . import __version__

# Disable check when running copier as editable installation
if __version__ == "0.0.0":
warn(
"Cannot check Copier version constraint.",
UnknownCopierVersionWarning,
)
return

if version.parse(__version__) < version.parse(version_str):
parsed_installed, parsed_min = map(Version, (__version__, version_str))
if parsed_installed < parsed_min:
raise UnsupportedVersionError(
f"This template requires Copier version >= {version_str}, "
f"while your version of Copier is {__version__}."
)
if parsed_installed.major > parsed_min.major:
warn(
f"This template was designed for Copier {version_str}, "
f"but your version of Copier is {__version__}. "
f"You could find some incompatibilities.",
OldTemplateWarning,
)


@dataclass
Expand Down Expand Up @@ -120,7 +140,7 @@ def _raw_config(self) -> AnyByStrDict:
"""
result = load_config_data(self.local_abspath)
with suppress(KeyError):
verify_minimum_version(result["_min_copier_version"])
verify_copier_version(result["_min_copier_version"])
return result

@cached_property
Expand Down
21 changes: 19 additions & 2 deletions tests/test_minimum_version.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import warnings

import pytest
from plumbum import local
from plumbum.cmd import git

import copier
from copier.errors import UnsupportedVersionError
from copier.errors import (
OldTemplateWarning,
UnknownCopierVersionWarning,
UnsupportedVersionError,
)

from .helpers import build_file_tree

Expand Down Expand Up @@ -68,4 +74,15 @@ def test_minimum_version_update(template_path, tmp_path, monkeypatch):
def test_version_0_0_0_ignored(template_path, tmp_path, monkeypatch):
monkeypatch.setattr("copier.__version__", "0.0.0")
# assert no error
copier.copy(template_path, tmp_path)
with warnings.catch_warnings():
warnings.simplefilter("error")
with pytest.raises(UnknownCopierVersionWarning):
copier.run_copy(template_path, tmp_path)


def test_version_bigger_major_warning(template_path, tmp_path, monkeypatch):
monkeypatch.setattr("copier.__version__", "11.0.0a0")
with warnings.catch_warnings():
warnings.simplefilter("error")
with pytest.raises(OldTemplateWarning):
copier.run_copy(template_path, tmp_path)