diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 1a88afeba..ac1267791 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -22,8 +22,13 @@ repos:
- id: rst-backticks
- id: rst-directive-colons
- id: rst-inline-touching-normal
+ - repo: https://github.com/asottile/pyupgrade
+ rev: v3.19.0
+ hooks:
+ - id: pyupgrade
+ args: [--py39-plus]
- repo: https://github.com/astral-sh/ruff-pre-commit
- rev: v0.7.3
+ rev: v0.8.2
hooks:
- id: ruff
args: [ --fix ]
diff --git a/docs/source/tools/mermaid.py b/docs/source/tools/mermaid.py
index a96e92854..a4da80188 100644
--- a/docs/source/tools/mermaid.py
+++ b/docs/source/tools/mermaid.py
@@ -25,7 +25,6 @@ import sphinx
from docutils import nodes
from docutils.parsers.rst import Directive, directives
from docutils.statemachine import ViewList
-from six import text_type
from sphinx.application import Sphinx
from sphinx.errors import SphinxError
from sphinx.locale import _
@@ -96,7 +95,7 @@ class Mermaid(Directive):
try:
with codecs.open(filename, "r", "utf-8") as fp:
mmcode = fp.read()
- except (IOError, OSError): # noqa
+ except OSError: # noqa
return [
document.reporter.warning(
"External Mermaid file %r not found or reading " "it failed" % filename,
@@ -144,8 +143,8 @@ def render_mm(self, code, options, fmt, prefix="mermaid"):
"utf-8"
)
- basename = "%s-%s" % (prefix, sha1(hashkey).hexdigest())
- fname = "%s.%s" % (basename, fmt)
+ basename = f"{prefix}-{sha1(hashkey).hexdigest()}"
+ fname = f"{basename}.{fmt}"
relfn = posixpath.join(self.builder.imgpath, fname)
outdir = os.path.join(self.builder.outdir, self.builder.imagedir)
outfn = os.path.join(outdir, fname)
@@ -157,7 +156,7 @@ def render_mm(self, code, options, fmt, prefix="mermaid"):
ensuredir(os.path.dirname(outfn))
# mermaid expects UTF-8 by default
- if isinstance(code, text_type):
+ if isinstance(code, str):
code = code.encode("utf-8")
with open(tmpfn, "wb") as t:
@@ -235,8 +234,8 @@ def render_mm_html(self, node, code, options, prefix="mermaid", imgcls=None, alt
alt = node.get("alt", self.encode(code).strip())
imgcss = imgcls and 'class="%s"' % imgcls or ""
if fmt == "svg":
- svgtag = """\n""" % (
+ svgtag = """\n""".format(
fname,
alt,
)
@@ -244,10 +243,10 @@ def render_mm_html(self, node, code, options, prefix="mermaid", imgcls=None, alt
else:
if "align" in node:
self.body.append(
- '
' % (node["align"], node["align"])
+ '
'.format(node["align"], node["align"])
)
- self.body.append('

\n' % (fname, alt, imgcss))
+ self.body.append(f'

\n')
if "align" in node:
self.body.append("
\n")
@@ -310,9 +309,7 @@ def render_mm_latex(self, node, code, options, prefix="mermaid"):
elif node["align"] == "right":
self.body.append("{\\hspace*{\\fill}")
post = "}"
- self.body.append(
- "%s\\sphinxincludegraphics{%s}%s" % (para_separator, fname, para_separator)
- )
+ self.body.append(f"{para_separator}\\sphinxincludegraphics{{{fname}}}{para_separator}")
if post:
self.body.append(post)
@@ -356,7 +353,7 @@ def man_visit_mermaid(self, node):
def config_inited(app, config):
version = config.mermaid_version
- mermaid_js_url = "https://unpkg.com/mermaid@{}/dist/mermaid.min.js".format(version)
+ mermaid_js_url = f"https://unpkg.com/mermaid@{version}/dist/mermaid.min.js"
app.add_js_file(mermaid_js_url)
app.add_js_file(
None,
diff --git a/docs/source/tools/mermaid_inheritance.py b/docs/source/tools/mermaid_inheritance.py
index 8d42f5f16..d8e519bba 100644
--- a/docs/source/tools/mermaid_inheritance.py
+++ b/docs/source/tools/mermaid_inheritance.py
@@ -29,7 +29,8 @@ r"""
:license: BSD, see LICENSE for details.
"""
-from typing import Any, Dict, Iterable, List, cast
+from typing import Any, cast
+from collections.abc import Iterable
import sphinx
from docutils import nodes
@@ -80,22 +81,22 @@ class MermaidGraph(InheritanceGraph):
# 'style': '"setlinewidth(0.5)"',
# }
- def _format_node_attrs(self, attrs: Dict) -> str:
+ def _format_node_attrs(self, attrs: dict) -> str:
# return ','.join(['%s=%s' % x for x in sorted(attrs.items())])
return ""
- def _format_graph_attrs(self, attrs: Dict) -> str:
+ def _format_graph_attrs(self, attrs: dict) -> str:
# return ''.join(['%s=%s;\n' % x for x in sorted(attrs.items())])
return ""
def generate_dot(
self,
name: str,
- urls: Dict = {}, # noqa
+ urls: dict = {}, # noqa
env: BuildEnvironment = None,
- graph_attrs: Dict = {}, # noqa
- node_attrs: Dict = {}, # noqa
- edge_attrs: Dict = {}, # noqa
+ graph_attrs: dict = {}, # noqa
+ node_attrs: dict = {}, # noqa
+ edge_attrs: dict = {}, # noqa
) -> str:
"""Generate a mermaid graph from the classes that were passed in
to __init__.
@@ -120,17 +121,17 @@ class MermaidGraph(InheritanceGraph):
res.append("classDiagram\n")
for name, fullname, bases, tooltip in sorted(self.class_info):
# Write the node
- res.append(" class {!s}\n".format(name))
+ res.append(f" class {name!s}\n")
if fullname in urls:
res.append(
' link {!s} "./{!s}" {!s}\n'.format(
- name, urls[fullname], tooltip or '"{}"'.format(name)
+ name, urls[fullname], tooltip or f'"{name}"'
)
)
# Write the edges
for base_name in bases:
- res.append(" {!s} <|-- {!s}\n".format(base_name, name))
+ res.append(f" {base_name!s} <|-- {name!s}\n")
return "".join(res)
@@ -159,7 +160,7 @@ class MermaidDiagram(InheritanceDiagram):
"top-classes": directives.unchanged_required,
}
- def run(self) -> List[Node]:
+ def run(self) -> list[Node]:
node = mermaid_inheritance()
node.document = self.state.document
class_names = self.arguments[0].split()
@@ -283,7 +284,7 @@ def texinfo_visit_mermaid_inheritance(self: TexinfoTranslator, node: inheritance
raise nodes.SkipNode
-def setup(app: Sphinx) -> Dict[str, Any]:
+def setup(app: Sphinx) -> dict[str, Any]:
app.setup_extension("mermaid")
app.add_node(
mermaid_inheritance,
diff --git a/libmambapy/src/libmambapy/version.py b/libmambapy/src/libmambapy/version.py
index d6f8f065d..ec5fea11a 100644
--- a/libmambapy/src/libmambapy/version.py
+++ b/libmambapy/src/libmambapy/version.py
@@ -2,4 +2,4 @@ version_info = ("2", "0", "5")
version_prerelease = "dev0"
__version__ = ".".join(map(str, version_info))
if version_prerelease != "":
- __version__ = "{}.{}".format(__version__, version_prerelease)
+ __version__ = f"{__version__}.{version_prerelease}"
diff --git a/micromamba/test-server/reposerver.py b/micromamba/test-server/reposerver.py
index 593c6f93d..65a5bbfec 100644
--- a/micromamba/test-server/reposerver.py
+++ b/micromamba/test-server/reposerver.py
@@ -7,7 +7,6 @@ import shutil
import sys
from http.server import HTTPServer, SimpleHTTPRequestHandler
from pathlib import Path
-from typing import Dict, List
try:
import conda_content_trust.authentication as cct_authentication
@@ -34,7 +33,7 @@ def get_fingerprint(gpg_output: str) -> str:
return fpline
-KeySet = Dict[str, List[Dict[str, str]]]
+KeySet = dict[str, list[dict[str, str]]]
def normalize_keys(keys: KeySet) -> KeySet:
diff --git a/micromamba/tests/conftest.py b/micromamba/tests/conftest.py
index 8bc497566..40eabb892 100644
--- a/micromamba/tests/conftest.py
+++ b/micromamba/tests/conftest.py
@@ -2,7 +2,8 @@ import copy
import os
import pathlib
import platform
-from typing import Any, Generator, Mapping, Optional
+from typing import Any, Optional
+from collections.abc import Generator, Mapping
import pytest
diff --git a/micromamba/tests/helpers.py b/micromamba/tests/helpers.py
index 708dbd8ce..ab3662c6a 100644
--- a/micromamba/tests/helpers.py
+++ b/micromamba/tests/helpers.py
@@ -16,9 +16,7 @@ import yaml
def subprocess_run(*args: str, **kwargs) -> str:
"""Execute a command in a subprocess while properly capturing stderr in exceptions."""
try:
- p = subprocess.run(
- args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True, **kwargs
- )
+ p = subprocess.run(args, capture_output=True, check=True, **kwargs)
except subprocess.CalledProcessError as e:
print(f"Command {args} failed with stderr: {e.stderr.decode()}")
print(f"Command {args} failed with stdout: {e.stdout.decode()}")
@@ -382,7 +380,7 @@ def read_windows_registry(target_path): # pragma: no cover
try:
key = winreg.OpenKey(main_key, subkey_str, 0, winreg.KEY_READ)
- except EnvironmentError as e:
+ except OSError as e:
if e.errno != errno.ENOENT:
raise
return None, None
@@ -410,7 +408,7 @@ def write_windows_registry(target_path, value_value, value_type): # pragma: no
main_key = getattr(winreg, main_key)
try:
key = winreg.OpenKey(main_key, subkey_str, 0, winreg.KEY_WRITE)
- except EnvironmentError as e:
+ except OSError as e:
if e.errno != errno.ENOENT:
raise
key = winreg.CreateKey(main_key, subkey_str)
diff --git a/micromamba/tests/test_activation.py b/micromamba/tests/test_activation.py
index bbf5b8c9b..c556b8d74 100644
--- a/micromamba/tests/test_activation.py
+++ b/micromamba/tests/test_activation.py
@@ -46,7 +46,7 @@ class WindowsProfiles:
"-Command",
"$PROFILE.CurrentUserAllHosts",
]
- res = subprocess.run(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True)
+ res = subprocess.run(args, capture_output=True, check=True)
return res.stdout.decode("utf-8").strip()
elif shell == "cmd.exe":
return None
@@ -185,8 +185,7 @@ def call_interpreter(s, tmp_path, interpreter, interactive=False, env=None):
try:
res = subprocess.run(
args,
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE,
+ capture_output=True,
check=True,
env=env,
encoding="utf-8",
@@ -273,7 +272,7 @@ def shvar(v, interpreter):
def env_to_dict(out, interpreter="bash"):
if interpreter == "cmd.exe":
- with open(out, "r") as f:
+ with open(out) as f:
out = f.read()
if interpreter == "fish":
diff --git a/micromamba/tests/test_env.py b/micromamba/tests/test_env.py
index e6785407d..cba0ed729 100644
--- a/micromamba/tests/test_env.py
+++ b/micromamba/tests/test_env.py
@@ -159,7 +159,7 @@ def test_env_remove(tmp_home, tmp_root_prefix):
env_json = helpers.run_env("list", "--json")
assert str(env_fp) in env_json["envs"]
assert env_fp.exists()
- with open(conda_env_file, "r", encoding="utf-8") as f:
+ with open(conda_env_file, encoding="utf-8") as f:
lines = [line.strip() for line in f]
assert str(env_fp) in lines
@@ -168,7 +168,7 @@ def test_env_remove(tmp_home, tmp_root_prefix):
env_json = helpers.run_env("list", "--json")
assert str(env_fp) not in env_json["envs"]
assert not env_fp.exists()
- with open(conda_env_file, "r", encoding="utf-8") as f:
+ with open(conda_env_file, encoding="utf-8") as f:
lines = [line.strip() for line in f]
assert str(env_fp) not in lines
diff --git a/micromamba/tests/test_package.py b/micromamba/tests/test_package.py
index 5d83bb9d5..4ad6796c3 100644
--- a/micromamba/tests/test_package.py
+++ b/micromamba/tests/test_package.py
@@ -52,8 +52,8 @@ def test_extract(cph_test_file: Path, tmp_path: Path):
dest_dir=str(tmp_path / "cph" / "cph_test_data-0.0.1-0"),
)
- conda = set((p.relative_to(tmp_path / "cph") for p in (tmp_path / "cph").rglob("**/*")))
- mamba = set((p.relative_to(tmp_path / "mm") for p in (tmp_path / "mm").rglob("**/*")))
+ conda = {p.relative_to(tmp_path / "cph") for p in (tmp_path / "cph").rglob("**/*")}
+ mamba = {p.relative_to(tmp_path / "mm") for p in (tmp_path / "mm").rglob("**/*")}
assert conda == mamba
extracted = cph_test_file.name.removesuffix(".tar.bz2")
diff --git a/micromamba/tests/test_proxy.py b/micromamba/tests/test_proxy.py
index d2f09df28..2d7888921 100644
--- a/micromamba/tests/test_proxy.py
+++ b/micromamba/tests/test_proxy.py
@@ -75,10 +75,10 @@ def test_proxy_install(
if auth is not None:
proxy_options = ["--proxyauth", urllib.parse.unquote(auth)]
- proxy_url = "http://{}@localhost:{}".format(auth, unused_tcp_port)
+ proxy_url = f"http://{auth}@localhost:{unused_tcp_port}"
else:
proxy_options = []
- proxy_url = "http://localhost:{}".format(unused_tcp_port)
+ proxy_url = f"http://localhost:{unused_tcp_port}"
proxy = MitmProxy(
exe=mitmdump_exe,
@@ -92,9 +92,9 @@ def test_proxy_install(
file_content = [
"proxy_servers:",
- " http: {}".format(proxy_url),
- " https: {}".format(proxy_url),
- "ssl_verify: {}".format(verify_string),
+ f" http: {proxy_url}",
+ f" https: {proxy_url}",
+ f"ssl_verify: {verify_string}",
]
with open(rc_file, "w") as f:
f.write("\n".join(file_content))
@@ -110,7 +110,7 @@ def test_proxy_install(
proxy.stop_proxy()
- with open(proxy.dump, "r") as f:
+ with open(proxy.dump) as f:
proxied_requests = f.read().splitlines()
for fetch in res["actions"]["FETCH"]:
diff --git a/micromamba/tests/test_remove.py b/micromamba/tests/test_remove.py
index a8f159f8c..ba3fc9dad 100644
--- a/micromamba/tests/test_remove.py
+++ b/micromamba/tests/test_remove.py
@@ -127,7 +127,7 @@ def test_remove_in_use(tmp_home, tmp_root_prefix, tmp_xtensor_env, tmp_env_name)
assert trash_file.exists()
all_trash_files = list(Path(tmp_xtensor_env).rglob("*.mamba_trash"))
- with open(trash_file, "r") as fi:
+ with open(trash_file) as fi:
lines = [x.strip() for x in fi.readlines()]
assert all([line.endswith(".mamba_trash") for line in lines])
assert len(all_trash_files) == len(lines)
@@ -144,7 +144,7 @@ def test_remove_in_use(tmp_home, tmp_root_prefix, tmp_xtensor_env, tmp_env_name)
assert trash_file.exists()
assert pyexe_trash.exists()
- with open(trash_file, "r") as fi:
+ with open(trash_file) as fi:
lines = [x.strip() for x in fi.readlines()]
assert all([line.endswith(".mamba_trash") for line in lines])
assert len(all_trash_files) == len(lines)
@@ -155,7 +155,7 @@ def test_remove_in_use(tmp_home, tmp_root_prefix, tmp_xtensor_env, tmp_env_name)
assert trash_file.exists() is False
assert pyexe_trash.exists() is False
- subprocess.Popen("TASKKILL /F /PID {pid} /T".format(pid=pyproc.pid))
+ subprocess.Popen(f"TASKKILL /F /PID {pyproc.pid} /T")
# check that another env mod clears lingering trash files
time.sleep(0.5)
helpers.install("xsimd", "-n", tmp_env_name, "--json", no_dry_run=True)
diff --git a/micromamba/tests/test_run.py b/micromamba/tests/test_run.py
index 9f7534377..1f2d892a0 100644
--- a/micromamba/tests/test_run.py
+++ b/micromamba/tests/test_run.py
@@ -80,7 +80,7 @@ class TestRun:
test_script_path = os.path.join(os.path.dirname(__file__), test_script_file_name)
if not os.path.isfile(test_script_path):
raise RuntimeError(
- "missing test script '{}' at '{}".format(test_script_file_name, test_script_path)
+ f"missing test script '{test_script_file_name}' at '{test_script_path}"
)
subprocess_run(test_script_path, shell=True)
diff --git a/releaser.py b/releaser.py
index 2b85d677a..da277ac4c 100644
--- a/releaser.py
+++ b/releaser.py
@@ -29,7 +29,7 @@ def apply_changelog(name, version_name, changes):
if name in templates:
template = templates[name]
- with open(template, "r") as fi:
+ with open(template) as fi:
final = template_substitute(fi.read())
with open(template[: -len(".tmpl")], "w") as fo:
fo.write(final)
@@ -53,7 +53,7 @@ def apply_changelog(name, version_name, changes):
res += "\n"
cl_file = name + "/CHANGELOG.md"
- with open(cl_file, "r") as fi:
+ with open(cl_file) as fi:
prev_cl = fi.read()
with open(cl_file, "w") as fo:
fo.write(res + prev_cl)
@@ -123,7 +123,7 @@ def populate_changes(name, sections, changes):
def main():
changes = {}
- with open("CHANGELOG.md", "r") as fi:
+ with open("CHANGELOG.md") as fi:
contents = fi.readlines()
for idx, line in enumerate(contents):
diff --git a/update_changelog.py b/update_changelog.py
index 01885f8ef..99bcc3b85 100644
--- a/update_changelog.py
+++ b/update_changelog.py
@@ -34,9 +34,7 @@ def validate_date(date_str):
def subprocess_run(*args: str, **kwargs) -> str:
"""Execute a command in a subprocess while properly capturing stderr in exceptions."""
try:
- p = subprocess.run(
- args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True, **kwargs
- )
+ p = subprocess.run(args, capture_output=True, check=True, **kwargs)
except subprocess.CalledProcessError as e:
print(f"Command {args} failed with stderr: {e.stderr.decode()}")
print(f"Command {args} failed with stdout: {e.stdout.decode()}")
@@ -45,18 +43,18 @@ def subprocess_run(*args: str, **kwargs) -> str:
def append_to_file(ctgr_name, prs, out_file):
- out_file.write("\n{}:\n\n".format(ctgr_name))
+ out_file.write(f"\n{ctgr_name}:\n\n")
for pr in prs:
# Author
- pr_author_cmd = "gh pr view {} --json author".format(pr)
+ pr_author_cmd = f"gh pr view {pr} --json author"
author_login = dict(json.loads(subprocess_run(*pr_author_cmd.split()).decode("utf-8")))[
"author"
]["login"]
# Title
- pr_title_cmd = "gh pr view {} --json title".format(pr)
+ pr_title_cmd = f"gh pr view {pr} --json title"
title = dict(json.loads(subprocess_run(*pr_title_cmd.split()).decode("utf-8")))["title"]
# URL
- pr_url_cmd = "gh pr view {} --json url".format(pr)
+ pr_url_cmd = f"gh pr view {pr} --json url"
url = dict(json.loads(subprocess_run(*pr_url_cmd.split()).decode("utf-8")))["url"]
# Files
# Use a different command with graphql allowing pagination
@@ -110,7 +108,7 @@ def main():
for pr in prs_nbrs:
# Get labels
- pr_labels_cmd = "gh pr view {} --json labels".format(pr)
+ pr_labels_cmd = f"gh pr view {pr} --json labels"
labels = dict(json.loads(subprocess_run(*pr_labels_cmd.split()).decode("utf-8")))["labels"]
nb_rls_lbls_types = 0
label = ""
@@ -121,7 +119,7 @@ def main():
# Only one release label should be set
if nb_rls_lbls_types == 0:
- raise ValueError("No release label is set for PR #{}".format(pr))
+ raise ValueError(f"No release label is set for PR #{pr}")
elif nb_rls_lbls_types > 1:
raise ValueError(
"Only one release label should be set. PR #{} has {} labels.".format(
@@ -137,7 +135,7 @@ def main():
elif label == "release::ci_docs":
ci_docs_prs.append(pr)
else:
- raise ValueError("Unknown release label {} for PR #{}".format(label, pr))
+ raise ValueError(f"Unknown release label {label} for PR #{pr}")
with open("CHANGELOG.md", "r+") as changelog_file:
# Make sure we're appending at the beginning of the file
diff --git a/version_scheme.py b/version_scheme.py
index 4bdd794cb..0eda6632c 100644
--- a/version_scheme.py
+++ b/version_scheme.py
@@ -41,11 +41,11 @@ class version_info:
version_errors = []
if not self.major.isdigit():
- version_errors.append("'{}' is not a valid major version number".format(self.major))
+ version_errors.append(f"'{self.major}' is not a valid major version number")
if not self.minor.isdigit():
- version_errors.append("'{}' is not a valid minor version number".format(self.minor))
+ version_errors.append(f"'{self.minor}' is not a valid minor version number")
if not self.patch.isdigit():
- version_errors.append("'{}' is not a valid patch version number".format(self.patch))
+ version_errors.append(f"'{self.patch}' is not a valid patch version number")
if self.pre_release != "" and not self.pre_release.startswith(
VALID_VERSION_PRERELEASE_TYPES
@@ -57,13 +57,13 @@ class version_info:
)
if len(version_errors) > 0:
- error_message = "'{}' is not a valid version name:".format(version)
+ error_message = f"'{version}' is not a valid version name:"
for error in version_errors:
- error_message += "\n - {}".format(error)
+ error_message += f"\n - {error}"
hint = (
"examples of valid versions: 1.2.3, 0.1.2, 1.2.3.alpha0, 1.2.3.beta1, 3.4.5.beta.2"
)
- error_message += "\n{}".format(hint)
+ error_message += f"\n{hint}"
raise ValueError(error_message)
self.name = version