Skip to content

Commit 31e32e7

Browse files
authored
build(deps-dev): switch to UV and TY (#358)
* build(deps): switch to uv and ty * build(conda): remove conda * style: fix lint * style: add proper IDE tooling * ci(github): update all workflows * docs(readme): update readme * docs(installation): remove conda * style: fix format * test(pyproject): fix test paths * style: fix lint & format * ci(github): update venv activation * ci(github): update venv config * ci(github): update python versions * ci(docs): update torch backend for documentation builds * fix(methods): fix extra_repr spelling * build(deps-dev): bump ty * ci(github): fix UV torch backend * style(activation): fix typing * build(deps-test): bump pytest version * ci(github): fix coverage report upload * ci(github): remove windows builds * ci(github): update codecov upload
1 parent f2a8379 commit 31e32e7

40 files changed

+804
-771
lines changed

.conda/meta.yaml

Lines changed: 0 additions & 46 deletions
This file was deleted.
File renamed without changes.

.github/ISSUE_TEMPLATE/config.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
blank_issues_enabled: true
22
contact_links:
3+
- name: Documentation
4+
url: https://frgfm.github.io/torch-cam/
5+
about: Please consult the documentation before creating an issue.
36
- name: Usage questions
47
url: https://github.com/frgfm/torch-cam/discussions
58
about: Ask questions and discuss with other TorchCAM community members

.github/SECURITY.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Reporting security issues
2+
3+
If you believe you have found a security vulnerability in TorchCAM, we encourage you to let us know right away. We will investigate all legitimate reports and do our best to quickly fix the problem.
4+
5+
Please report security issues using https://github.com/frgfm/torch-cam/security/advisories/new

.github/collect_env.py

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,10 @@
99
Run it with `python collect_env.py`.
1010
"""
1111

12-
from __future__ import absolute_import, division, print_function, unicode_literals
13-
1412
import locale
1513
import os
1614
import re
17-
import subprocess # noqa S404
15+
import subprocess # noqa: S404
1816
import sys
1917
from pathlib import Path
2018
from typing import NamedTuple
@@ -120,7 +118,7 @@ def get_cudnn_version(run_lambda):
120118
cudnn_cmd = 'ldconfig -p | grep libcudnn | rev | cut -d" " -f1 | rev'
121119
rc, out, _ = run_lambda(cudnn_cmd)
122120
# find will return 1 if there are permission errors or if not found
123-
if len(out) == 0 or rc not in (1, 0):
121+
if len(out) == 0 or rc not in {1, 0}:
124122
lib = os.environ.get("CUDNN_LIBRARY")
125123
if lib is not None and Path(lib).is_file():
126124
return os.path.realpath(lib)
@@ -137,7 +135,7 @@ def get_cudnn_version(run_lambda):
137135
if len(files) == 1:
138136
return files[0]
139137
result = "\n".join(files)
140-
return "Probably one of the following:\n{}".format(result)
138+
return f"Probably one of the following:\n{result}"
141139

142140

143141
def get_nvidia_smi():
@@ -151,7 +149,7 @@ def get_nvidia_smi():
151149
smis = [new_path, legacy_path]
152150
for candidate_smi in smis:
153151
if Path(candidate_smi).exists():
154-
smi = '"{}"'.format(candidate_smi)
152+
smi = f'"{candidate_smi}"'
155153
break
156154
return smi
157155

@@ -187,14 +185,14 @@ def check_release_file(run_lambda):
187185
def get_os(run_lambda):
188186
platform = get_platform()
189187

190-
if platform in ("win32", "cygwin"):
188+
if platform in {"win32", "cygwin"}:
191189
return get_windows_version(run_lambda)
192190

193191
if platform == "darwin":
194192
version = get_mac_version(run_lambda)
195193
if version is None:
196194
return None
197-
return "Mac OSX {}".format(version)
195+
return f"Mac OSX {version}"
198196

199197
if platform == "linux":
200198
# Ubuntu/Debian based
@@ -271,7 +269,7 @@ def replace_bools(dct, true="Yes", false="No"):
271269
def maybe_start_on_next_line(string):
272270
# If `string` is multiline, prepend a \n to it.
273271
if string is not None and len(string.split("\n")) > 1:
274-
return "\n{}\n".format(string)
272+
return f"\n{string}\n"
275273
return string
276274

277275
mutable_dict = envinfo._asdict()

.github/dependabot.yml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,16 @@ updates:
88
- package-ecosystem: "github-actions"
99
directory: "/"
1010
schedule:
11-
interval: "weekly"
11+
interval: "daily"
1212
- package-ecosystem: "pip"
1313
directory: "/"
1414
schedule:
1515
interval: "daily"
1616
allow:
17+
# Safe updates
1718
- dependency-name: "ruff"
18-
- dependency-name: "mypy"
19+
- dependency-name: "ty"
1920
- dependency-name: "pre-commit"
21+
- dependency-name: "pytest"
22+
- dependency-name: "pytest-cov"
23+
- dependency-name: "pytest-pretty"

.github/verify_deps_sync.py

Lines changed: 73 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -3,75 +3,115 @@
33
# This program is licensed under the Apache License 2.0.
44
# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0> for full license details.
55

6-
from pathlib import Path
76

7+
# /// script
8+
# requires-python = ">=3.11"
9+
# dependencies = [
10+
# "pyyaml>=6.0",
11+
# ]
12+
# ///
13+
14+
import logging
15+
import re
16+
import sys
817
import tomllib
18+
from pathlib import Path
19+
920
import yaml
1021

11-
PRECOMMIT_PATH = ".pre-commit-config.yaml"
12-
PYPROJECT_PATH = "pyproject.toml"
22+
DOCKERFILES = []
23+
PRECOMMIT_CONFIG = ".pre-commit-config.yaml"
24+
PYPROJECTS = ["./pyproject.toml"]
25+
26+
logger = logging.getLogger(__name__)
27+
logger.setLevel(logging.DEBUG)
28+
stream_handler = logging.StreamHandler(sys.stdout)
29+
log_formatter = logging.Formatter("%(levelname)s: %(message)s")
30+
stream_handler.setFormatter(log_formatter)
31+
logger.addHandler(stream_handler)
32+
1333

34+
def parse_dep_str(dep_str: str) -> dict[str, str]:
35+
# No version specified
36+
if all(dep_str.find(char) == -1 for char in ("<", ">", "=")):
37+
version_idx = len(dep_str)
38+
else:
39+
version_idx = min(idx for idx in (dep_str.find(char) for char in ("<", ">", "=")) if idx != -1)
40+
pkg_idx = dep_str.find("[")
41+
extra_idx = dep_str.find("]")
42+
return {
43+
"pkg": dep_str[: pkg_idx if pkg_idx != -1 else version_idx],
44+
"version": dep_str[version_idx:],
45+
"extras": [] if extra_idx == -1 else dep_str[pkg_idx + 1 : extra_idx].split(","),
46+
}
1447

15-
def main():
48+
49+
def main(): # noqa: PLR0912
1650
# Retrieve & parse all deps files
17-
deps_dict = {}
18-
# UV: Dockerfile, precommit, .github
51+
deps_dict = {
52+
"uv": [],
53+
"ruff": [],
54+
"ty": [],
55+
"pre-commit": [],
56+
}
57+
# Parse dockerfiles
58+
for dockerfile in DOCKERFILES:
59+
dockerfile_content_ = Path(dockerfile).read_text(encoding="utf-8")
60+
uv_version = re.search(r"ghcr\.io/astral-sh/uv:(\d+\.\d+\.\d+)", dockerfile_content_).group(1) # ty: ignore[possibly-unbound-attribute]
61+
deps_dict["uv"].append({"file": dockerfile, "version": f"=={uv_version}"})
1962
# Parse precommit
20-
with Path(PRECOMMIT_PATH).open("r") as f:
63+
with Path(PRECOMMIT_CONFIG).open("r", encoding="utf-8") as f:
2164
precommit = yaml.safe_load(f)
22-
2365
for repo in precommit["repos"]:
2466
if repo["repo"] == "https://github.com/astral-sh/uv-pre-commit":
25-
if "uv" not in deps_dict:
26-
deps_dict["uv"] = []
27-
deps_dict["uv"].append({"file": PRECOMMIT_PATH, "version": repo["rev"].lstrip("v")})
67+
deps_dict["uv"].append({"file": PRECOMMIT_CONFIG, "version": f"=={repo['rev'].lstrip('v')}"})
2868
elif repo["repo"] == "https://github.com/charliermarsh/ruff-pre-commit":
29-
if "ruff" not in deps_dict:
30-
deps_dict["ruff"] = []
31-
deps_dict["ruff"].append({"file": PRECOMMIT_PATH, "version": repo["rev"].lstrip("v")})
32-
69+
deps_dict["ruff"].append({"file": PRECOMMIT_CONFIG, "version": f"=={repo['rev'].lstrip('v')}"})
3370
# Parse pyproject.toml
34-
with Path(PYPROJECT_PATH).open("rb") as f:
35-
pyproject = tomllib.load(f)
36-
37-
dev_deps = pyproject["project"]["optional-dependencies"]["quality"]
38-
for dep in dev_deps:
39-
if dep.startswith("ruff=="):
40-
if "ruff" not in deps_dict:
41-
deps_dict["ruff"] = []
42-
deps_dict["ruff"].append({"file": PYPROJECT_PATH, "version": dep.split("==")[1]})
43-
elif dep.startswith("mypy=="):
44-
if "mypy" not in deps_dict:
45-
deps_dict["mypy"] = []
46-
deps_dict["mypy"].append({"file": PYPROJECT_PATH, "version": dep.split("==")[1]})
71+
for pyproject_path in PYPROJECTS:
72+
with Path(pyproject_path).open("rb") as f:
73+
pyproject = tomllib.load(f)
74+
75+
# Parse dependencies
76+
core_deps = [parse_dep_str(dep) for dep in pyproject["project"]["dependencies"]]
77+
core_deps = {dep["pkg"]: dep for dep in core_deps}
78+
for dep in deps_dict: # noqa: PLC0206
79+
if dep in core_deps:
80+
deps_dict[dep].append({"file": pyproject_path, "version": core_deps[dep]["version"]})
81+
82+
# Parse optional dependencies
83+
quality_deps = [parse_dep_str(dep) for dep in pyproject["project"]["optional-dependencies"]["quality"]]
84+
quality_deps = {dep["pkg"]: dep for dep in quality_deps}
85+
for dep in deps_dict: # noqa: PLC0206
86+
if dep in quality_deps:
87+
deps_dict[dep].append({"file": pyproject_path, "version": quality_deps[dep]["version"]})
4788

4889
# Parse github/workflows/...
4990
for workflow_file in Path(".github/workflows").glob("*.yml"):
5091
with workflow_file.open("r") as f:
5192
workflow = yaml.safe_load(f)
5293
if "env" in workflow and "UV_VERSION" in workflow["env"]:
53-
if "uv" not in deps_dict:
54-
deps_dict["uv"] = []
5594
deps_dict["uv"].append({
5695
"file": str(workflow_file),
57-
"version": workflow["env"]["UV_VERSION"].lstrip("v"),
96+
"version": f"=={workflow['env']['UV_VERSION'].lstrip('v')}",
5897
})
5998

6099
# Assert all deps are in sync
61100
troubles = []
62101
for dep, versions in deps_dict.items():
63102
versions_ = {v["version"] for v in versions}
64-
if len(versions_) != 1:
103+
if len(versions_) > 1:
65104
inv_dict = {v: set() for v in versions_}
66105
for version in versions:
67106
inv_dict[version["version"]].add(version["file"])
68107
troubles.extend([
69-
f"{dep}:",
108+
f"\033[31m{dep}\033[0m:",
70109
"\n".join(f"- '{v}': {', '.join(files)}" for v, files in inv_dict.items()),
71110
])
72111

73112
if len(troubles) > 0:
74113
raise AssertionError("Some dependencies are out of sync:\n\n" + "\n".join(troubles))
114+
logger.info("\033[32mAll dependencies are in sync!\033[0m")
75115

76116

77117
if __name__ == "__main__":

.github/verify_labels.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
with no labeling responsibility, so we don't want to bother them.
1313
"""
1414

15-
from typing import Any, Set, Tuple
15+
from argparse import ArgumentDefaultsHelpFormatter, ArgumentParser
16+
from typing import Any
1617

1718
import requests
1819

@@ -54,7 +55,7 @@ def query_repo(cmd: str, *, accept) -> Any:
5455
return response.json()
5556

5657

57-
def get_pr_merger_and_labels(pr_number: int) -> Tuple[str, Set[str]]:
58+
def get_pr_merger_and_labels(pr_number: int) -> tuple[str, set[str]]:
5859
# See https://docs.github.com/en/rest/reference/pulls#get-a-pull-request
5960
data = query_repo(f"pulls/{pr_number}", accept="application/vnd.github.v3+json")
6061
merger = data.get("merged_by", {}).get("login")
@@ -70,11 +71,9 @@ def main(args):
7071

7172

7273
def parse_args():
73-
import argparse
74-
75-
parser = argparse.ArgumentParser(
74+
parser = ArgumentParser(
7675
description="PR label checker",
77-
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
76+
formatter_class=ArgumentDefaultsHelpFormatter,
7877
)
7978

8079
parser.add_argument("pr", type=int, help="PR number")

0 commit comments

Comments
 (0)