diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a85f153d..b2315fc6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -68,7 +68,6 @@ jobs: matrix: python-version: [ - "3.9", "3.10", "3.11", "3.14", @@ -76,6 +75,8 @@ jobs: "3.13t", "3.14", "3.14t", + "3.15", + "3.15t", "pypy-3.11", "graalpy25.0", ] @@ -131,14 +132,6 @@ jobs: rust-target: "aarch64-unknown-linux-gnu", } exclude: - # macOS arm doesn't have Python builds before 3.10 - - python-version: 3.9 - platform: - { - os: "macos-latest", - python-architecture: "arm64", - rust-target: "aarch64-apple-darwin", - } # no graalpy available on Windows - python-version: graalpy25.0 platform: diff --git a/CHANGELOG.md b/CHANGELOG.md index ac6e7156..901972e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## Unreleased +### Packaging +- Drop support for Python 3.9. +- Bump `setuptools` minimum version to 83.0. + ## 1.13.0 (2026-06-27) ### Added - Add `generated-files` option to `RustExtension` to copy files from the build script output directory to the wheel. [#574](https://github.com/PyO3/setuptools-rust/pull/574) diff --git a/README.md b/README.md index cf07e10b..6174025f 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,7 @@ version = "0.1.0" edition = "2021" [dependencies] -pyo3 = "0.25" +pyo3 = "0.28" [lib] name = "_lib" # private module to be nested into Python package, diff --git a/docs/conf.py b/docs/conf.py index aabcf930..20e0338d 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -80,7 +80,6 @@ def basetext(o): for t in node.traverse(basetext): t1 = Text(t.replace(DOCS_URL, "", 1), t.rawsource) t.parent.replace(t, t1) - return # end of class @@ -88,4 +87,3 @@ def basetext(o): def setup(app): app.add_transform(RelativeDocLinks) - return diff --git a/examples/generated-files/Cargo.toml b/examples/generated-files/Cargo.toml index 341cfcce..1b13a9d2 100644 --- a/examples/generated-files/Cargo.toml +++ b/examples/generated-files/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" build = "build.rs" [dependencies] -pyo3 = "0.27" +pyo3 = "0.28" [lib] name = "_lib" # private module to be nested into Python package diff --git a/examples/generated-files/python/generated_files/__init__.py b/examples/generated-files/python/generated_files/__init__.py index 3ae4f105..335523c5 100644 --- a/examples/generated-files/python/generated_files/__init__.py +++ b/examples/generated-files/python/generated_files/__init__.py @@ -1,6 +1,7 @@ -__all__ = ["library_ok", "data_files_content"] +__all__ = ["data_files_content", "library_ok"] from pathlib import Path + from ._lib import library_ok diff --git a/examples/generated-files/tests/test_lib.py b/examples/generated-files/tests/test_lib.py index 04a25248..fce0833f 100644 --- a/examples/generated-files/tests/test_lib.py +++ b/examples/generated-files/tests/test_lib.py @@ -1,4 +1,5 @@ from pathlib import Path + import generated_files diff --git a/examples/html-py-ever/pyproject.toml b/examples/html-py-ever/pyproject.toml index 23e6e098..eafe93c6 100644 --- a/examples/html-py-ever/pyproject.toml +++ b/examples/html-py-ever/pyproject.toml @@ -7,15 +7,17 @@ name = "html-py-ever" version = "0.1.0" license = { text = "MIT" } readme = "README.md" -requires-python = ">=3.6" +requires-python = ">=3.10" classifiers = [ "License :: OSI Approved :: MIT License", "Intended Audience :: Developers", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", + "Programming Language :: Python :: 3.15", "Development Status :: 5 - Production/Stable", "Operating System :: POSIX", "Operating System :: MacOS :: MacOS X", diff --git a/examples/html-py-ever/tests/conftest.py b/examples/html-py-ever/tests/conftest.py index 2ede9f7d..8d4fe225 100644 --- a/examples/html-py-ever/tests/conftest.py +++ b/examples/html-py-ever/tests/conftest.py @@ -1,4 +1,5 @@ import sys + import pytest if sys.platform == "emscripten": diff --git a/examples/html-py-ever/tests/run_all.py b/examples/html-py-ever/tests/run_all.py index 89faa27a..6c3397a1 100755 --- a/examples/html-py-ever/tests/run_all.py +++ b/examples/html-py-ever/tests/run_all.py @@ -2,7 +2,6 @@ import os from glob import glob from time import perf_counter -from typing import Tuple import html_py_ever from bs4 import BeautifulSoup @@ -13,7 +12,7 @@ lxml = None -def rust(filename: str) -> Tuple[int, float, float]: +def rust(filename: str) -> tuple[int, float, float]: start_load = perf_counter() doc = html_py_ever.parse_file(filename) end_load = perf_counter() @@ -25,7 +24,7 @@ def rust(filename: str) -> Tuple[int, float, float]: return len(links), end_load - start_load, end_search - start_search -def python(filename: str, parser: str) -> Tuple[int, float, float]: +def python(filename: str, parser: str) -> tuple[int, float, float]: start_load = perf_counter() with open(filename, encoding="utf8") as fp: soup = BeautifulSoup(fp, parser) diff --git a/examples/html-py-ever/tests/test_parsing.py b/examples/html-py-ever/tests/test_parsing.py index 77c65efd..7f610c50 100755 --- a/examples/html-py-ever/tests/test_parsing.py +++ b/examples/html-py-ever/tests/test_parsing.py @@ -1,13 +1,12 @@ #!/usr/bin/env python -from glob import glob import os +from glob import glob import html_py_ever import pytest from bs4 import BeautifulSoup from html_py_ever import Document - HTML_FILES = glob(os.path.join(os.path.dirname(__file__), "*.html")) diff --git a/examples/namespace_package/tests/test_namespace_package.py b/examples/namespace_package/tests/test_namespace_package.py index e224694a..80fbfd80 100644 --- a/examples/namespace_package/tests/test_namespace_package.py +++ b/examples/namespace_package/tests/test_namespace_package.py @@ -1,4 +1,4 @@ -from namespace_package import rust, python +from namespace_package import python, rust def test_rust(): diff --git a/examples/rust_with_cffi/cffi_module.py b/examples/rust_with_cffi/cffi_module.py index 24eaaf48..220a68d6 100644 --- a/examples/rust_with_cffi/cffi_module.py +++ b/examples/rust_with_cffi/cffi_module.py @@ -1,6 +1,5 @@ import cffi - ffi = cffi.FFI() ffi.cdef( """ diff --git a/examples/rust_with_cffi/setup.py b/examples/rust_with_cffi/setup.py index a288a58f..0bc2d815 100644 --- a/examples/rust_with_cffi/setup.py +++ b/examples/rust_with_cffi/setup.py @@ -1,5 +1,5 @@ -#!/usr/bin/env python from setuptools import find_packages, setup + from setuptools_rust import RustExtension setup( diff --git a/noxfile.py b/noxfile.py index cd874470..dd6af1cd 100644 --- a/noxfile.py +++ b/noxfile.py @@ -1,11 +1,11 @@ import os -from contextlib import ExitStack -from inspect import cleandoc as heredoc -from glob import glob -from pathlib import Path import shutil import sys import tempfile +from contextlib import ExitStack +from glob import glob +from inspect import cleandoc as heredoc +from pathlib import Path import nox import nox.command diff --git a/pyproject.toml b/pyproject.toml index f9ffe273..749e0c22 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,7 @@ name = "setuptools-rust" version = "1.13.0" description = "Setuptools Rust extension plugin" readme = "README.md" -requires-python = ">=3.9" +requires-python = ">=3.10" keywords = ["distutils", "setuptools", "rust"] authors = [ {name = "Nikolay Kim", email = "fafhrd91@gmail.com"}, @@ -13,10 +13,12 @@ classifiers = [ "License :: OSI Approved :: MIT License", "Intended Audience :: Developers", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", + "Programming Language :: Python :: 3.15", "Development Status :: 5 - Production/Stable", "Operating System :: POSIX", "Operating System :: MacOS :: MacOS X", @@ -24,7 +26,7 @@ classifiers = [ ] dependencies = [ - "setuptools>=62.4", + "setuptools>=83.0", "semantic_version>=2.8.2,<3", ] diff --git a/setuptools_rust/_utils.py b/setuptools_rust/_utils.py index 62c7d72a..23dbffff 100644 --- a/setuptools_rust/_utils.py +++ b/setuptools_rust/_utils.py @@ -1,5 +1,5 @@ import subprocess -from typing import Any, Optional, Union, cast +from typing import Any, cast class Env: @@ -8,9 +8,9 @@ class Env: Dictionaries are unhashable, but ``functools.lru_cache`` needs all parameters to be hashable, which we solve which a custom ``__hash__``.""" - env: Optional[dict[str, str]] + env: dict[str, str] | None - def __init__(self, env: Optional[dict[str, str]]): + def __init__(self, env: dict[str, str] | None): self.env = env def __eq__(self, other: object) -> bool: @@ -26,17 +26,17 @@ def __hash__(self) -> int: def run_subprocess( - *args: Any, env: Union[Env, dict[str, str], None], **kwargs: Any + *args: Any, env: Env | dict[str, str] | None, **kwargs: Any ) -> subprocess.CompletedProcess: """Wrapper around subprocess.run that requires a decision to pass env.""" if isinstance(env, Env): env = env.env kwargs["env"] = env - return subprocess.run(*args, **kwargs) # noqa: TID251 # this is a wrapper to implement the rule + return subprocess.run(*args, **kwargs) # noqa: PLW1510, TID251 # this is a wrapper to implement the rule def check_subprocess_output( - *args: Any, env: Union[Env, dict[str, str], None], **kwargs: Any + *args: Any, env: Env | dict[str, str] | None, **kwargs: Any ) -> str: """Wrapper around subprocess.run that requires a decision to pass env.""" if isinstance(env, Env): diff --git a/setuptools_rust/build.py b/setuptools_rust/build.py index ff7ab184..f01cf5f3 100644 --- a/setuptools_rust/build.py +++ b/setuptools_rust/build.py @@ -3,32 +3,33 @@ import collections import enum import json +import logging import os import platform import shutil import subprocess import sys import sysconfig -import logging import warnings -from setuptools.errors import ( - CompileError, - ExecError, - FileError, - InternalError, - PlatformError, -) -from sysconfig import get_config_var from pathlib import Path -from typing import Dict, List, Literal, NamedTuple, Optional, Set, Tuple, Union, cast +from sysconfig import get_config_var +from typing import Literal, NamedTuple, cast from setuptools import Distribution +from setuptools.command.bdist_wheel import bdist_wheel as CommandBdistWheel from setuptools.command.build_ext import build_ext as CommandBuildExt from setuptools.command.build_ext import get_abi3_suffix from setuptools.command.build_py import build_py as setuptools_build_py from setuptools.command.install_scripts import install_scripts as CommandInstallScripts +from setuptools.errors import ( + CompileError, + ExecError, + FileError, + InternalError, + PlatformError, +) -from ._utils import check_subprocess_output, format_called_process_error, Env +from ._utils import Env, check_subprocess_output, format_called_process_error from .command import RustCommand from .extension import Binding, RustBin, RustExtension, Strip from .rustc_info import ( @@ -40,16 +41,7 @@ logger = logging.getLogger(__name__) -try: - from setuptools.command.bdist_wheel import bdist_wheel as CommandBdistWheel -except ImportError: # old version of setuptools - try: - from wheel.bdist_wheel import bdist_wheel as CommandBdistWheel # type: ignore[no-redef] - except ImportError: - from setuptools import Command as CommandBdistWheel # type: ignore[assignment] - - -def _check_cargo_supports_crate_type_option(env: Optional[Env]) -> bool: +def _check_cargo_supports_crate_type_option(env: Env | None) -> bool: version = get_rust_version(env) if version is None: @@ -66,7 +58,7 @@ class build_rust(RustCommand): description = "build Rust extensions (compile/link to build directory)" - user_options = [ + user_options = [ # noqa: RUF012 ( "inplace", "i", @@ -83,15 +75,15 @@ class build_rust(RustCommand): ), ("target=", None, "Build for the target triple"), ] - boolean_options = ["inplace", "debug", "release", "qbuild"] + boolean_options = ("inplace", "debug", "release", "qbuild") inplace: bool = False debug: bool = False release: bool = False qbuild: bool = False - plat_name: Optional[str] = None - build_temp: Optional[str] = None + plat_name: str | None = None + build_temp: str | None = None def initialize_options(self) -> None: super().initialize_options() @@ -131,7 +123,7 @@ def run_for_extension(self, ext: RustExtension) -> None: def build_extension( self, ext: RustExtension - ) -> Tuple[List["_BuiltModule"], Optional[Path]]: + ) -> tuple[list[_BuiltModule], Path | None]: """ Build the Rust components, but don't install them anywhere. @@ -157,8 +149,8 @@ def build_extension( cargo_args = self._cargo_args(ext=ext, release=not debug, quiet=quiet) - rustc_args: List[str] = [] - rustflags: List[str] = [] + rustc_args: list[str] = [] + rustflags: list[str] = [] if ext._uses_exec_binding(): command = [ self.cargo, @@ -207,7 +199,7 @@ def build_extension( print(f"[RUSTFLAGS={new_rustflags}]", end=" ", file=sys.stderr) if self.target is _Platform.CARGO_DEFAULT: - targets: List[Optional[str]] = [None] + targets: list[str | None] = [None] elif self.target is _Platform.UNIVERSAL2: targets = list(_UNIVERSAL2_TARGETS) if ext.generated_files: @@ -217,7 +209,7 @@ def build_extension( else: targets = [self.target] - cargo_messages: Dict[str, List[str]] = {} + cargo_messages: dict[str, list[str]] = {} for target in targets: target_command = command.copy() if target is None: @@ -344,8 +336,8 @@ def build_extension( def install_extension( self, ext: RustExtension, - dylib_paths: List["_BuiltModule"], - build_artifact_dir: Optional[Path], + dylib_paths: list[_BuiltModule], + build_artifact_dir: Path | None, ) -> None: debug_build = self._is_debug_build(ext) @@ -544,7 +536,7 @@ def _cargo_args( ext: RustExtension, release: bool, quiet: bool, - ) -> List[str]: + ) -> list[str]: args = [] ext_profile = ext.get_cargo_profile() env_profile = os.getenv("SETUPTOOLS_RUST_CARGO_PROFILE") @@ -589,12 +581,12 @@ def _cargo_args( def _config_specific_rust_args( self, ext: RustExtension - ) -> Tuple[List[str], List[str]]: + ) -> tuple[list[str], list[str]]: """Get extra arguments for `rustc` and the `RUSTFLAGS` environment variable that depend on the specific environmental configuration for the compilation target.""" - def apple_specific_rustc() -> List[str]: + def apple_specific_rustc() -> list[str]: # Apple platforms require special linker arguments ext_basename = os.path.basename(self.get_dylib_ext_path(ext, ext.name)) return [ @@ -603,8 +595,8 @@ def apple_specific_rustc() -> List[str]: f"-Clink-arg=-Wl,-install_name,@rpath/{ext_basename}", ] - rustc_args: List[str] = [] # Command-line arguments for rustc. - rust_flags: List[str] = [] # Extras for the `RUSTFLAGS` environment variable. + rustc_args: list[str] = [] # Command-line arguments for rustc. + rust_flags: list[str] = [] # Extras for the `RUSTFLAGS` environment variable. if self.target is _Platform.UNIVERSAL2: # In this case we're in a multi-target compilation, so there's no one single @@ -628,7 +620,7 @@ def apple_specific_rustc() -> List[str]: return rustc_args, rust_flags -def _combine_universal2_artifacts(artifacts: List[str]) -> List[str]: +def _combine_universal2_artifacts(artifacts: list[str]) -> list[str]: """For a multi-target compilation corresponding to an intended universal2 build, combine each set of corresponding separate-target artifacts into a single universal2 binary. @@ -655,14 +647,14 @@ def _combine_universal2_artifacts(artifacts: List[str]) -> List[str]: return combined -def create_universal2_binary(output_path: str, input_paths: List[str]) -> None: +def create_universal2_binary(output_path: str, input_paths: list[str]) -> None: # Try lipo first command = ["lipo", "-create", "-output", output_path, *input_paths] try: check_subprocess_output(command, env=None, text=True) except subprocess.CalledProcessError as e: output = e.output - raise CompileError("lipo failed with code: %d\n%s" % (e.returncode, output)) + raise CompileError(f"lipo failed with code: {e.returncode}\n{output}") except OSError: # lipo not found, try using the fat-macho library try: @@ -702,7 +694,7 @@ class _BuiltModule(NamedTuple): path: str -def _replace_vendor_with_unknown(target: str) -> Optional[str]: +def _replace_vendor_with_unknown(target: str) -> str | None: """Replaces vendor in the target triple with unknown. Returns None if the target is not made of 4 parts. @@ -714,12 +706,12 @@ def _replace_vendor_with_unknown(target: str) -> Optional[str]: return "-".join(components) -def _prepare_build_environment(env: Env, ext: RustExtension) -> Dict[str, str]: +def _prepare_build_environment(env: Env, ext: RustExtension) -> dict[str, str]: """Prepares environment variables to use when executing cargo build.""" base_executable = None if os.getenv("SETUPTOOLS_RUST_PEP517_USE_BASE_PYTHON"): - base_executable = getattr(sys, "_base_executable") + base_executable = sys._base_executable # type: ignore[attr-defined] if base_executable and os.path.exists(base_executable): executable = os.path.realpath(base_executable) @@ -750,7 +742,7 @@ def _prepare_build_environment(env: Env, ext: RustExtension) -> Dict[str, str]: def _is_py_limited_api( ext_setting: Literal["auto", True, False], - wheel_setting: Optional[_PyLimitedApi], + wheel_setting: _PyLimitedApi | None, ) -> bool: """Returns whether this extension is being built for the limited api. @@ -778,7 +770,7 @@ def _is_py_limited_api( def _binding_features( ext: RustExtension, py_limited_api: _PyLimitedApi, -) -> Set[str]: +) -> set[str]: if ext.binding in (Binding.NoBinding, Binding.Exec): return set() elif ext.binding is Binding.PyO3: @@ -799,9 +791,9 @@ def _binding_features( _PyLimitedApi = Literal["cp37", "cp38", "cp39", "cp310", "cp311", "cp312", True, False] -def _override_cargo_default_target(plat_name: str, env: Env) -> Union[str, _Platform]: +def _override_cargo_default_target(plat_name: str, env: Env) -> str | _Platform: """Get a platform-specific override, if one is needed for correctness.""" - override: Union[str, _Platform] = _Platform.CARGO_DEFAULT + override: str | _Platform = _Platform.CARGO_DEFAULT if plat_name in ("win32", "win-amd64"): toolchain = ( "gnu" if get_rustc_cfgs(None, env).get("target_env") == "gnu" else "msvc" @@ -823,7 +815,7 @@ def _override_cargo_default_target(plat_name: str, env: Env) -> Union[str, _Plat return override -def _macos_target_from_arch_flags(arch_flags: Optional[str]) -> Union[str, _Platform]: +def _macos_target_from_arch_flags(arch_flags: str | None) -> str | _Platform: """Detect the macOS target to compile for, based on what (if anything) is set in the `ARCHFLAGS`.""" if arch_flags is None: @@ -839,7 +831,7 @@ def _macos_target_from_arch_flags(arch_flags: Optional[str]) -> Union[str, _Plat return _Platform.CARGO_DEFAULT -def _split_platform_and_extension(ext_path: str) -> Tuple[str, str, str]: +def _split_platform_and_extension(ext_path: str) -> tuple[str, str, str]: """Splits an extension path into a tuple (ext_path, plat_tag, extension). >>> _split_platform_and_extension("foo/bar.platform.so") @@ -854,11 +846,11 @@ def _split_platform_and_extension(ext_path: str) -> Tuple[str, str, str]: def _find_cargo_artifacts( - cargo_messages: List[str], + cargo_messages: list[str], *, package_id: str, - kinds: Set[str], -) -> List[str]: + kinds: set[str], +) -> list[str]: """Identifies cargo artifacts built for the given `package_id` from the provided cargo_messages. @@ -915,7 +907,7 @@ def _find_cargo_artifacts( return artifacts -def _find_cargo_out_dir(cargo_messages: List[str], package_id: str) -> Optional[Path]: +def _find_cargo_out_dir(cargo_messages: list[str], package_id: str) -> Path | None: # Chances are that the line we're looking for will be the third-last line in the # messages. The last is the completion report, the penultimate is generally the # build of the final artifact. @@ -944,10 +936,10 @@ def _replace_cross_target_dir(path: str, ext: RustExtension, *, quiet: bool) -> def _get_bdist_wheel_cmd( dist: Distribution, create: Literal[True, False] = True -) -> Optional[CommandBdistWheel]: +) -> CommandBdistWheel | None: try: cmd_obj = dist.get_command_obj("bdist_wheel", create=create) cmd_obj.ensure_finalized() # type: ignore[union-attr] return cast(CommandBdistWheel, cmd_obj) - except Exception: + except Exception: # noqa: BLE001 return None diff --git a/setuptools_rust/clean.py b/setuptools_rust/clean.py index e1a132c2..a69b2b34 100644 --- a/setuptools_rust/clean.py +++ b/setuptools_rust/clean.py @@ -27,5 +27,5 @@ def run_for_extension(self, ext: RustExtension) -> None: # Execute cargo command try: check_subprocess_output(args, env=ext.env) - except Exception: + except Exception: # noqa: BLE001, S110 pass diff --git a/setuptools_rust/command.py b/setuptools_rust/command.py index 1aa5c08d..27d2c5f9 100644 --- a/setuptools_rust/command.py +++ b/setuptools_rust/command.py @@ -1,8 +1,8 @@ -from abc import ABC, abstractmethod import logging -from setuptools import Command, Distribution +from abc import ABC, abstractmethod + +from setuptools import Command from setuptools.errors import PlatformError -from typing import List, Optional from .extension import RustExtension from .rustc_info import get_rust_version @@ -13,16 +13,11 @@ class RustCommand(Command, ABC): """Abstract base class for commands which interact with Rust Extensions.""" - # Types for distutils variables which exist on all commands but seem to be - # missing from https://github.com/python/typeshed/blob/master/stdlib/distutils/cmd.pyi - distribution: Distribution - verbose: int - def initialize_options(self) -> None: - self.extensions: List[RustExtension] = [] + self.extensions: list[RustExtension] = [] def finalize_options(self) -> None: - extensions: Optional[List[RustExtension]] = getattr( + extensions: list[RustExtension] | None = getattr( self.distribution, "rust_extensions", None ) if extensions is None: @@ -32,14 +27,14 @@ def finalize_options(self) -> None: if not isinstance(extensions, list): ty = type(extensions) - raise ValueError( + raise TypeError( "expected list of RustExtension objects for rust_extensions " f"argument to setup(), got `{ty}`" ) for i, extension in enumerate(extensions): if not isinstance(extension, RustExtension): ty = type(extension) - raise ValueError( + raise TypeError( "expected RustExtension object for rust_extensions " f"argument to setup(), got `{ty}` at position {i}" ) diff --git a/setuptools_rust/extension.py b/setuptools_rust/extension.py index bd3e5c03..ab66f6d3 100644 --- a/setuptools_rust/extension.py +++ b/setuptools_rust/extension.py @@ -5,26 +5,23 @@ import re import subprocess import warnings -from setuptools.errors import SetupError +from collections.abc import Sequence from enum import IntEnum, auto from functools import lru_cache from typing import ( + TYPE_CHECKING, Any, - Dict, - List, Literal, NewType, - Optional, - Sequence, - TYPE_CHECKING, - Union, cast, ) +from setuptools.errors import SetupError + if TYPE_CHECKING: from semantic_version import SimpleSpec -from ._utils import check_subprocess_output, format_called_process_error, Env +from ._utils import Env, check_subprocess_output, format_called_process_error class Binding(IntEnum): @@ -128,26 +125,26 @@ class RustExtension: def __init__( self, - target: Union[str, Dict[str, str]], + target: str | dict[str, str], path: str = "Cargo.toml", - args: Optional[Sequence[str]] = (), - cargo_manifest_args: Optional[Sequence[str]] = (), - features: Optional[Sequence[str]] = (), - rustc_flags: Optional[Sequence[str]] = (), - rust_version: Optional[str] = None, + args: Sequence[str] | None = (), + cargo_manifest_args: Sequence[str] | None = (), + features: Sequence[str] | None = (), + rustc_flags: Sequence[str] | None = (), + rust_version: str | None = None, quiet: bool = False, - debug: Optional[bool] = None, + debug: bool | None = None, binding: Binding = Binding.PyO3, strip: Strip = Strip.No, script: bool = False, native: bool = False, optional: bool = False, py_limited_api: Literal["auto", True, False] = "auto", - env: Optional[Dict[str, str]] = None, - generated_files: Optional[Dict[str, str]] = None, + env: dict[str, str] | None = None, + generated_files: dict[str, str] | None = None, ): if isinstance(target, dict): - name = "; ".join("%s=%s" % (key, val) for key, val in target.items()) + name = "; ".join(f"{key}={val}" for key, val in target.items()) else: name = target target = {"": target} @@ -206,7 +203,7 @@ def get_lib_name(self, *, quiet: bool) -> str: assert isinstance(name, str) return re.sub(r"[./\\-]", "_", name) - def get_rust_version(self) -> Optional[SimpleSpec]: # type: ignore[no-any-unimported] + def get_rust_version(self) -> SimpleSpec | None: # type: ignore[no-any-unimported] if self.rust_version is None: return None try: @@ -218,7 +215,7 @@ def get_rust_version(self) -> Optional[SimpleSpec]: # type: ignore[no-any-unimp "Can not parse rust compiler version: %s", self.rust_version ) - def get_cargo_profile(self) -> Optional[str]: + def get_cargo_profile(self) -> str | None: try: index = self.args.index("--profile") return self.args[index + 1] @@ -237,12 +234,12 @@ def get_cargo_profile(self) -> Optional[str]: else: return None - def entry_points(self) -> List[str]: + def entry_points(self) -> list[str]: entry_points = [] if self.script and self.binding == Binding.Exec: for executable, mod in self.target.items(): base_mod, name = mod.rsplit(".") - script = "%s=%s.%s:run" % (name, base_mod, _script_name(executable)) + script = f"{name}={base_mod}.{_script_name(executable)}:run" entry_points.append(script) return entry_points @@ -256,7 +253,7 @@ def install_script(self, module_name: str, exe_path: str) -> None: with open(file, "w") as f: f.write(_SCRIPT_TEMPLATE.format(executable=repr(executable))) - def metadata(self, *, quiet: bool) -> "CargoMetadata": + def metadata(self, *, quiet: bool) -> CargoMetadata: """Returns cargo metadata for this extension package. Cached - will only execute cargo on first invocation. @@ -264,8 +261,8 @@ def metadata(self, *, quiet: bool) -> "CargoMetadata": return self._metadata(os.environ.get("CARGO", "cargo"), quiet) - @lru_cache() - def _metadata(self, cargo: str, quiet: bool) -> "CargoMetadata": + @lru_cache # noqa: B019 + def _metadata(self, cargo: str, quiet: bool) -> CargoMetadata: metadata_command = [ cargo, "metadata", @@ -330,17 +327,17 @@ class RustBin(RustExtension): def __init__( self, - target: Union[str, Dict[str, str]], + target: str | dict[str, str], path: str = "Cargo.toml", - args: Optional[Sequence[str]] = (), - cargo_manifest_args: Optional[Sequence[str]] = (), - features: Optional[Sequence[str]] = (), - rust_version: Optional[str] = None, + args: Sequence[str] | None = (), + cargo_manifest_args: Sequence[str] | None = (), + features: Sequence[str] | None = (), + rust_version: str | None = None, quiet: bool = False, - debug: Optional[bool] = None, + debug: bool | None = None, strip: Strip = Strip.No, optional: bool = False, - env: Optional[dict[str, str]] = None, + env: dict[str, str] | None = None, ): super().__init__( target=target, @@ -358,11 +355,11 @@ def __init__( env=env, ) - def entry_points(self) -> List[str]: + def entry_points(self) -> list[str]: return [] -CargoMetadata = NewType("CargoMetadata", Dict[str, Any]) +CargoMetadata = NewType("CargoMetadata", dict[str, Any]) def _script_name(executable: str) -> str: diff --git a/setuptools_rust/rustc_info.py b/setuptools_rust/rustc_info.py index ad372492..326746c8 100644 --- a/setuptools_rust/rustc_info.py +++ b/setuptools_rust/rustc_info.py @@ -2,9 +2,10 @@ import os import subprocess -from setuptools.errors import PlatformError from functools import lru_cache -from typing import Dict, List, NewType, Optional, TYPE_CHECKING +from typing import TYPE_CHECKING, NewType + +from setuptools.errors import PlatformError from ._utils import Env, check_subprocess_output @@ -12,7 +13,7 @@ from semantic_version import Version -def get_rust_version(env: Optional[Env]) -> Optional[Version]: # type: ignore[no-any-unimported] +def get_rust_version(env: Env | None) -> Version | None: # type: ignore[no-any-unimported] try: # first line of rustc -Vv is something like # rustc 1.61.0 (fe5b13d68 2022-05-18) @@ -26,7 +27,7 @@ def get_rust_version(env: Optional[Env]) -> Optional[Version]: # type: ignore[n _HOST_LINE_START = "host: " -def get_rust_host(env: Optional[Env]) -> str: +def get_rust_host(env: Env | None) -> str: # rustc -Vv has a line denoting the host which cargo uses to decide the # default target, e.g. # host: aarch64-apple-darwin @@ -36,7 +37,7 @@ def get_rust_host(env: Optional[Env]) -> str: raise PlatformError("Could not determine rust host") -RustCfgs = NewType("RustCfgs", Dict[str, Optional[str]]) +RustCfgs = NewType("RustCfgs", dict[str, str | None]) def _is_custom_target(target: str) -> bool: @@ -52,7 +53,7 @@ def _is_custom_target(target: str) -> bool: return False -def get_rustc_cfgs(target_triple: Optional[str], env: Env) -> RustCfgs: +def get_rustc_cfgs(target_triple: str | None, env: Env) -> RustCfgs: cfgs = RustCfgs({}) for entry in get_rust_target_info(target_triple, env): maybe_split = entry.split("=", maxsplit=1) @@ -64,8 +65,8 @@ def get_rustc_cfgs(target_triple: Optional[str], env: Env) -> RustCfgs: return cfgs -@lru_cache() -def get_rust_target_info(target_triple: Optional[str], env: Env) -> List[str]: +@lru_cache +def get_rust_target_info(target_triple: str | None, env: Env) -> list[str]: cmd = ["rustc", "--print", "cfg"] if target_triple: if _is_custom_target(target_triple): @@ -75,19 +76,19 @@ def get_rust_target_info(target_triple: Optional[str], env: Env) -> List[str]: return output.splitlines() -@lru_cache() -def get_rust_target_list(env: Env) -> List[str]: +@lru_cache +def get_rust_target_list(env: Env) -> list[str]: output = check_subprocess_output( ["rustc", "--print", "target-list"], env=env, text=True ) return output.splitlines() -@lru_cache() +@lru_cache def _rust_version(env: Env) -> str: return check_subprocess_output(["rustc", "-V"], env=env, text=True) -@lru_cache() +@lru_cache def _rust_version_verbose(env: Env) -> str: return check_subprocess_output(["rustc", "-Vv"], env=env, text=True) diff --git a/setuptools_rust/setuptools_ext.py b/setuptools_rust/setuptools_ext.py index cbb4c1d0..774c84a5 100644 --- a/setuptools_rust/setuptools_ext.py +++ b/setuptools_rust/setuptools_ext.py @@ -3,7 +3,7 @@ import sys import sysconfig from functools import partial -from typing import List, Literal, Optional, Set, Tuple, Type, TypeVar, cast +from typing import Literal, TypeVar, cast from setuptools.command.build_ext import build_ext from setuptools.command.install import install @@ -39,7 +39,7 @@ def add_rust_extension(dist: Distribution) -> None: - sdist_base_class = cast(Type[sdist], dist.cmdclass.get("sdist", sdist)) + sdist_base_class = cast(type[sdist], dist.cmdclass.get("sdist", sdist)) sdist_options = sdist_base_class.user_options.copy() sdist_boolean_options = sdist_base_class.boolean_options.copy() sdist_negative_opt = sdist_base_class.negative_opt.copy() @@ -93,9 +93,9 @@ def make_distribution(self) -> None: # --frozen, --locked, or --offline. # # https://doc.rust-lang.org/cargo/commands/cargo-build.html#manifest-options - cargo_manifest_args: Set[str] = set() - env: Optional[Env] = None - env_source: Optional[str] = None + cargo_manifest_args: set[str] = set() + env: Env | None = None + env_source: str | None = None for ext in self.distribution.rust_extensions: if env is not None: if ext.env != env: @@ -161,7 +161,7 @@ def make_distribution(self) -> None: dist.cmdclass["sdist"] = sdist_rust_extension build_ext_base_class = cast( - Type[build_ext], dist.cmdclass.get("build_ext", build_ext) + type[build_ext], dist.cmdclass.get("build_ext", build_ext) ) build_ext_options = build_ext_base_class.user_options.copy() build_ext_options.append(("target", None, "Build for the target triple")) @@ -184,9 +184,9 @@ def run(self) -> None: build_rust.plat_name = self._get_wheel_plat_name() or self.plat_name build_rust.run() - def _get_wheel_plat_name(self) -> Optional[str]: + def _get_wheel_plat_name(self) -> str | None: cmd = _get_bdist_wheel_cmd(self.distribution) - return cast(Optional[str], getattr(cmd, "plat_name", None)) + return cast(str | None, getattr(cmd, "plat_name", None)) dist.cmdclass["build_ext"] = build_ext_rust_extension @@ -202,7 +202,7 @@ def run(self) -> None: dist.cmdclass["clean"] = clean_rust_extension - install_base_class = cast(Type[install], dist.cmdclass.get("install", install)) + install_base_class = cast(type[install], dist.cmdclass.get("install", install)) # this is required to make install_scripts compatible with RustBin class install_rust_extension(install_base_class): # type: ignore[misc,valid-type] @@ -220,13 +220,13 @@ def run(self) -> None: dist.cmdclass["install"] = install_rust_extension install_lib_base_class = cast( - Type[install_lib], dist.cmdclass.get("install_lib", install_lib) + type[install_lib], dist.cmdclass.get("install_lib", install_lib) ) # prevent RustBin from being installed to data_dir class install_lib_rust_extension(install_lib_base_class): # type: ignore[misc,valid-type] - def get_exclusions(self) -> Set[str]: - exclusions: Set[str] = super().get_exclusions() + def get_exclusions(self) -> set[str]: + exclusions: set[str] = super().get_exclusions() install_scripts_obj = cast( install_scripts, self.get_finalized_command("install_scripts") ) @@ -244,7 +244,7 @@ def get_exclusions(self) -> Set[str]: dist.cmdclass["install_lib"] = install_lib_rust_extension install_scripts_base_class = cast( - Type[install_scripts], dist.cmdclass.get("install_scripts", install_scripts) + type[install_scripts], dist.cmdclass.get("install_scripts", install_scripts) ) # this is required to make install_scripts compatible with RustBin @@ -266,7 +266,7 @@ def run(self) -> None: if bdist_wheel is not None: bdist_wheel_base_class = cast( - Type[bdist_wheel], dist.cmdclass.get("bdist_wheel", bdist_wheel) + type[bdist_wheel], dist.cmdclass.get("bdist_wheel", bdist_wheel) ) bdist_wheel_options = bdist_wheel_base_class.user_options.copy() bdist_wheel_options.append(("target", None, "Build for the target triple")) @@ -279,7 +279,7 @@ def initialize_options(self) -> None: super().initialize_options() self.target = os.getenv("CARGO_BUILD_TARGET") - def get_tag(self) -> Tuple[str, str, str]: + def get_tag(self) -> tuple[str, str, str]: python, abi, plat = super().get_tag() arch_flags = os.getenv("ARCHFLAGS") universal2 = False @@ -293,7 +293,7 @@ def get_tag(self) -> Tuple[str, str, str]: # Example: macosx_11_0_arm64 macos_target = ".".join(plat.split("_")[1:3]) plat = calculate_macosx_platform_tag( - self.bdist_dir, "macosx-{}-universal2".format(macos_target) + self.bdist_dir, f"macosx-{macos_target}-universal2" ) return python, abi, plat @@ -301,7 +301,7 @@ def get_tag(self) -> Tuple[str, str, str]: def rust_extensions( - dist: Distribution, attr: Literal["rust_extensions"], value: List[RustExtension] + dist: Distribution, attr: Literal["rust_extensions"], value: list[RustExtension] ) -> None: assert attr == "rust_extensions" has_rust_extensions = len(value) > 0 @@ -319,7 +319,7 @@ def pyprojecttoml_config(dist: Distribution) -> None: with open("pyproject.toml", "rb") as f: cfg = toml_load(f).get("tool", {}).get("setuptools-rust") except FileNotFoundError: - return None + return if cfg: modules = map(partial(_create, RustExtension), cfg.get("ext-modules", [])) @@ -328,7 +328,7 @@ def pyprojecttoml_config(dist: Distribution) -> None: rust_extensions(dist, "rust_extensions", dist.rust_extensions) # type: ignore[attr-defined] -def _create(constructor: Type[T], config: dict) -> T: +def _create(constructor: type[T], config: dict) -> T: kwargs = { # PEP 517/621 convention: pyproject.toml uses dashes k.replace("-", "_"): v diff --git a/setuptools_rust/version.py b/setuptools_rust/version.py index 4a49e494..1db3c766 100644 --- a/setuptools_rust/version.py +++ b/setuptools_rust/version.py @@ -1,4 +1,4 @@ __version__ = version = "1.13.0" __version_tuple__ = version_tuple = tuple( - map(lambda x: int(x[1]) if x[0] < 3 else x[1], enumerate(__version__.split("."))) + int(x[1]) if x[0] < 3 else x[1] for x in enumerate(__version__.split(".")) ) diff --git a/tests/test_build.py b/tests/test_build.py index 3a72dc78..49d831b1 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -1,8 +1,7 @@ from unittest import mock -from setuptools_rust.build import _override_cargo_default_target from setuptools_rust._utils import Env - +from setuptools_rust.build import _override_cargo_default_target NO_ENV = Env(None)