diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 45e4951b..069dd109 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -27,6 +27,11 @@ repos: hooks: - id: pyupgrade args: [--py38-plus] +- repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.0.291 + hooks: + - id: ruff + args: [--fix, --exit-non-zero-on-fix] - repo: https://github.com/tox-dev/pyproject-fmt rev: "1.1.0" hooks: diff --git a/MANIFEST.in b/MANIFEST.in index fc1b5325..6b9e3204 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,6 +1,8 @@ exclude *.nix exclude .pre-commit-config.yaml +exclude changelog.d/* exclude .git_archival.txt +exclude .readthedocs.yaml include *.py include testing/*.py include tox.ini @@ -10,5 +12,14 @@ include *.toml include mypy.ini include testing/Dockerfile.* include src/setuptools_scm/.git_archival.txt +include README.md +include CHANGELOG.md + + recursive-include testing *.bash prune nextgen + +recursive-include docs *.md +include docs/examples/version_scheme_code/*.py +include docs/examples/version_scheme_code/*.toml +include mkdocs.yml diff --git a/changelog.d/20230922_220934_opensource_try_ruff.md b/changelog.d/20230922_220934_opensource_try_ruff.md new file mode 100644 index 00000000..e14d49b5 --- /dev/null +++ b/changelog.d/20230922_220934_opensource_try_ruff.md @@ -0,0 +1,4 @@ + +### Changed + +- introduce ruff as a linter \ No newline at end of file diff --git a/hatch.toml b/hatch.toml index b82884f2..aad1b874 100644 --- a/hatch.toml +++ b/hatch.toml @@ -1,3 +1,13 @@ +[envs.test] +extras = ["test", "dev"] + +[envs.test.scripts] +all = "pytest {args}" + +[[env.test.matrix]] +python = ["3.8", "3.9", "3.10", "3.11"] + + [envs.docs] python = "3.11" extras = ["docs"] diff --git a/pyproject.toml b/pyproject.toml index 7a9df4d2..bb86ff48 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,7 +42,7 @@ dependencies = [ "packaging>=20", "setuptools", 'tomli>=1; python_version < "3.11"', - 'typing-extensions; python_version < "3.11"', + "typing-extensions", ] [project.optional-dependencies] docs = [ @@ -108,5 +108,25 @@ version = { attr = "_own_version_helper.version"} [tool.setuptools_scm] +[tool.ruff] +select = ["E", "F", "B", "U", "YTT", "C", "DTZ", "PYI", "PT"] +ignore = ["B028"] + +[tool.pytest.ini_options] +testpaths = ["testing"] +filterwarnings = [ + "error", + "ignore:.*tool\\.setuptools_scm.*", + "ignore:.*git archive did not support describe output.*:UserWarning", +] +log_level = "debug" +log_cli_level = "info" +# disable unraisable until investigated +addopts = ["-p", "no:unraisableexception"] +markers = [ + "issue(id): reference to github issue", + "skip_commit: allows to skip committing in the helpers", +] + [tool.scriv] format = "md" diff --git a/src/setuptools_scm/_config.py b/src/setuptools_scm/_config.py index 6a83491e..aaf0440d 100644 --- a/src/setuptools_scm/_config.py +++ b/src/setuptools_scm/_config.py @@ -65,8 +65,7 @@ def _check_absolute_root(root: _t.PathT, relative_to: _t.PathT | None) -> str: and not os.path.commonpath([root, relative_to]) == root ): warnings.warn( - "absolute root path '%s' overrides relative_to '%s'" - % (root, relative_to) + f"absolute root path '{root}' overrides relative_to '{relative_to}'" ) if os.path.isdir(relative_to): warnings.warn( diff --git a/src/setuptools_scm/_file_finders/__init__.py b/src/setuptools_scm/_file_finders/__init__.py index 90726c45..591aa903 100644 --- a/src/setuptools_scm/_file_finders/__init__.py +++ b/src/setuptools_scm/_file_finders/__init__.py @@ -44,7 +44,7 @@ def scm_find_files( # dirpath with symlinks resolved realdirpath = os.path.normcase(os.path.realpath(dirpath)) - def _link_not_in_scm(n: str) -> bool: + def _link_not_in_scm(n: str, realdirpath: str = realdirpath) -> bool: fn = os.path.join(realdirpath, os.path.normcase(n)) return os.path.islink(fn) and fn not in scm_files diff --git a/src/setuptools_scm/_file_finders/hg.py b/src/setuptools_scm/_file_finders/hg.py index ec8604a6..f87ba066 100644 --- a/src/setuptools_scm/_file_finders/hg.py +++ b/src/setuptools_scm/_file_finders/hg.py @@ -34,7 +34,7 @@ def _hg_ls_files_and_dirs(toplevel: str) -> tuple[set[str], set[str]]: hg_dirs = {toplevel} res = _run(["hg", "files"], cwd=toplevel) if res.returncode: - (), () + return set(), set() for name in res.stdout.splitlines(): name = os.path.normcase(name).replace("/", os.path.sep) fullname = os.path.join(toplevel, name) diff --git a/src/setuptools_scm/_version_cls.py b/src/setuptools_scm/_version_cls.py index 55c00c60..61abad50 100644 --- a/src/setuptools_scm/_version_cls.py +++ b/src/setuptools_scm/_version_cls.py @@ -84,6 +84,8 @@ def _validate_version_cls( try: return cast(Type[_VersionT], import_name(version_cls)) except: # noqa - raise ValueError(f"Unable to import version_cls='{version_cls}'") + raise ValueError( + f"Unable to import version_cls='{version_cls}'" + ) from None else: return version_cls diff --git a/src/setuptools_scm/git.py b/src/setuptools_scm/git.py index e075a3fa..d511961c 100644 --- a/src/setuptools_scm/git.py +++ b/src/setuptools_scm/git.py @@ -8,6 +8,7 @@ import warnings from datetime import date from datetime import datetime +from datetime import timezone from os.path import samefile from pathlib import Path from typing import Callable @@ -268,7 +269,7 @@ def _git_parse_inner( tag=tag, distance=distance, dirty=dirty, node=node, config=config ) branch = wd.get_branch() - node_date = wd.get_head_date() or date.today() + node_date = wd.get_head_date() or datetime.now(timezone.utc).date() return dataclasses.replace(version, branch=branch, node_date=node_date) diff --git a/src/setuptools_scm/version.py b/src/setuptools_scm/version.py index d4097fb2..f43e14b6 100644 --- a/src/setuptools_scm/version.py +++ b/src/setuptools_scm/version.py @@ -240,7 +240,7 @@ def guess_next_simple_semver( try: parts = [int(i) for i in str(version.tag).split(".")[:retain]] except ValueError: - raise ValueError(f"{version} can't be parsed as numeric version") + raise ValueError(f"{version} can't be parsed as numeric version") from None while len(parts) < retain: parts.append(0) if increment: @@ -355,7 +355,11 @@ def guess_next_date_ver( if match is None: tag_date = today else: - tag_date = datetime.strptime(match.group("date"), date_fmt).date() + tag_date = ( + datetime.strptime(match.group("date"), date_fmt) + .replace(tzinfo=timezone.utc) + .date() + ) if tag_date == head_date: patch = "0" if match is None else (match.group("patch") or "0") patch = int(patch) + 1 @@ -363,9 +367,8 @@ def guess_next_date_ver( if tag_date > head_date and match is not None: # warn on future times warnings.warn( - "your previous tag ({}) is ahead your node date ({})".format( - tag_date, head_date - ) + f"your previous tag ({tag_date})" + f" is ahead your node date ({head_date})" ) patch = 0 next_version = "{node_date:{date_fmt}}.{patch}".format( diff --git a/testing/conftest.py b/testing/conftest.py index ecbfb1cf..8baec7cc 100644 --- a/testing/conftest.py +++ b/testing/conftest.py @@ -8,6 +8,7 @@ from typing import Iterator import pytest +from typing_extensions import Self from .wd_wrapper import WorkDir from setuptools_scm._run_cmd import run @@ -46,7 +47,7 @@ class DebugMode(contextlib.AbstractContextManager): # type: ignore[type-arg] def __init__(self) -> None: self.__stack = contextlib.ExitStack() - def __enter__(self) -> DebugMode: + def __enter__(self) -> Self: self.enable() return self @@ -71,14 +72,14 @@ def debug_mode() -> Iterator[DebugMode]: yield debug_mode -@pytest.fixture +@pytest.fixture() def wd(tmp_path: Path) -> WorkDir: target_wd = tmp_path.resolve() / "wd" target_wd.mkdir() return WorkDir(target_wd) -@pytest.fixture +@pytest.fixture() def repositories_hg_git(tmp_path: Path) -> tuple[WorkDir, WorkDir]: tmp_path = tmp_path.resolve() path_git = tmp_path / "repo_git" diff --git a/testing/test_basic_api.py b/testing/test_basic_api.py index 6510e0f1..26f87b13 100644 --- a/testing/test_basic_api.py +++ b/testing/test_basic_api.py @@ -226,14 +226,18 @@ def test_custom_version_cls() -> None: """Test that `normalize` and `version_cls` work as expected""" class MyVersion: - def __init__(self, tag_str: str): + def __init__(self, tag_str: str) -> None: self.version = tag_str def __repr__(self) -> str: return f"hello,{self.version}" # you can not use normalize=False and version_cls at the same time - with pytest.raises(ValueError): + with pytest.raises( + ValueError, + match="Providing a custom `version_cls`" + " is not permitted when `normalize=False`", + ): setuptools_scm.get_version(normalize=False, version_cls=MyVersion) # TODO unfortunately with PRETEND_KEY the preformatted flag becomes True diff --git a/testing/test_config.py b/testing/test_config.py index 7246a79b..668ddd02 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -10,7 +10,7 @@ @pytest.mark.parametrize( - "tag, expected_version", + ("tag", "expected_version"), [ ("apache-arrow-0.9.0", "0.9.0"), ("arrow-0.9.0", "0.9.0"), diff --git a/testing/test_file_finder.py b/testing/test_file_finder.py index 3825500d..21b523a8 100644 --- a/testing/test_file_finder.py +++ b/testing/test_file_finder.py @@ -2,7 +2,6 @@ import os import sys -from typing import Generator from typing import Iterable import pytest @@ -14,8 +13,8 @@ @pytest.fixture(params=["git", "hg"]) def inwd( request: pytest.FixtureRequest, wd: WorkDir, monkeypatch: pytest.MonkeyPatch -) -> Generator[WorkDir, None, None]: - param: str = getattr(request, "param") # todo: fix +) -> WorkDir: + param: str = request.param # type: ignore if param == "git": try: wd("git init") @@ -42,7 +41,7 @@ def inwd( if request.node.get_closest_marker("skip_commit") is None: wd.add_and_commit() monkeypatch.chdir(wd.cwd) - yield wd + return wd def _sep(paths: Iterable[str]) -> set[str]: @@ -198,7 +197,7 @@ def test_symlink_not_in_scm_while_target_is(inwd: WorkDir) -> None: @pytest.mark.issue(587) -@pytest.mark.skip_commit +@pytest.mark.skip_commit() def test_not_commited(inwd: WorkDir) -> None: assert find_files() == [] @@ -212,7 +211,7 @@ def test_unexpanded_git_archival(wd: WorkDir, monkeypatch: pytest.MonkeyPatch) - assert find_files() == [] -@pytest.mark.parametrize("archive_file", (".git_archival.txt", ".hg_archival.txt")) +@pytest.mark.parametrize("archive_file", [".git_archival.txt", ".hg_archival.txt"]) def test_archive( wd: WorkDir, monkeypatch: pytest.MonkeyPatch, archive_file: str ) -> None: diff --git a/testing/test_functions.py b/testing/test_functions.py index afa45d3b..9a4a09b4 100644 --- a/testing/test_functions.py +++ b/testing/test_functions.py @@ -20,7 +20,7 @@ @pytest.mark.parametrize( - "tag, expected", + ("tag", "expected"), [ ("1.1", "1.2"), ("1.2.dev", "1.2"), @@ -48,7 +48,7 @@ def test_next_tag(tag: str, expected: str) -> None: @pytest.mark.parametrize( - "version,version_scheme, local_scheme,expected", + ("version", "version_scheme", "local_scheme", "expected"), [ ("exact", "guess-next-dev", "node-and-date", "1.1"), ("dirty", "guess-next-dev", "node-and-date", "1.2.dev0+d20090213"), @@ -172,7 +172,7 @@ def test_has_command_logs_stderr(caplog: pytest.LogCaptureFixture) -> None: @pytest.mark.parametrize( - "tag, expected_version", + ("tag", "expected_version"), [ ("1.1", "1.1"), ("release-1.1", "1.1"), diff --git a/testing/test_git.py b/testing/test_git.py index 8f7ac9c7..79fede3a 100644 --- a/testing/test_git.py +++ b/testing/test_git.py @@ -50,7 +50,7 @@ def wd(wd: WorkDir, monkeypatch: pytest.MonkeyPatch, debug_mode: DebugMode) -> W @pytest.mark.parametrize( - "given, tag, number, node, dirty", + ("given", "tag", "number", "node", "dirty"), [ ("3.3.1-rc26-0-g9df187b", "3.3.1-rc26", 0, "g9df187b", False), ("17.33.0-rc-17-g38c3047c0", "17.33.0-rc", 17, "g38c3047c0", False), @@ -158,7 +158,8 @@ def test_version_from_git(wd: WorkDir) -> None: assert wd.get_version() == "0.1.dev0+d20090213" parsed = git.parse(str(wd.cwd), Configuration(), git.DEFAULT_DESCRIBE) - assert parsed is not None and parsed.branch in ("master", "main") + assert parsed is not None + assert parsed.branch in ("master", "main") wd.commit_testfile() assert wd.get_version().startswith("0.1.dev1+g") @@ -285,7 +286,8 @@ def test_git_dirty_notag( tag = datetime.now(timezone.utc).date().strftime(".d%Y%m%d") else: tag = ".d20090213" - assert version.startswith("0.1.dev1+g") and version.endswith(tag) + assert version.startswith("0.1.dev1+g") + assert version.endswith(tag) @pytest.mark.issue(193) @@ -300,7 +302,7 @@ def test_git_worktree_support(wd: WorkDir, tmp_path: Path) -> None: assert str(worktree) in res.stdout -@pytest.fixture +@pytest.fixture() def shallow_wd(wd: WorkDir, tmp_path: Path) -> Path: wd.commit_testfile() wd.commit_testfile() @@ -461,7 +463,7 @@ def test_gitdir(monkeypatch: pytest.MonkeyPatch, wd: WorkDir) -> None: def test_git_getdate(wd: WorkDir) -> None: # TODO: case coverage for git wd parse - today = date.today() + today = datetime.now(timezone.utc).date() def parse_date() -> date: parsed = git.parse(os.fspath(wd.cwd), Configuration()) @@ -492,7 +494,7 @@ def test_git_getdate_badgit( assert git_wd.get_head_date() is None -@pytest.fixture +@pytest.fixture() def signed_commit_wd(monkeypatch: pytest.MonkeyPatch, wd: WorkDir) -> WorkDir: if not has_command("gpg", args=["--version"], warn=False): pytest.skip("gpg executable not found") @@ -519,14 +521,14 @@ def signed_commit_wd(monkeypatch: pytest.MonkeyPatch, wd: WorkDir) -> WorkDir: @pytest.mark.issue("https://github.com/pypa/setuptools_scm/issues/548") def test_git_getdate_signed_commit(signed_commit_wd: WorkDir) -> None: - today = date.today() + today = datetime.now(timezone.utc).date() signed_commit_wd.commit_testfile(signed=True) git_wd = git.GitWorkdir(signed_commit_wd.cwd) assert git_wd.get_head_date() == today @pytest.mark.parametrize( - "expected, from_data", + ("expected", "from_data"), [ ( "1.0", diff --git a/testing/test_integration.py b/testing/test_integration.py index 53035a5c..bcdd6184 100644 --- a/testing/test_integration.py +++ b/testing/test_integration.py @@ -18,7 +18,7 @@ c = Configuration() -@pytest.fixture +@pytest.fixture() def wd(wd: WorkDir) -> WorkDir: wd("git init") wd("git config user.email test@example.com") diff --git a/testing/test_main.py b/testing/test_main.py index 10d78c8e..cf760a6e 100644 --- a/testing/test_main.py +++ b/testing/test_main.py @@ -19,7 +19,7 @@ def test_main() -> None: exec(code, ns) -@pytest.fixture +@pytest.fixture() def repo(wd: WorkDir) -> WorkDir: wd("git init") wd("git config user.email user@host") diff --git a/testing/test_mercurial.py b/testing/test_mercurial.py index 1ca35be3..3aa00973 100644 --- a/testing/test_mercurial.py +++ b/testing/test_mercurial.py @@ -20,7 +20,7 @@ ) -@pytest.fixture +@pytest.fixture() def wd(wd: WorkDir) -> WorkDir: wd("hg init") wd.add_command = "hg add ." @@ -41,7 +41,7 @@ def wd(wd: WorkDir) -> WorkDir: } -@pytest.mark.parametrize("expected,data", sorted(archival_mapping.items())) +@pytest.mark.parametrize(("expected", "data"), sorted(archival_mapping.items())) def test_archival_to_version(expected: str, data: dict[str, str]) -> None: config = Configuration( version_scheme="guess-next-dev", local_scheme="node-and-date" @@ -136,7 +136,7 @@ def test_parse_no_worktree(tmp_path: Path) -> None: assert ret is None -@pytest.fixture +@pytest.fixture() def version_1_0(wd: WorkDir) -> WorkDir: wd("hg branch default") wd.commit_testfile() @@ -144,7 +144,7 @@ def version_1_0(wd: WorkDir) -> WorkDir: return wd -@pytest.fixture +@pytest.fixture() def pre_merge_commit_after_tag(version_1_0: WorkDir) -> WorkDir: wd = version_1_0 wd("hg branch testbranch") diff --git a/testing/test_version.py b/testing/test_version.py index 54f397f9..ea4c7d99 100644 --- a/testing/test_version.py +++ b/testing/test_version.py @@ -25,7 +25,7 @@ @pytest.mark.parametrize( - "version, expected_next", + ("version", "expected_next"), [ pytest.param(meta("1.0.0", config=c), "1.0.0", id="exact"), pytest.param(meta("1.0", config=c), "1.0.0", id="short_tag"), @@ -70,7 +70,7 @@ def test_next_semver_bad_tag() -> None: @pytest.mark.parametrize( - "version, expected_next", + ("version", "expected_next"), [ pytest.param(meta("1.0.0", config=c), "1.0.0", id="exact"), pytest.param( @@ -115,7 +115,7 @@ def m(tag: str, **kw: Any) -> ScmVersion: @pytest.mark.parametrize( - "version, expected_next", + ("version", "expected_next"), [ pytest.param( m("1.0.0", distance=2), @@ -143,7 +143,7 @@ def test_no_guess_version(version: ScmVersion, expected_next: str) -> None: @pytest.mark.parametrize( - "version, match", + ("version", "match"), [ ("1.0.dev1", "choosing custom numbers for the `.devX` distance"), ("1.0.post1", "already is a post release"), @@ -159,19 +159,19 @@ def test_bump_dev_version_zero() -> None: def test_bump_dev_version_nonzero_raises() -> None: - with pytest.raises(ValueError) as excinfo: - guess_next_version(m("1.0.dev1")) - - assert str(excinfo.value) == ( + match = ( "choosing custom numbers for the `.devX` distance " "is not supported.\n " "The 1.0.dev1 can't be bumped\n" "Please drop the tag or create a new supported one ending in .dev0" ) + with pytest.raises(ValueError, match=match): + guess_next_version(m("1.0.dev1")) + @pytest.mark.parametrize( - "tag, expected", + ("tag", "expected"), [ ("v1.0.0", "1.0.0"), ("v1.0.0-rc.1", "1.0.0rc1"), @@ -194,7 +194,7 @@ def test_version_bump_bad() -> None: class YikesVersion: val: str - def __init__(self, val: str): + def __init__(self, val: str) -> None: self.val = val def __str__(self) -> str: @@ -254,7 +254,7 @@ def date_to_str( @pytest.mark.parametrize( - "version, expected_next", + ("version", "expected_next"), [ pytest.param( meta(date_to_str(days_offset=3), config=c_non_normalize), @@ -346,7 +346,7 @@ def test_calver_by_date(version: ScmVersion, expected_next: str) -> None: @pytest.mark.parametrize( - "version, expected_next", + ("version", "expected_next"), [ pytest.param(meta("1.0.0", config=c), "1.0.0", id="SemVer exact stays"), pytest.param( @@ -370,7 +370,7 @@ def test_calver_by_date_future_warning() -> None: @pytest.mark.parametrize( - ["tag", "node_date", "expected"], + ("tag", "node_date", "expected"), [ pytest.param("20.03.03", date(2020, 3, 4), "20.03.04.0", id="next day"), pytest.param("20.03.03", date(2020, 3, 3), "20.03.03.1", id="same day"), @@ -396,7 +396,7 @@ def test_custom_version_cls() -> None: """Test that we can pass our own version class instead of pkg_resources""" class MyVersion: - def __init__(self, tag_str: str): + def __init__(self, tag_str: str) -> None: self.tag = tag_str def __str__(self) -> str: diff --git a/tox.ini b/tox.ini index d52660a9..5079a2d5 100644 --- a/tox.ini +++ b/tox.ini @@ -1,26 +1,12 @@ [tox] envlist=py{38,39,310,311},check_readme,check-dist - -[pytest] -testpaths=testing -filterwarnings= - error - ignore:.*tool\.setuptools_scm.* - ignore:.*git archive did not support describe output.*:UserWarning -log_level = debug -log_cli_level = info -markers= - issue(id): reference to github issue - skip_commit: allows to skip committing in the helpers -# disable unraisable until investigated -addopts = -p no:unraisableexception +requires= tox>4 [flake8] max-complexity = 10 max-line-length = 88 ignore=E203,W503 - [testenv] usedevelop=True deps= @@ -29,7 +15,7 @@ deps= virtualenv>20 rich commands= - pytest [] + pytest {posargs} @@ -39,11 +25,10 @@ deps= check-manifest docutils pygments - typing_extensions; python_version<'3.8' + typing_extensions hatchling rich commands= - rst2html.py README.rst {envlogdir}/README.html --strict [] check-manifest --no-build-isolation [testenv:check_dist]