diff -Nru hatchling-1.18.0/.gitignore hatchling-1.21.0/.gitignore --- hatchling-1.18.0/.gitignore 2020-02-02 00:00:00.000000000 +0000 +++ hatchling-1.21.0/.gitignore 2020-02-02 00:00:00.000000000 +0000 @@ -10,6 +10,7 @@ # Root directories /.benchmarks/ +/.cache/ /.env/ /.idea/ /.mypy_cache/ diff -Nru hatchling-1.18.0/PKG-INFO hatchling-1.21.0/PKG-INFO --- hatchling-1.18.0/PKG-INFO 2020-02-02 00:00:00.000000000 +0000 +++ hatchling-1.21.0/PKG-INFO 2020-02-02 00:00:00.000000000 +0000 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: hatchling -Version: 1.18.0 +Version: 1.21.0 Summary: Modern, extensible Python build backend Project-URL: Homepage, https://hatch.pypa.io/latest/ Project-URL: Sponsor, https://github.com/sponsors/ofek @@ -20,6 +20,7 @@ Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Topic :: Software Development :: Build Tools @@ -42,7 +43,7 @@ | | | | --- | --- | | Package | [![PyPI - Version](https://img.shields.io/pypi/v/hatchling.svg?logo=pypi&label=PyPI&logoColor=gold)](https://pypi.org/project/hatchling/) [![PyPI - Downloads](https://img.shields.io/pypi/dm/hatchling.svg?color=blue&label=Downloads&logo=pypi&logoColor=gold)](https://pypi.org/project/hatchling/) [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/hatchling.svg?logo=python&label=Python&logoColor=gold)](https://pypi.org/project/hatchling/) | -| Meta | [![Hatch project](https://img.shields.io/badge/%F0%9F%A5%9A-Hatch-4051b5.svg)](https://github.com/pypa/hatch) [![linting - Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v0.json)](https://github.com/charliermarsh/ruff) [![code style - Black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) [![types - Mypy](https://img.shields.io/badge/types-Mypy-blue.svg)](https://github.com/python/mypy) [![License - MIT](https://img.shields.io/badge/license-MIT-9400d3.svg)](https://spdx.org/licenses/) [![GitHub Sponsors](https://img.shields.io/github/sponsors/ofek?logo=GitHub%20Sponsors&style=social)](https://github.com/sponsors/ofek) | +| Meta | [![Hatch project](https://img.shields.io/badge/%F0%9F%A5%9A-Hatch-4051b5.svg)](https://github.com/pypa/hatch) [![linting - Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff) [![code style - Black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) [![types - Mypy](https://img.shields.io/badge/types-Mypy-blue.svg)](https://github.com/python/mypy) [![License - MIT](https://img.shields.io/badge/license-MIT-9400d3.svg)](https://spdx.org/licenses/) [![GitHub Sponsors](https://img.shields.io/github/sponsors/ofek?logo=GitHub%20Sponsors&style=social)](https://github.com/sponsors/ofek) | diff -Nru hatchling-1.18.0/README.md hatchling-1.21.0/README.md --- hatchling-1.18.0/README.md 2020-02-02 00:00:00.000000000 +0000 +++ hatchling-1.21.0/README.md 2020-02-02 00:00:00.000000000 +0000 @@ -7,7 +7,7 @@ | | | | --- | --- | | Package | [![PyPI - Version](https://img.shields.io/pypi/v/hatchling.svg?logo=pypi&label=PyPI&logoColor=gold)](https://pypi.org/project/hatchling/) [![PyPI - Downloads](https://img.shields.io/pypi/dm/hatchling.svg?color=blue&label=Downloads&logo=pypi&logoColor=gold)](https://pypi.org/project/hatchling/) [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/hatchling.svg?logo=python&label=Python&logoColor=gold)](https://pypi.org/project/hatchling/) | -| Meta | [![Hatch project](https://img.shields.io/badge/%F0%9F%A5%9A-Hatch-4051b5.svg)](https://github.com/pypa/hatch) [![linting - Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v0.json)](https://github.com/charliermarsh/ruff) [![code style - Black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) [![types - Mypy](https://img.shields.io/badge/types-Mypy-blue.svg)](https://github.com/python/mypy) [![License - MIT](https://img.shields.io/badge/license-MIT-9400d3.svg)](https://spdx.org/licenses/) [![GitHub Sponsors](https://img.shields.io/github/sponsors/ofek?logo=GitHub%20Sponsors&style=social)](https://github.com/sponsors/ofek) | +| Meta | [![Hatch project](https://img.shields.io/badge/%F0%9F%A5%9A-Hatch-4051b5.svg)](https://github.com/pypa/hatch) [![linting - Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff) [![code style - Black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) [![types - Mypy](https://img.shields.io/badge/types-Mypy-blue.svg)](https://github.com/python/mypy) [![License - MIT](https://img.shields.io/badge/license-MIT-9400d3.svg)](https://spdx.org/licenses/) [![GitHub Sponsors](https://img.shields.io/github/sponsors/ofek?logo=GitHub%20Sponsors&style=social)](https://github.com/sponsors/ofek) | diff -Nru hatchling-1.18.0/debian/changelog hatchling-1.21.0/debian/changelog --- hatchling-1.18.0/debian/changelog 2023-06-18 14:35:06.000000000 +0000 +++ hatchling-1.21.0/debian/changelog 2023-12-20 21:08:12.000000000 +0000 @@ -1,3 +1,9 @@ +hatchling (1.21.0-1) unstable; urgency=medium + + * New upstream release. + + -- Stefano Rivera Wed, 20 Dec 2023 17:08:12 -0400 + hatchling (1.18.0-1) unstable; urgency=medium * New upstream release. diff -Nru hatchling-1.18.0/pyproject.toml hatchling-1.21.0/pyproject.toml --- hatchling-1.18.0/pyproject.toml 2020-02-02 00:00:00.000000000 +0000 +++ hatchling-1.21.0/pyproject.toml 2020-02-02 00:00:00.000000000 +0000 @@ -28,6 +28,7 @@ "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Software Development :: Build Tools", diff -Nru hatchling-1.18.0/scripts/update_licenses.py hatchling-1.21.0/scripts/update_licenses.py --- hatchling-1.18.0/scripts/update_licenses.py 2020-02-02 00:00:00.000000000 +0000 +++ hatchling-1.21.0/scripts/update_licenses.py 2020-02-02 00:00:00.000000000 +0000 @@ -16,14 +16,14 @@ try: response = httpx.get(url) response.raise_for_status() - except Exception: + except Exception: # noqa: BLE001 time.sleep(1) continue else: return json.loads(response.content.decode('utf-8')) message = 'Download failed' - raise Exception(message) + raise ConnectionError(message) def main(): diff -Nru hatchling-1.18.0/src/hatchling/__about__.py hatchling-1.21.0/src/hatchling/__about__.py --- hatchling-1.18.0/src/hatchling/__about__.py 2020-02-02 00:00:00.000000000 +0000 +++ hatchling-1.21.0/src/hatchling/__about__.py 2020-02-02 00:00:00.000000000 +0000 @@ -1 +1 @@ -__version__ = '1.18.0' +__version__ = '1.21.0' diff -Nru hatchling-1.18.0/src/hatchling/bridge/app.py hatchling-1.21.0/src/hatchling/bridge/app.py --- hatchling-1.18.0/src/hatchling/bridge/app.py 2020-02-02 00:00:00.000000000 +0000 +++ hatchling-1.21.0/src/hatchling/bridge/app.py 2020-02-02 00:00:00.000000000 +0000 @@ -1,51 +1,10 @@ from __future__ import annotations import os -import pickle import sys from typing import Any -class InvokedApplication: - def __init__(self) -> None: - self.__verbosity = int(os.environ.get('HATCH_VERBOSE', '0')) - int(os.environ.get('HATCH_QUIET', '0')) - - @property - def verbosity(self) -> int: - return self.__verbosity - - def display(self, *args: Any, **kwargs: Any) -> None: - send_app_command('display', *args, **kwargs) - - def display_info(self, *args: Any, **kwargs: Any) -> None: - send_app_command('display_info', *args, **kwargs) - - def display_waiting(self, *args: Any, **kwargs: Any) -> None: - send_app_command('display_waiting', *args, **kwargs) - - def display_success(self, *args: Any, **kwargs: Any) -> None: - send_app_command('display_success', *args, **kwargs) - - def display_warning(self, *args: Any, **kwargs: Any) -> None: - send_app_command('display_warning', *args, **kwargs) - - def display_error(self, *args: Any, **kwargs: Any) -> None: - send_app_command('display_error', *args, **kwargs) - - def display_debug(self, *args: Any, **kwargs: Any) -> None: - send_app_command('display_debug', *args, **kwargs) - - def display_mini_header(self, *args: Any, **kwargs: Any) -> None: - send_app_command('display_mini_header', *args, **kwargs) - - def abort(self, *args: Any, **kwargs: Any) -> None: - send_app_command('abort', *args, **kwargs) - sys.exit(kwargs.get('code', 1)) - - def get_safe_application(self) -> SafeApplication: - return SafeApplication(self) - - class Application: """ The way output is displayed can be [configured](../config/hatch.md#terminal) by users. @@ -65,46 +24,47 @@ """ return self.__verbosity - def display(self, message: str = '', **kwargs: Any) -> None: + @staticmethod + def display(message: str = '', **kwargs: Any) -> None: # noqa: ARG004 # Do not document - print(message) + _display(message) - def display_info(self, message: str = '', **kwargs: Any) -> None: + def display_info(self, message: str = '', **kwargs: Any) -> None: # noqa: ARG002 """ Meant to be used for messages conveying basic information. """ if self.__verbosity >= 0: - print(message) + _display(message) - def display_waiting(self, message: str = '', **kwargs: Any) -> None: + def display_waiting(self, message: str = '', **kwargs: Any) -> None: # noqa: ARG002 """ Meant to be used for messages shown before potentially time consuming operations. """ if self.__verbosity >= 0: - print(message) + _display(message) - def display_success(self, message: str = '', **kwargs: Any) -> None: + def display_success(self, message: str = '', **kwargs: Any) -> None: # noqa: ARG002 """ Meant to be used for messages indicating some positive outcome. """ if self.__verbosity >= 0: - print(message) + _display(message) - def display_warning(self, message: str = '', **kwargs: Any) -> None: + def display_warning(self, message: str = '', **kwargs: Any) -> None: # noqa: ARG002 """ Meant to be used for messages conveying important information. """ if self.__verbosity >= -1: - print(message) + _display(message) - def display_error(self, message: str = '', **kwargs: Any) -> None: + def display_error(self, message: str = '', **kwargs: Any) -> None: # noqa: ARG002 """ Meant to be used for messages indicating some unrecoverable error. """ if self.__verbosity >= -2: # noqa: PLR2004 - print(message) + _display(message) - def display_debug(self, message: str = '', level: int = 1, **kwargs: Any) -> None: + def display_debug(self, message: str = '', level: int = 1, **kwargs: Any) -> None: # noqa: ARG002 """ Meant to be used for messages that are not useful for most user experiences. The `level` option must be between 1 and 3 (inclusive). @@ -112,19 +72,20 @@ if not 1 <= level <= 3: # noqa: PLR2004 error_message = 'Debug output can only have verbosity levels between 1 and 3 (inclusive)' raise ValueError(error_message) - elif self.__verbosity >= level: - print(message) - def display_mini_header(self, message: str = '', **kwargs: Any) -> None: + if self.__verbosity >= level: + _display(message) + + def display_mini_header(self, message: str = '', **kwargs: Any) -> None: # noqa: ARG002 if self.__verbosity >= 0: - print(f'[{message}]') + _display(f'[{message}]') - def abort(self, message: str = '', code: int = 1, **kwargs: Any) -> None: + def abort(self, message: str = '', code: int = 1, **kwargs: Any) -> None: # noqa: ARG002 """ Terminate the program with the given return code. """ if message and self.__verbosity >= -2: # noqa: PLR2004 - print(message) + _display(message) sys.exit(code) @@ -133,7 +94,7 @@ class SafeApplication: - def __init__(self, app: InvokedApplication | Application) -> None: + def __init__(self, app: Application) -> None: self.abort = app.abort self.verbosity = app.verbosity self.display = app.display @@ -146,19 +107,4 @@ self.display_mini_header = app.display_mini_header -def format_app_command(method: str, *args: Any, **kwargs: Any) -> str: - procedure = pickle.dumps((method, args, kwargs), 4) - - return f"__HATCH__:{''.join('%02x' % i for i in procedure)}" - - -def get_application(*, called_by_app: bool) -> InvokedApplication | Application: - return InvokedApplication() if called_by_app else Application() - - -def send_app_command(method: str, *args: Any, **kwargs: Any) -> None: - _send_app_command(format_app_command(method, *args, **kwargs)) - - -def _send_app_command(command: str) -> None: - print(command) +_display = print diff -Nru hatchling-1.18.0/src/hatchling/build.py hatchling-1.21.0/src/hatchling/build.py --- hatchling-1.18.0/src/hatchling/build.py 2020-02-02 00:00:00.000000000 +0000 +++ hatchling-1.21.0/src/hatchling/build.py 2020-02-02 00:00:00.000000000 +0000 @@ -11,10 +11,10 @@ 'get_requires_for_build_sdist', 'get_requires_for_build_wheel', ] -__all__.append('__all__') +__all__ += ['__all__'] -def get_requires_for_build_sdist(config_settings: dict[str, Any] | None = None) -> list[str]: +def get_requires_for_build_sdist(config_settings: dict[str, Any] | None = None) -> list[str]: # noqa: ARG001 """ https://peps.python.org/pep-0517/#get-requires-for-build-sdist """ @@ -24,17 +24,17 @@ return builder.config.dependencies -def build_sdist(sdist_directory: str, config_settings: dict[str, Any] | None = None) -> str: +def build_sdist(sdist_directory: str, config_settings: dict[str, Any] | None = None) -> str: # noqa: ARG001 """ https://peps.python.org/pep-0517/#build-sdist """ from hatchling.builders.sdist import SdistBuilder builder = SdistBuilder(os.getcwd()) - return os.path.basename(next(builder.build(sdist_directory, ['standard']))) + return os.path.basename(next(builder.build(directory=sdist_directory, versions=['standard']))) -def get_requires_for_build_wheel(config_settings: dict[str, Any] | None = None) -> list[str]: +def get_requires_for_build_wheel(config_settings: dict[str, Any] | None = None) -> list[str]: # noqa: ARG001 """ https://peps.python.org/pep-0517/#get-requires-for-build-wheel """ @@ -45,7 +45,9 @@ def build_wheel( - wheel_directory: str, config_settings: dict[str, Any] | None = None, metadata_directory: str | None = None + wheel_directory: str, + config_settings: dict[str, Any] | None = None, # noqa: ARG001 + metadata_directory: str | None = None, # noqa: ARG001 ) -> str: """ https://peps.python.org/pep-0517/#build-wheel @@ -53,10 +55,10 @@ from hatchling.builders.wheel import WheelBuilder builder = WheelBuilder(os.getcwd()) - return os.path.basename(next(builder.build(wheel_directory, ['standard']))) + return os.path.basename(next(builder.build(directory=wheel_directory, versions=['standard']))) -def get_requires_for_build_editable(config_settings: dict[str, Any] | None = None) -> list[str]: +def get_requires_for_build_editable(config_settings: dict[str, Any] | None = None) -> list[str]: # noqa: ARG001 """ https://peps.python.org/pep-0660/#get-requires-for-build-editable """ @@ -67,7 +69,9 @@ def build_editable( - wheel_directory: str, config_settings: dict[str, Any] | None = None, metadata_directory: str | None = None + wheel_directory: str, + config_settings: dict[str, Any] | None = None, # noqa: ARG001 + metadata_directory: str | None = None, # noqa: ARG001 ) -> str: """ https://peps.python.org/pep-0660/#build-editable @@ -75,7 +79,7 @@ from hatchling.builders.wheel import WheelBuilder builder = WheelBuilder(os.getcwd()) - return os.path.basename(next(builder.build(wheel_directory, ['editable']))) + return os.path.basename(next(builder.build(directory=wheel_directory, versions=['editable']))) # Any builder that has build-time hooks like Hatchling and setuptools cannot technically keep PEP 517's identical @@ -91,10 +95,12 @@ # See: https://github.com/pypa/pip/blob/22.2.2/src/pip/_internal/operations/build/build_tracker.py#L41-L51 # Example use case: https://github.com/pypa/hatch/issues/532 if 'PIP_BUILD_TRACKER' not in os.environ: - __all__.append('prepare_metadata_for_build_editable') - __all__.append('prepare_metadata_for_build_wheel') + __all__ += ['prepare_metadata_for_build_editable', 'prepare_metadata_for_build_wheel'] - def prepare_metadata_for_build_wheel(metadata_directory: str, config_settings: dict[str, Any] | None = None) -> str: + def prepare_metadata_for_build_wheel( + metadata_directory: str, + config_settings: dict[str, Any] | None = None, # noqa: ARG001 + ) -> str: """ https://peps.python.org/pep-0517/#prepare-metadata-for-build-wheel """ @@ -112,7 +118,8 @@ return os.path.basename(directory) def prepare_metadata_for_build_editable( - metadata_directory: str, config_settings: dict[str, Any] | None = None + metadata_directory: str, + config_settings: dict[str, Any] | None = None, # noqa: ARG001 ) -> str: """ https://peps.python.org/pep-0660/#prepare-metadata-for-build-editable diff -Nru hatchling-1.18.0/src/hatchling/builders/app.py hatchling-1.21.0/src/hatchling/builders/app.py --- hatchling-1.18.0/src/hatchling/builders/app.py 2020-02-02 00:00:00.000000000 +0000 +++ hatchling-1.21.0/src/hatchling/builders/app.py 2020-02-02 00:00:00.000000000 +0000 @@ -34,7 +34,8 @@ f'Script #{i} of field `tool.hatch.build.targets.{self.plugin_name}.scripts` must be a string' ) raise TypeError(message) - elif script not in known_scripts: + + if script not in known_scripts: message = f'Unknown script in field `tool.hatch.build.targets.{self.plugin_name}.scripts`: {script}' raise ValueError(message) @@ -88,17 +89,25 @@ def get_version_api(self) -> dict[str, Callable]: return {'bootstrap': self.build_bootstrap} - def get_default_versions(self) -> list[str]: + def get_default_versions(self) -> list[str]: # noqa: PLR6301 return ['bootstrap'] - def clean(self, directory: str, versions: list[str]) -> None: + def clean( # noqa: PLR6301 + self, + directory: str, + versions: list[str], # noqa: ARG002 + ) -> None: import shutil app_dir = os.path.join(directory, 'app') if os.path.isdir(app_dir): shutil.rmtree(app_dir) - def build_bootstrap(self, directory: str, **build_data: Any) -> str: + def build_bootstrap( + self, + directory: str, + **build_data: Any, # noqa: ARG002 + ) -> str: import shutil import tempfile @@ -176,16 +185,13 @@ def cargo_build(self, *args: Any, **kwargs: Any) -> None: import subprocess - if not self.app.verbosity: + if self.app.verbosity < 0: kwargs['stdout'] = subprocess.PIPE kwargs['stderr'] = subprocess.STDOUT - process = subprocess.run(*args, **kwargs) + process = subprocess.run(*args, **kwargs) # noqa: PLW1510 if process.returncode: - message = f'Compilation of failed (code {process.returncode})' - if not self.app.verbosity: - message = f'{process.stdout.decode("utf-8")}\n{message}' - + message = f'Compilation failed (code {process.returncode})' raise OSError(message) @classmethod diff -Nru hatchling-1.18.0/src/hatchling/builders/config.py hatchling-1.21.0/src/hatchling/builders/config.py --- hatchling-1.18.0/src/hatchling/builders/config.py 2020-02-02 00:00:00.000000000 +0000 +++ hatchling-1.21.0/src/hatchling/builders/config.py 2020-02-02 00:00:00.000000000 +0000 @@ -48,6 +48,10 @@ self.__exclude_patterns: list[str] | None = None self.__artifact_patterns: list[str] | None = None + # This is used when the only file selection is based on forced inclusion or build-time artifacts. This + # instructs to `exclude` every encountered path without doing pattern matching that matches everything. + self.__exclude_all: bool = False + # Modified at build time self.build_artifact_spec: pathspec.GitIgnoreSpec | None = None self.build_force_include: dict[str, str] = {} @@ -90,7 +94,6 @@ or self.path_is_artifact(relative_path) or ( not (self.only_packages and not is_package) - and not self.path_is_reserved(relative_path) and not self.path_is_excluded(relative_path) and (explicit or self.path_is_included(relative_path)) ) @@ -103,6 +106,9 @@ return self.include_spec.match_file(relative_path) def path_is_excluded(self, relative_path: str) -> bool: + if self.__exclude_all: + return True + if self.exclude_spec is None: return False @@ -155,16 +161,16 @@ if not isinstance(include_pattern, str): message = f'Pattern #{i} in field `{include_location}` must be a string' raise TypeError(message) - elif not include_pattern: + + if not include_pattern: message = f'Pattern #{i} in field `{include_location}` cannot be an empty string' raise ValueError(message) all_include_patterns.append(include_pattern) - for relative_path in self.packages: - # Matching only at the root requires a forward slash, back slashes do not work. As such, - # normalize to forward slashes for consistency. - all_include_patterns.append(f"/{relative_path.replace(os.sep, '/')}/") + # Matching only at the root requires a forward slash, back slashes do not work. As such, + # normalize to forward slashes for consistency. + all_include_patterns.extend(f"/{relative_path.replace(os.sep, '/')}/" for relative_path in self.packages) if all_include_patterns: self.__include_spec = pathspec.GitIgnoreSpec.from_lines(all_include_patterns) @@ -194,7 +200,8 @@ if not isinstance(exclude_pattern, str): message = f'Pattern #{i} in field `{exclude_location}` must be a string' raise TypeError(message) - elif not exclude_pattern: + + if not exclude_pattern: message = f'Pattern #{i} in field `{exclude_location}` cannot be an empty string' raise ValueError(message) @@ -231,7 +238,8 @@ if not isinstance(artifact_pattern, str): message = f'Pattern #{i} in field `{artifact_location}` must be a string' raise TypeError(message) - elif not artifact_pattern: + + if not artifact_pattern: message = f'Pattern #{i} in field `{artifact_location}` cannot be an empty string' raise ValueError(message) @@ -273,16 +281,19 @@ hook_config[hook_name] = config - final_hook_config = {} if not env_var_enabled(BuildEnvVars.NO_HOOKS): all_hooks_enabled = env_var_enabled(BuildEnvVars.HOOKS_ENABLE) - for hook_name, config in hook_config.items(): + final_hook_config = { + hook_name: config + for hook_name, config in hook_config.items() if ( all_hooks_enabled or config.get('enable-by-default', True) or env_var_enabled(f'{BuildEnvVars.HOOK_ENABLE_PREFIX}{hook_name.upper()}') - ): - final_hook_config[hook_name] = config + ) + } + else: + final_hook_config = {} self.__hook_config = final_hook_config @@ -381,15 +392,16 @@ raise TypeError(message) all_features: dict[str, None] = {} - for i, feature in enumerate(require_runtime_features, 1): - if not isinstance(feature, str): + for i, raw_feature in enumerate(require_runtime_features, 1): + if not isinstance(raw_feature, str): message = f'Feature #{i} of field `{features_location}` must be a string' raise TypeError(message) - elif not feature: + + if not raw_feature: message = f'Feature #{i} of field `{features_location}` cannot be an empty string' raise ValueError(message) - feature = normalize_project_name(feature) + feature = normalize_project_name(raw_feature) if feature not in self.builder.metadata.core.optional_dependencies: message = ( f'Feature `{feature}` of field `{features_location}` is not defined in ' @@ -470,7 +482,8 @@ if not isinstance(dev_mode_dir, str): message = f'Directory #{i} in field `{dev_mode_dirs_location}` must be a string' raise TypeError(message) - elif not dev_mode_dir: + + if not dev_mode_dir: message = f'Directory #{i} in field `{dev_mode_dirs_location}` cannot be an empty string' raise ValueError(message) @@ -515,7 +528,8 @@ f'Version #{i} in field `tool.hatch.build.targets.{self.plugin_name}.versions` must be a string' ) raise TypeError(message) - elif not version: + + if not version: message = ( f'Version #{i} in field `tool.hatch.build.targets.{self.plugin_name}.versions` ' f'cannot be an empty string' @@ -581,7 +595,8 @@ if not isinstance(hook_require_runtime_dependencies, bool): message = f'Option `require-runtime-dependencies` of build hook `{hook_name}` must be a boolean' raise TypeError(message) - elif hook_require_runtime_dependencies: + + if hook_require_runtime_dependencies: require_runtime_dependencies = True hook_require_runtime_features = config.get('require-runtime-features', []) @@ -589,21 +604,22 @@ message = f'Option `require-runtime-features` of build hook `{hook_name}` must be an array' raise TypeError(message) - for i, feature in enumerate(hook_require_runtime_features, 1): - if not isinstance(feature, str): + for i, raw_feature in enumerate(hook_require_runtime_features, 1): + if not isinstance(raw_feature, str): message = ( f'Feature #{i} of option `require-runtime-features` of build hook `{hook_name}` ' f'must be a string' ) raise TypeError(message) - elif not feature: + + if not raw_feature: message = ( f'Feature #{i} of option `require-runtime-features` of build hook `{hook_name}` ' f'cannot be an empty string' ) raise ValueError(message) - feature = normalize_project_name(feature) + feature = normalize_project_name(raw_feature) if feature not in self.builder.metadata.core.optional_dependencies: message = ( f'Feature `{feature}` of option `require-runtime-features` of build hook `{hook_name}` ' @@ -658,17 +674,15 @@ if not isinstance(source, str): message = f'Source #{i} in field `{sources_location}` must be a string' raise TypeError(message) - elif not source: + + if not source: message = f'Source #{i} in field `{sources_location}` cannot be an empty string' raise ValueError(message) sources[normalize_relative_directory(source)] = '' elif isinstance(raw_sources, dict): - for i, (source, path) in enumerate(raw_sources.items(), 1): - if not source: - message = f'Source #{i} in field `{sources_location}` cannot be an empty string' - raise ValueError(message) - elif not isinstance(path, str): + for source, path in raw_sources.items(): + if not isinstance(path, str): message = f'Path for source `{source}` in field `{sources_location}` must be a string' raise TypeError(message) @@ -678,13 +692,13 @@ else: normalized_path += os.sep - sources[normalize_relative_directory(source)] = normalized_path + sources[normalize_relative_directory(source) if source else source] = normalized_path else: message = f'Field `{sources_location}` must be a mapping or array of strings' raise TypeError(message) for relative_path in self.packages: - source, package = os.path.split(relative_path) + source, _package = os.path.split(relative_path) if source and normalize_relative_directory(relative_path) not in sources: sources[normalize_relative_directory(source)] = '' @@ -711,7 +725,8 @@ if not isinstance(package, str): message = f'Package #{i} in field `{package_location}` must be a string' raise TypeError(message) - elif not package: + + if not package: message = f'Package #{i} in field `{package_location}` cannot be an empty string' raise ValueError(message) @@ -738,10 +753,12 @@ if not source: message = f'Source #{i} in field `{force_include_location}` cannot be an empty string' raise ValueError(message) - elif not isinstance(relative_path, str): + + if not isinstance(relative_path, str): message = f'Path for source `{source}` in field `{force_include_location}` must be a string' raise TypeError(message) - elif not relative_path: + + if not relative_path: message = ( f'Path for source `{source}` in field `{force_include_location}` cannot be an empty string' ) @@ -777,7 +794,8 @@ if not normalized_path or normalized_path.startswith(('~', '..')): message = f'Path #{i} in field `{only_include_location}` must be relative: {relative_path}' raise ValueError(message) - elif normalized_path in inclusion_map: + + if normalized_path in inclusion_map: message = f'Duplicate path in field `{only_include_location}`: {normalized_path}' raise ValueError(message) @@ -790,6 +808,9 @@ def get_distribution_path(self, relative_path: str) -> str: # src/foo/bar.py -> foo/bar.py for source, replacement in self.sources.items(): + if not source: + return replacement + relative_path + if relative_path.startswith(source): return relative_path.replace(source, replacement, 1) @@ -829,10 +850,12 @@ if exact_line == 'syntax: glob': glob_mode = True continue - elif exact_line.startswith('syntax: '): + + if exact_line.startswith('syntax: '): glob_mode = False continue - elif glob_mode: + + if glob_mode: patterns.append(line) return patterns @@ -843,23 +866,26 @@ return os.path.normpath(build_directory) - def default_include(self) -> list: + def default_include(self) -> list: # noqa: PLR6301 return [] - def default_exclude(self) -> list: + def default_exclude(self) -> list: # noqa: PLR6301 return [] - def default_packages(self) -> list: + def default_packages(self) -> list: # noqa: PLR6301 return [] - def default_only_include(self) -> list: + def default_only_include(self) -> list: # noqa: PLR6301 return [] - def default_global_exclude(self) -> list[str]: + def default_global_exclude(self) -> list[str]: # noqa: PLR6301 patterns = ['*.py[cdo]', f'/{DEFAULT_BUILD_DIRECTORY}'] patterns.sort() return patterns + def set_exclude_all(self) -> None: + self.__exclude_all = True + def get_force_include(self) -> dict[str, str]: force_include = self.force_include.copy() force_include.update(self.build_force_include) @@ -881,11 +907,11 @@ # old/ -> new/ # old.ext -> new.ext if source.startswith(f'{self.root}{os.sep}'): - self.build_reserved_paths.add(os.path.relpath(source, self.root)) + self.build_reserved_paths.add(self.get_distribution_path(os.path.relpath(source, self.root))) # Ignore target files only # ../out.ext -> ../in.ext elif os.path.isfile(source): - self.build_reserved_paths.add(target) + self.build_reserved_paths.add(self.get_distribution_path(target)) yield finally: @@ -896,9 +922,9 @@ def env_var_enabled(env_var: str, *, default: bool = False) -> bool: if env_var in os.environ: - return os.environ[env_var] in ('1', 'true') - else: - return default + return os.environ[env_var] in {'1', 'true'} + + return default BuilderConfigBound = TypeVar('BuilderConfigBound', bound=BuilderConfig) diff -Nru hatchling-1.18.0/src/hatchling/builders/constants.py hatchling-1.21.0/src/hatchling/builders/constants.py --- hatchling-1.18.0/src/hatchling/builders/constants.py 2020-02-02 00:00:00.000000000 +0000 +++ hatchling-1.21.0/src/hatchling/builders/constants.py 2020-02-02 00:00:00.000000000 +0000 @@ -1,27 +1,25 @@ DEFAULT_BUILD_DIRECTORY = 'dist' -EXCLUDED_DIRECTORIES = frozenset( - ( - # Python bytecode - '__pycache__', - # Git - '.git', - # Mercurial - '.hg', - # Hatch - '.hatch', - # tox - '.tox', - # nox - '.nox', - # Ruff - '.ruff_cache', - # pytest - '.pytest_cache', - # Mypy - '.mypy_cache', - ) -) +EXCLUDED_DIRECTORIES = frozenset(( + # Python bytecode + '__pycache__', + # Git + '.git', + # Mercurial + '.hg', + # Hatch + '.hatch', + # tox + '.tox', + # nox + '.nox', + # Ruff + '.ruff_cache', + # pytest + '.pytest_cache', + # Mypy + '.mypy_cache', +)) class BuildEnvVars: diff -Nru hatchling-1.18.0/src/hatchling/builders/custom.py hatchling-1.21.0/src/hatchling/builders/custom.py --- hatchling-1.18.0/src/hatchling/builders/custom.py 2020-02-02 00:00:00.000000000 +0000 +++ hatchling-1.21.0/src/hatchling/builders/custom.py 2020-02-02 00:00:00.000000000 +0000 @@ -35,7 +35,8 @@ if not isinstance(build_script, str): message = f'Option `path` for builder `{cls.PLUGIN_NAME}` must be a string' raise TypeError(message) - elif not build_script: + + if not build_script: message = f'Option `path` for builder `{cls.PLUGIN_NAME}` must not be empty if defined' raise ValueError(message) diff -Nru hatchling-1.18.0/src/hatchling/builders/hooks/custom.py hatchling-1.21.0/src/hatchling/builders/hooks/custom.py --- hatchling-1.18.0/src/hatchling/builders/hooks/custom.py 2020-02-02 00:00:00.000000000 +0000 +++ hatchling-1.21.0/src/hatchling/builders/hooks/custom.py 2020-02-02 00:00:00.000000000 +0000 @@ -22,7 +22,8 @@ if not isinstance(build_script, str): message = f'Option `path` for build hook `{cls.PLUGIN_NAME}` must be a string' raise TypeError(message) - elif not build_script: + + if not build_script: message = f'Option `path` for build hook `{cls.PLUGIN_NAME}` must not be empty if defined' raise ValueError(message) diff -Nru hatchling-1.18.0/src/hatchling/builders/hooks/plugin/interface.py hatchling-1.21.0/src/hatchling/builders/hooks/plugin/interface.py --- hatchling-1.18.0/src/hatchling/builders/hooks/plugin/interface.py 2020-02-02 00:00:00.000000000 +0000 +++ hatchling-1.21.0/src/hatchling/builders/hooks/plugin/interface.py 2020-02-02 00:00:00.000000000 +0000 @@ -13,29 +13,25 @@ """ Example usage: - === ":octicons-file-code-16: plugin.py" + ```python tab="plugin.py" + from hatchling.builders.hooks.plugin.interface import BuildHookInterface - ```python - from hatchling.builders.hooks.plugin.interface import BuildHookInterface + class SpecialBuildHook(BuildHookInterface): + PLUGIN_NAME = 'special' + ... + ``` - class SpecialBuildHook(BuildHookInterface): - PLUGIN_NAME = 'special' - ... - ``` - - === ":octicons-file-code-16: hooks.py" - - ```python - from hatchling.plugin import hookimpl + ```python tab="hooks.py" + from hatchling.plugin import hookimpl - from .plugin import SpecialBuildHook + from .plugin import SpecialBuildHook - @hookimpl - def hatch_register_build_hook(): - return SpecialBuildHook - ``` + @hookimpl + def hatch_register_build_hook(): + return SpecialBuildHook + ``` """ PLUGIN_NAME = '' @@ -83,19 +79,10 @@ """ The cumulative hook configuration. - === ":octicons-file-code-16: pyproject.toml" - - ```toml - [tool.hatch.build.hooks.] - [tool.hatch.build.targets..hooks.] - ``` - - === ":octicons-file-code-16: hatch.toml" - - ```toml - [build.hooks.] - [build.targets..hooks.] - ``` + ```toml config-example + [tool.hatch.build.hooks.] + [tool.hatch.build.targets..hooks.] + ``` """ return self.__config diff -Nru hatchling-1.18.0/src/hatchling/builders/hooks/version.py hatchling-1.21.0/src/hatchling/builders/hooks/version.py --- hatchling-1.18.0/src/hatchling/builders/hooks/version.py 2020-02-02 00:00:00.000000000 +0000 +++ hatchling-1.21.0/src/hatchling/builders/hooks/version.py 2020-02-02 00:00:00.000000000 +0000 @@ -23,7 +23,8 @@ if not isinstance(path, str): message = f'Option `path` for build hook `{self.PLUGIN_NAME}` must be a string' raise TypeError(message) - elif not path: + + if not path: message = f'Option `path` for build hook `{self.PLUGIN_NAME}` is required' raise ValueError(message) @@ -55,10 +56,14 @@ return self.__config_pattern - def initialize(self, version: str, build_data: dict[str, Any]) -> None: + def initialize( + self, + version: str, # noqa: ARG002 + build_data: dict[str, Any], + ) -> None: version_file = VersionFile(self.root, self.config_path) if self.config_pattern: - version_file.read(self.config_pattern) + version_file.read(pattern=self.config_pattern) version_file.set_version(self.metadata.version) else: version_file.write(self.metadata.version, self.config_template) diff -Nru hatchling-1.18.0/src/hatchling/builders/plugin/interface.py hatchling-1.21.0/src/hatchling/builders/plugin/interface.py --- hatchling-1.18.0/src/hatchling/builders/plugin/interface.py 2020-02-02 00:00:00.000000000 +0000 +++ hatchling-1.21.0/src/hatchling/builders/plugin/interface.py 2020-02-02 00:00:00.000000000 +0000 @@ -29,29 +29,25 @@ """ Example usage: - === ":octicons-file-code-16: plugin.py" - - ```python - from hatchling.builders.plugin.interface import BuilderInterface + ```python tab="plugin.py" + from hatchling.builders.plugin.interface import BuilderInterface - class SpecialBuilder(BuilderInterface): - PLUGIN_NAME = 'special' - ... - ``` + class SpecialBuilder(BuilderInterface): + PLUGIN_NAME = 'special' + ... + ``` - === ":octicons-file-code-16: hooks.py" + ```python tab="hooks.py" + from hatchling.plugin import hookimpl - ```python - from hatchling.plugin import hookimpl + from .plugin import SpecialBuilder - from .plugin import SpecialBuilder - - @hookimpl - def hatch_register_builder(): - return SpecialBuilder - ``` + @hookimpl + def hatch_register_builder(): + return SpecialBuilder + ``` """ PLUGIN_NAME = '' @@ -82,12 +78,13 @@ def build( self, + *, directory: str | None = None, versions: list[str] | None = None, hooks_only: bool | None = None, clean: bool | None = None, clean_hooks_after: bool | None = None, - clean_only: bool | None = False, # noqa: FBT002 + clean_only: bool | None = False, ) -> Generator[str, None, None]: # Fail early for invalid project metadata self.metadata.validate_fields() @@ -195,6 +192,10 @@ is_package = '__init__.py' in files for f in files: relative_file_path = os.path.join(relative_path, f) + distribution_path = self.config.get_distribution_path(relative_file_path) + if self.config.path_is_reserved(distribution_path): + continue + if self.config.include_path(relative_file_path, is_package=is_package): yield IncludedFile( os.path.join(root, f), relative_file_path, self.config.get_distribution_path(relative_file_path) @@ -218,22 +219,28 @@ files.sort() for f in files: relative_file_path = os.path.join(target_path, relative_directory, f) - if not self.config.path_is_reserved(relative_file_path): + distribution_path = self.config.get_distribution_path(relative_file_path) + if not self.config.path_is_reserved(distribution_path): yield IncludedFile( os.path.join(root, f), '' if external else relative_file_path, - self.config.get_distribution_path(relative_file_path), + distribution_path, ) + else: + msg = f'Forced include not found: {source}' + raise FileNotFoundError(msg) def recurse_explicit_files(self, inclusion_map: dict[str, str]) -> Iterable[IncludedFile]: for source, target_path in inclusion_map.items(): external = not source.startswith(self.root) if os.path.isfile(source): - yield IncludedFile( - source, - '' if external else os.path.relpath(source, self.root), - self.config.get_distribution_path(target_path), - ) + distribution_path = self.config.get_distribution_path(target_path) + if not self.config.path_is_reserved(distribution_path): + yield IncludedFile( + source, + '' if external else os.path.relpath(source, self.root), + self.config.get_distribution_path(target_path), + ) elif os.path.isdir(source): for root, dirs, files in safe_walk(source): relative_directory = get_relative_path(root, source) @@ -244,11 +251,13 @@ is_package = '__init__.py' in files for f in files: relative_file_path = os.path.join(target_path, relative_directory, f) + distribution_path = self.config.get_distribution_path(relative_file_path) + if self.config.path_is_reserved(distribution_path): + continue + if self.config.include_path(relative_file_path, explicit=True, is_package=is_package): yield IncludedFile( - os.path.join(root, f), - '' if external else relative_file_path, - self.config.get_distribution_path(relative_file_path), + os.path.join(root, f), '' if external else relative_file_path, distribution_path ) @property @@ -324,17 +333,9 @@ @property def build_config(self) -> dict[str, Any]: """ - === ":octicons-file-code-16: pyproject.toml" - - ```toml - [tool.hatch.build] - ``` - - === ":octicons-file-code-16: hatch.toml" - - ```toml - [build] - ``` + ```toml config-example + [tool.hatch.build] + ``` """ if self.__build_config is None: self.__build_config = self.metadata.hatch.build_config @@ -344,17 +345,9 @@ @property def target_config(self) -> dict[str, Any]: """ - === ":octicons-file-code-16: pyproject.toml" - - ```toml - [tool.hatch.build.targets.] - ``` - - === ":octicons-file-code-16: hatch.toml" - - ```toml - [build.targets.] - ``` + ```toml config-example + [tool.hatch.build.targets.] + ``` """ if self.__target_config is None: target_config: dict[str, Any] = self.metadata.hatch.build_targets.get(self.PLUGIN_NAME, {}) @@ -408,13 +401,13 @@ """ return list(self.get_version_api()) - def get_default_build_data(self) -> dict[str, Any]: + def get_default_build_data(self) -> dict[str, Any]: # noqa: PLR6301 """ A mapping that can be modified by [build hooks](../build-hook/reference.md) to influence the behavior of builds. """ return {} - def set_build_data_defaults(self, build_data: dict[str, Any]) -> None: + def set_build_data_defaults(self, build_data: dict[str, Any]) -> None: # noqa: PLR6301 build_data.setdefault('artifacts', []) build_data.setdefault('force_include', {}) @@ -436,4 +429,4 @@ """ https://peps.python.org/pep-0427/#escaping-and-unicode """ - return re.sub(r'[^\w\d.]+', '_', file_name, re.UNICODE) + return re.sub(r'[^\w\d.]+', '_', file_name, flags=re.UNICODE) diff -Nru hatchling-1.18.0/src/hatchling/builders/sdist.py hatchling-1.21.0/src/hatchling/builders/sdist.py --- hatchling-1.18.0/src/hatchling/builders/sdist.py 2020-02-02 00:00:00.000000000 +0000 +++ hatchling-1.21.0/src/hatchling/builders/sdist.py 2020-02-02 00:00:00.000000000 +0000 @@ -74,7 +74,7 @@ setattr(self, name, attr) return attr - def __enter__(self) -> SdistArchive: + def __enter__(self) -> SdistArchive: # noqa: PYI034 return self def __exit__( @@ -150,10 +150,14 @@ def get_version_api(self) -> dict[str, Callable]: return {'standard': self.build_standard} - def get_default_versions(self) -> list[str]: + def get_default_versions(self) -> list[str]: # noqa: PLR6301 return ['standard'] - def clean(self, directory: str, versions: list[str]) -> None: + def clean( # noqa: PLR6301 + self, + directory: str, + versions: list[str], # noqa: ARG002 + ) -> None: for filename in os.listdir(directory): if filename.endswith('.tar.gz'): os.remove(os.path.join(directory, filename)) @@ -245,8 +249,8 @@ if dependencies: contents += ' install_requires=[\n' - for specifier in dependencies: - specifier = specifier.replace("'", '"') + for raw_specifier in dependencies: + specifier = raw_specifier.replace("'", '"') contents += f' {specifier!r},\n' contents += ' ],\n' @@ -260,8 +264,8 @@ contents += f' {option!r}: [\n' - for specifier in specifiers: - specifier = specifier.replace("'", '"') + for raw_specifier in specifiers: + specifier = raw_specifier.replace("'", '"') contents += f' {specifier!r},\n' contents += ' ],\n' @@ -319,11 +323,11 @@ return contents def get_default_build_data(self) -> dict[str, Any]: - force_include = { - os.path.join(self.root, 'pyproject.toml'): 'pyproject.toml', - os.path.join(self.root, DEFAULT_CONFIG_FILE): DEFAULT_CONFIG_FILE, - os.path.join(self.root, DEFAULT_BUILD_SCRIPT): DEFAULT_BUILD_SCRIPT, - } + force_include = {} + for filename in ['pyproject.toml', DEFAULT_CONFIG_FILE, DEFAULT_BUILD_SCRIPT]: + path = os.path.join(self.root, filename) + if os.path.exists(path): + force_include[path] = filename build_data = {'force_include': force_include, 'dependencies': []} for exclusion_files in self.config.vcs_exclusion_files.values(): @@ -338,8 +342,8 @@ license_files = self.metadata.core.license_files if license_files: for license_file in license_files: - license_file = normalize_relative_path(license_file) - force_include[os.path.join(self.root, license_file)] = license_file + relative_path = normalize_relative_path(license_file) + force_include[os.path.join(self.root, relative_path)] = relative_path return build_data diff -Nru hatchling-1.18.0/src/hatchling/builders/utils.py hatchling-1.21.0/src/hatchling/builders/utils.py --- hatchling-1.18.0/src/hatchling/builders/utils.py 2020-02-02 00:00:00.000000000 +0000 +++ hatchling-1.21.0/src/hatchling/builders/utils.py 2020-02-02 00:00:00.000000000 +0000 @@ -56,8 +56,8 @@ def normalize_inclusion_map(inclusion_map: dict[str, str], root: str) -> dict[str, str]: normalized_inclusion_map = {} - for source, relative_path in inclusion_map.items(): - source = os.path.expanduser(os.path.normpath(source)) + for raw_source, relative_path in inclusion_map.items(): + source = os.path.expanduser(os.path.normpath(raw_source)) if not os.path.isabs(source): source = os.path.abspath(os.path.join(root, source)) diff -Nru hatchling-1.18.0/src/hatchling/builders/wheel.py hatchling-1.21.0/src/hatchling/builders/wheel.py --- hatchling-1.18.0/src/hatchling/builders/wheel.py 2020-02-02 00:00:00.000000000 +0000 +++ hatchling-1.21.0/src/hatchling/builders/wheel.py 2020-02-02 00:00:00.000000000 +0000 @@ -4,10 +4,12 @@ import hashlib import os import stat +import sys import tempfile import zipfile +from functools import cached_property from io import StringIO -from typing import TYPE_CHECKING, Any, Callable, Iterable, Sequence, Tuple, cast +from typing import TYPE_CHECKING, Any, Callable, Iterable, NamedTuple, Sequence, Tuple, cast from hatchling.__about__ import __version__ from hatchling.builders.config import BuilderConfig @@ -35,6 +37,13 @@ TIME_TUPLE = Tuple[int, int, int, int, int, int] +class FileSelectionOptions(NamedTuple): + include: list[str] + exclude: list[str] + packages: list[str] + only_include: list[str] + + class RecordFile: def __init__(self) -> None: self.__file_obj = StringIO() @@ -46,7 +55,7 @@ def construct(self) -> str: return self.__file_obj.getvalue() - def __enter__(self) -> RecordFile: + def __enter__(self) -> RecordFile: # noqa: PYI034 return self def __exit__( @@ -139,7 +148,7 @@ return relative_path, f'sha256={hash_digest}', str(len(contents)) - def __enter__(self) -> WheelArchive: + def __enter__(self) -> WheelArchive: # noqa: PYI034 return self def __exit__( @@ -153,75 +162,73 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) - self.__include_defined: bool = bool( - self.target_config.get('include', self.build_config.get('include')) - or self.target_config.get('packages', self.build_config.get('packages')) - or self.target_config.get('only-include', self.build_config.get('only-include')) - ) - self.__include: list[str] = [] - self.__exclude: list[str] = [] - self.__packages: list[str] = [] - self.__only_include: list[str] = [] - self.__core_metadata_constructor: Callable[..., str] | None = None self.__shared_data: dict[str, str] | None = None self.__extra_metadata: dict[str, str] | None = None self.__strict_naming: bool | None = None self.__macos_max_compat: bool | None = None - def set_default_file_selection(self) -> None: - if self.__include or self.__exclude or self.__packages or self.__only_include: - return + @cached_property + def default_file_selection_options(self) -> FileSelectionOptions: + include = self.target_config.get('include', self.build_config.get('include', [])) + exclude = self.target_config.get('exclude', self.build_config.get('exclude', [])) + packages = self.target_config.get('packages', self.build_config.get('packages', [])) + only_include = self.target_config.get('only-include', self.build_config.get('only-include', [])) + + if include or packages or only_include: + return FileSelectionOptions(include, exclude, packages, only_include) for project_name in ( self.builder.normalize_file_name_component(self.builder.metadata.core.raw_name), self.builder.normalize_file_name_component(self.builder.metadata.core.name), ): if os.path.isfile(os.path.join(self.root, project_name, '__init__.py')): - self.__packages.append(project_name) - break - elif os.path.isfile(os.path.join(self.root, 'src', project_name, '__init__.py')): - self.__packages.append(f'src/{project_name}') - break - elif os.path.isfile(os.path.join(self.root, f'{project_name}.py')): - self.__only_include.append(f'{project_name}.py') - break - else: - from glob import glob + normalized_project_name = self.get_raw_fs_path_name(self.root, project_name) + return FileSelectionOptions([], exclude, [normalized_project_name], []) - possible_namespace_packages = glob(os.path.join(self.root, '*', project_name, '__init__.py')) - if len(possible_namespace_packages) == 1: - relative_path = os.path.relpath(possible_namespace_packages[0], self.root) - namespace = relative_path.split(os.sep)[0] - self.__packages.append(namespace) - break - else: - self.__include.append('*.py') - self.__exclude.append('test*') + if os.path.isfile(os.path.join(self.root, 'src', project_name, '__init__.py')): + normalized_project_name = self.get_raw_fs_path_name(os.path.join(self.root, 'src'), project_name) + return FileSelectionOptions([], exclude, [f'src/{normalized_project_name}'], []) + + module_file = f'{project_name}.py' + if os.path.isfile(os.path.join(self.root, module_file)): + normalized_project_name = self.get_raw_fs_path_name(self.root, module_file) + return FileSelectionOptions([], exclude, [], [module_file]) + + from glob import glob + + possible_namespace_packages = glob(os.path.join(self.root, '*', project_name, '__init__.py')) + if len(possible_namespace_packages) == 1: + relative_path = os.path.relpath(possible_namespace_packages[0], self.root) + namespace = relative_path.split(os.sep)[0] + return FileSelectionOptions([], exclude, [namespace], []) + + if self.bypass_selection or self.build_artifact_spec is not None or self.get_force_include(): + self.set_exclude_all() + return FileSelectionOptions([], exclude, [], []) + + message = ( + 'Unable to determine which files to ship inside the wheel using the following heuristics: ' + 'https://hatch.pypa.io/latest/plugins/builder/wheel/#default-file-selection\n\nAt least one ' + 'file selection option must be defined in the `tool.hatch.build.targets.wheel` table, see: ' + 'https://hatch.pypa.io/latest/config/build/\n\nAs an example, if you intend to ship a ' + 'directory named `foo` that resides within a `src` directory located at the root of your ' + 'project, you can define the following:\n\n[tool.hatch.build.targets.wheel]\n' + 'packages = ["src/foo"]' + ) + raise ValueError(message) def default_include(self) -> list[str]: - if not self.__include_defined: - self.set_default_file_selection() - - return self.__include + return self.default_file_selection_options.include def default_exclude(self) -> list[str]: - if not self.__include_defined: - self.set_default_file_selection() - - return self.__exclude + return self.default_file_selection_options.exclude def default_packages(self) -> list[str]: - if not self.__include_defined: - self.set_default_file_selection() - - return self.__packages + return self.default_file_selection_options.packages def default_only_include(self) -> list[str]: - if not self.__include_defined: - self.set_default_file_selection() - - return self.__only_include + return self.default_file_selection_options.only_include @property def core_metadata_constructor(self) -> Callable[..., str]: @@ -259,13 +266,15 @@ f'cannot be an empty string' ) raise ValueError(message) - elif not isinstance(relative_path, str): + + if not isinstance(relative_path, str): message = ( f'Path for source `{source}` in field ' f'`tool.hatch.build.targets.{self.plugin_name}.shared-data` must be a string' ) raise TypeError(message) - elif not relative_path: + + if not relative_path: message = ( f'Path for source `{source}` in field ' f'`tool.hatch.build.targets.{self.plugin_name}.shared-data` cannot be an empty string' @@ -291,13 +300,15 @@ f'cannot be an empty string' ) raise ValueError(message) - elif not isinstance(relative_path, str): + + if not isinstance(relative_path, str): message = ( f'Path for source `{source}` in field ' f'`tool.hatch.build.targets.{self.plugin_name}.extra-metadata` must be a string' ) raise TypeError(message) - elif not relative_path: + + if not relative_path: message = ( f'Path for source `{source}` in field ' f'`tool.hatch.build.targets.{self.plugin_name}.extra-metadata` cannot be an empty string' @@ -338,6 +349,33 @@ return self.__macos_max_compat + @cached_property + def bypass_selection(self) -> bool: + bypass_selection = self.target_config.get('bypass-selection', False) + if not isinstance(bypass_selection, bool): + message = f'Field `tool.hatch.build.targets.{self.plugin_name}.bypass-selection` must be a boolean' + raise TypeError(message) + + return bypass_selection + + if sys.platform in {'darwin', 'win32'}: + + @staticmethod + def get_raw_fs_path_name(directory: str, name: str) -> str: + normalized = name.casefold() + entries = os.listdir(directory) + for entry in entries: + if entry.casefold() == normalized: + return entry + + return name # no cov + + else: + + @staticmethod + def get_raw_fs_path_name(directory: str, name: str) -> str: # noqa: ARG004 + return name + class WheelBuilder(BuilderInterface): """ @@ -349,10 +387,14 @@ def get_version_api(self) -> dict[str, Callable]: return {'standard': self.build_standard, 'editable': self.build_editable} - def get_default_versions(self) -> list[str]: + def get_default_versions(self) -> list[str]: # noqa: PLR6301 return ['standard'] - def clean(self, directory: str, versions: list[str]) -> None: + def clean( # noqa: PLR6301 + self, + directory: str, + versions: list[str], # noqa: ARG002 + ) -> None: for filename in os.listdir(directory): if filename.endswith('.whl'): os.remove(os.path.join(directory, filename)) @@ -384,8 +426,8 @@ def build_editable(self, directory: str, **build_data: Any) -> str: if self.config.dev_mode_dirs: return self.build_editable_explicit(directory, **build_data) - else: - return self.build_editable_detection(directory, **build_data) + + return self.build_editable_detection(directory, **build_data) def build_editable_detection(self, directory: str, **build_data: Any) -> str: from editables import EditableProject @@ -437,7 +479,8 @@ for relative_path in exposed_packages.values(): editable_project.add_to_path(os.path.dirname(relative_path)) - for filename, content in sorted(editable_project.files()): + for raw_filename, content in sorted(editable_project.files()): + filename = raw_filename if filename.endswith('.pth') and not filename.startswith('_'): filename = f'_{filename}' @@ -449,7 +492,8 @@ records.write(record) extra_dependencies = list(build_data['dependencies']) - for dependency in editable_project.dependencies(): + for raw_dependency in editable_project.dependencies(): + dependency = raw_dependency if dependency == 'editables': dependency += f'~={EDITABLES_MINIMUM_VERSION}' else: # no cov @@ -533,7 +577,8 @@ # extra_metadata/ - write last self.add_extra_metadata(archive, records, build_data) - def write_archive_metadata(self, archive: WheelArchive, records: RecordFile, build_data: dict[str, Any]) -> None: + @staticmethod + def write_archive_metadata(archive: WheelArchive, records: RecordFile, build_data: dict[str, Any]) -> None: from packaging.tags import parse_tag metadata = f"""\ @@ -642,7 +687,7 @@ return '-'.join(tag_parts) - def get_default_build_data(self) -> dict[str, Any]: + def get_default_build_data(self) -> dict[str, Any]: # noqa: PLR6301 return { 'infer_tag': False, 'pure_python': True, diff -Nru hatchling-1.18.0/src/hatchling/cli/build/__init__.py hatchling-1.21.0/src/hatchling/cli/build/__init__.py --- hatchling-1.18.0/src/hatchling/cli/build/__init__.py 2020-02-02 00:00:00.000000000 +0000 +++ hatchling-1.21.0/src/hatchling/cli/build/__init__.py 2020-02-02 00:00:00.000000000 +0000 @@ -6,7 +6,7 @@ def build_impl( *, - called_by_app: bool, + called_by_app: bool, # noqa: ARG001 directory: str, targets: list[str], hooks_only: bool, @@ -17,12 +17,12 @@ ) -> None: import os - from hatchling.bridge.app import get_application + from hatchling.bridge.app import Application from hatchling.builders.constants import BuildEnvVars from hatchling.metadata.core import ProjectMetadata from hatchling.plugin.manager import PluginManager - app = get_application(called_by_app=called_by_app) + app = Application() if hooks_only and no_hooks: app.abort('Cannot use both --hooks-only and --no-hooks together') @@ -67,7 +67,7 @@ builder_class = builders[target_name] # Display name before instantiation in case of errors - if not clean_only: + if not clean_only and len(target_data) > 1: app.display_mini_header(target_name) builder = builder_class(root, plugin_manager=plugin_manager, metadata=metadata, app=app.get_safe_application()) diff -Nru hatchling-1.18.0/src/hatchling/cli/metadata/__init__.py hatchling-1.21.0/src/hatchling/cli/metadata/__init__.py --- hatchling-1.18.0/src/hatchling/cli/metadata/__init__.py 2020-02-02 00:00:00.000000000 +0000 +++ hatchling-1.21.0/src/hatchling/cli/metadata/__init__.py 2020-02-02 00:00:00.000000000 +0000 @@ -4,16 +4,21 @@ from typing import Any -def metadata_impl(*, called_by_app: bool, field: str, compact: bool) -> None: +def metadata_impl( + *, + called_by_app: bool, # noqa: ARG001 + field: str, + compact: bool, +) -> None: import json import os - from hatchling.bridge.app import get_application + from hatchling.bridge.app import Application from hatchling.metadata.core import ProjectMetadata from hatchling.metadata.utils import resolve_metadata_fields from hatchling.plugin.manager import PluginManager - app = get_application(called_by_app=called_by_app) + app = Application() root = os.getcwd() plugin_manager = PluginManager() @@ -42,7 +47,10 @@ app.display(json.dumps(metadata, indent=4)) -def metadata_command(subparsers: argparse._SubParsersAction, defaults: Any) -> None: +def metadata_command( + subparsers: argparse._SubParsersAction, + defaults: Any, # noqa: ARG001 +) -> None: parser = subparsers.add_parser('metadata') parser.add_argument('field', nargs='?') parser.add_argument('-c', '--compact', action='store_true') diff -Nru hatchling-1.18.0/src/hatchling/cli/version/__init__.py hatchling-1.21.0/src/hatchling/cli/version/__init__.py --- hatchling-1.18.0/src/hatchling/cli/version/__init__.py 2020-02-02 00:00:00.000000000 +0000 +++ hatchling-1.21.0/src/hatchling/cli/version/__init__.py 2020-02-02 00:00:00.000000000 +0000 @@ -4,14 +4,18 @@ from typing import Any -def version_impl(*, called_by_app: bool, desired_version: str) -> None: +def version_impl( + *, + called_by_app: bool, # noqa: ARG001 + desired_version: str, +) -> None: import os - from hatchling.bridge.app import get_application + from hatchling.bridge.app import Application from hatchling.metadata.core import ProjectMetadata from hatchling.plugin.manager import PluginManager - app = get_application(called_by_app=called_by_app) + app = Application() root = os.getcwd() plugin_manager = PluginManager() diff -Nru hatchling-1.18.0/src/hatchling/dep/core.py hatchling-1.21.0/src/hatchling/dep/core.py --- hatchling-1.18.0/src/hatchling/dep/core.py 2020-02-02 00:00:00.000000000 +0000 +++ hatchling-1.21.0/src/hatchling/dep/core.py 2020-02-02 00:00:00.000000000 +0000 @@ -20,9 +20,10 @@ possible_distribution = self._distributions.get(item) if possible_distribution is not None: return possible_distribution + # Be safe even though the code as-is will never reach this since # the first unknown distribution will fail fast - elif self._search_exhausted: # no cov + if self._search_exhausted: # no cov return None for distribution in self._resolver: @@ -76,8 +77,9 @@ if requirement.specifier and not requirement.specifier.contains(distribution.version): return False + # TODO: handle https://discuss.python.org/t/11938 - elif requirement.url: + if requirement.url: direct_url_file = distribution.read_text('direct_url.json') if direct_url_file is not None: import json @@ -89,14 +91,15 @@ vcs_info = direct_url_data['vcs_info'] vcs = vcs_info['vcs'] commit_id = vcs_info['commit_id'] - requested_revision = vcs_info['requested_revision'] if 'requested_revision' in vcs_info else None + requested_revision = vcs_info.get('requested_revision') # Try a few variations, see https://peps.python.org/pep-0440/#direct-references if ( requested_revision and requirement.url == f'{vcs}+{url}@{requested_revision}#{commit_id}' ) or requirement.url == f'{vcs}+{url}@{commit_id}': return True - elif requirement.url == f'{vcs}+{url}' or requirement.url == f'{vcs}+{url}@{requested_revision}': + + if requirement.url in {f'{vcs}+{url}', f'{vcs}+{url}@{requested_revision}'}: import subprocess if vcs == 'git': @@ -106,13 +109,13 @@ # TODO: add elifs for hg, svn, and bzr https://github.com/pypa/hatch/issues/760 else: return False - result = subprocess.run(vcs_cmd, capture_output=True, text=True) + result = subprocess.run(vcs_cmd, capture_output=True, text=True) # noqa: PLW1510 if result.returncode: return False latest_commit_id, *_ = result.stdout.split() return commit_id == latest_commit_id - else: - return False + + return False return True diff -Nru hatchling-1.18.0/src/hatchling/licenses/parse.py hatchling-1.21.0/src/hatchling/licenses/parse.py --- hatchling-1.18.0/src/hatchling/licenses/parse.py 2020-02-02 00:00:00.000000000 +0000 +++ hatchling-1.21.0/src/hatchling/licenses/parse.py 2020-02-02 00:00:00.000000000 +0000 @@ -39,11 +39,11 @@ # expression should evaluate as such. python_tokens = [] for token in tokens: - if token not in ('or', 'and', 'with', '(', ')'): + if token not in {'or', 'and', 'with', '(', ')'}: python_tokens.append('False') - elif token == 'with': + elif token == 'with': # noqa: S105 python_tokens.append('or') - elif token == '(' and python_tokens and python_tokens[-1] not in ('or', 'and'): + elif token == '(' and python_tokens and python_tokens[-1] not in {'or', 'and'}: # noqa: S105 message = f'invalid license expression: {raw_license_expression}' raise ValueError(message) else: @@ -51,16 +51,18 @@ python_expression = ' '.join(python_tokens) try: - if eval(python_expression) is not False: - raise Exception - except Exception: + result = eval(python_expression) # noqa: PGH001, S307 + except Exception: # noqa: BLE001 + result = True + + if result is not False: message = f'invalid license expression: {raw_license_expression}' raise ValueError(message) from None # Take a final pass to check for unknown licenses/exceptions normalized_tokens = [] for token in tokens: - if token in ('or', 'and', 'with', '(', ')'): + if token in {'or', 'and', 'with', '(', ')'}: normalized_tokens.append(token.upper()) continue @@ -72,21 +74,20 @@ normalized_tokens.append(cast(str, EXCEPTIONS[token]['id'])) else: if token.endswith('+'): - token = token[:-1] + final_token = token[:-1] suffix = '+' else: + final_token = token suffix = '' - if token not in valid_licenses: - message = f'unknown license: {token}' + if final_token not in valid_licenses: + message = f'unknown license: {final_token}' raise ValueError(message) - normalized_tokens.append(cast(str, valid_licenses[token]['id']) + suffix) + normalized_tokens.append(cast(str, valid_licenses[final_token]['id']) + suffix) # Construct the normalized expression normalized_expression = ' '.join(normalized_tokens) # Fix internal padding for parentheses - normalized_expression = normalized_expression.replace('( ', '(').replace(' )', ')') - - return normalized_expression + return normalized_expression.replace('( ', '(').replace(' )', ')') diff -Nru hatchling-1.18.0/src/hatchling/metadata/core.py hatchling-1.21.0/src/hatchling/metadata/core.py --- hatchling-1.18.0/src/hatchling/metadata/core.py 2020-02-02 00:00:00.000000000 +0000 +++ hatchling-1.21.0/src/hatchling/metadata/core.py 2020-02-02 00:00:00.000000000 +0000 @@ -225,7 +225,7 @@ if version is None: version = self.hatch.version.cached source = f'source `{self.hatch.version.source_name}`' - core_metadata._version_set = True + core_metadata._version_set = True # noqa: SLF001 else: source = 'field `project.version`' @@ -385,7 +385,8 @@ if not raw_name: message = 'Missing required field `project.name`' raise ValueError(message) - elif not isinstance(raw_name, str): + + if not isinstance(raw_name, str): message = 'Field `project.name` must be a string' raise TypeError(message) @@ -517,10 +518,12 @@ if content_type is None: message = 'Field `content-type` is required in the `project.readme` table' raise ValueError(message) - elif not isinstance(content_type, str): + + if not isinstance(content_type, str): message = 'Field `content-type` in the `project.readme` table must be a string' raise TypeError(message) - elif content_type not in ('text/markdown', 'text/x-rst', 'text/plain'): + + if content_type not in {'text/markdown', 'text/x-rst', 'text/plain'}: message = ( 'Field `content-type` in the `project.readme` table must be one of the following: ' 'text/markdown, text/x-rst, text/plain' @@ -723,7 +726,8 @@ if not isinstance(data, dict): message = 'Field `project.license-files` must be a table' raise TypeError(message) - elif 'paths' in data and 'globs' in data: + + if 'paths' in data and 'globs' in data: message = 'Cannot specify both `paths` and `globs` in the `project.license-files` table' raise ValueError(message) @@ -759,9 +763,11 @@ raise TypeError(message) full_pattern = os.path.normpath(os.path.join(self.root, pattern)) - for path in glob(full_pattern): - if os.path.isfile(path): - license_files.append(os.path.relpath(path, self.root).replace('\\', '/')) + license_files.extend( + os.path.relpath(path, self.root).replace('\\', '/') + for path in glob(full_pattern) + if os.path.isfile(path) + ) else: message = 'Must specify either `paths` or `globs` in the `project.license-files` table if defined' raise ValueError(message) @@ -973,7 +979,8 @@ if not isinstance(classifier, str): message = f'Classifier #{i} of field `project.classifiers` must be a string' raise TypeError(message) - elif not self.__classifier_is_private(classifier) and classifier not in known_classifiers: + + if not self.__classifier_is_private(classifier) and classifier not in known_classifiers: message = f'Unknown classifier in field `project.classifiers`: {classifier}' raise ValueError(message) @@ -1111,10 +1118,10 @@ message = 'Field `project.entry-points` must be a table' raise TypeError(message) - for forbidden_field in ('scripts', 'gui-scripts'): + for forbidden_field, expected_field in (('console_scripts', 'scripts'), ('gui-scripts', 'gui-scripts')): if forbidden_field in defined_entry_point_groups: message = ( - f'Field `{forbidden_field}` must be defined as `project.{forbidden_field}` ' + f'Field `{forbidden_field}` must be defined as `project.{expected_field}` ' f'instead of in the `project.entry-points` table' ) raise ValueError(message) @@ -1235,7 +1242,8 @@ f'ASCII letters/digits.' ) raise ValueError(message) - elif not isinstance(dependencies, list): + + if not isinstance(dependencies, list): message = ( f'Dependencies for option `{option}` of field `project.optional-dependencies` must be an array' ) @@ -1310,7 +1318,8 @@ if not isinstance(self._dynamic, list): message = 'Field `project.dynamic` must be an array' raise TypeError(message) - elif not all(isinstance(entry, str) for entry in self._dynamic): + + if not all(isinstance(entry, str) for entry in self._dynamic): message = 'Field `project.dynamic` must only contain strings' raise TypeError(message) @@ -1410,7 +1419,7 @@ if self._cached is None: try: self._cached = self.source.get_version_data()['version'] - except Exception as e: + except Exception as e: # noqa: BLE001 message = f'Error getting the version from source `{self.source.PLUGIN_NAME}`: {e}' raise type(e)(message) from None @@ -1423,7 +1432,8 @@ if not source: message = 'The `source` option under the `tool.hatch.version` table must not be empty if defined' raise ValueError(message) - elif not isinstance(source, str): + + if not isinstance(source, str): message = 'Field `tool.hatch.version.source` must be a string' raise TypeError(message) @@ -1438,7 +1448,8 @@ if not scheme: message = 'The `scheme` option under the `tool.hatch.version` table must not be empty if defined' raise ValueError(message) - elif not isinstance(scheme, str): + + if not isinstance(scheme, str): message = 'Field `tool.hatch.version.scheme` must be a string' raise TypeError(message) diff -Nru hatchling-1.18.0/src/hatchling/metadata/custom.py hatchling-1.21.0/src/hatchling/metadata/custom.py --- hatchling-1.18.0/src/hatchling/metadata/custom.py 2020-02-02 00:00:00.000000000 +0000 +++ hatchling-1.21.0/src/hatchling/metadata/custom.py 2020-02-02 00:00:00.000000000 +0000 @@ -22,7 +22,8 @@ if not isinstance(build_script, str): message = f'Option `path` for metadata hook `{cls.PLUGIN_NAME}` must be a string' raise TypeError(message) - elif not build_script: + + if not build_script: message = f'Option `path` for metadata hook `{cls.PLUGIN_NAME}` must not be empty if defined' raise ValueError(message) diff -Nru hatchling-1.18.0/src/hatchling/metadata/plugin/interface.py hatchling-1.21.0/src/hatchling/metadata/plugin/interface.py --- hatchling-1.18.0/src/hatchling/metadata/plugin/interface.py 2020-02-02 00:00:00.000000000 +0000 +++ hatchling-1.21.0/src/hatchling/metadata/plugin/interface.py 2020-02-02 00:00:00.000000000 +0000 @@ -7,29 +7,25 @@ """ Example usage: - === ":octicons-file-code-16: plugin.py" + ```python tab="plugin.py" + from hatchling.metadata.plugin.interface import MetadataHookInterface - ```python - from hatchling.metadata.plugin.interface import MetadataHookInterface + class SpecialMetadataHook(MetadataHookInterface): + PLUGIN_NAME = 'special' + ... + ``` - class SpecialMetadataHook(MetadataHookInterface): - PLUGIN_NAME = 'special' - ... - ``` - - === ":octicons-file-code-16: hooks.py" + ```python tab="hooks.py" + from hatchling.plugin import hookimpl - ```python - from hatchling.plugin import hookimpl + from .plugin import SpecialMetadataHook - from .plugin import SpecialMetadataHook - - @hookimpl - def hatch_register_metadata_hook(): - return SpecialMetadataHook - ``` + @hookimpl + def hatch_register_metadata_hook(): + return SpecialMetadataHook + ``` """ PLUGIN_NAME = '' @@ -51,17 +47,9 @@ """ The hook configuration. - === ":octicons-file-code-16: pyproject.toml" - - ```toml - [tool.hatch.metadata.hooks.] - ``` - - === ":octicons-file-code-16: hatch.toml" - - ```toml - [metadata.hooks.] - ``` + ```toml config-example + [tool.hatch.metadata.hooks.] + ``` """ return self.__config @@ -71,7 +59,7 @@ This updates the metadata mapping of the `project` table in-place. """ - def get_known_classifiers(self) -> list[str]: + def get_known_classifiers(self) -> list[str]: # noqa: PLR6301 """ This returns extra classifiers that should be considered valid in addition to the ones known to PyPI. """ diff -Nru hatchling-1.18.0/src/hatchling/metadata/spec.py hatchling-1.21.0/src/hatchling/metadata/spec.py --- hatchling-1.18.0/src/hatchling/metadata/spec.py 2020-02-02 00:00:00.000000000 +0000 +++ hatchling-1.21.0/src/hatchling/metadata/spec.py 2020-02-02 00:00:00.000000000 +0000 @@ -147,7 +147,8 @@ metadata_file += f'Provides-Extra: {option}\n' for dependency in dependencies: if ';' in dependency: - metadata_file += f'Requires-Dist: {dependency} and extra == {option!r}\n' + dep_name, dep_env_marker = dependency.split(';', maxsplit=1) + metadata_file += f'Requires-Dist: {dep_name}; ({dep_env_marker.strip()}) and extra == {option!r}\n' elif '@ ' in dependency: metadata_file += f'Requires-Dist: {dependency} ; extra == {option!r}\n' else: @@ -228,7 +229,8 @@ metadata_file += f'Provides-Extra: {option}\n' for dependency in dependencies: if ';' in dependency: - metadata_file += f'Requires-Dist: {dependency} and extra == {option!r}\n' + dep_name, dep_env_marker = dependency.split(';', maxsplit=1) + metadata_file += f'Requires-Dist: {dep_name}; ({dep_env_marker.strip()}) and extra == {option!r}\n' elif '@ ' in dependency: metadata_file += f'Requires-Dist: {dependency} ; extra == {option!r}\n' else: @@ -298,7 +300,8 @@ metadata_file += f'Provides-Extra: {option}\n' for dependency in dependencies: if ';' in dependency: - metadata_file += f'Requires-Dist: {dependency} and extra == {option!r}\n' + dep_name, dep_env_marker = dependency.split(';', maxsplit=1) + metadata_file += f'Requires-Dist: {dep_name}; ({dep_env_marker.strip()}) and extra == {option!r}\n' elif '@ ' in dependency: metadata_file += f'Requires-Dist: {dependency} ; extra == {option!r}\n' else: diff -Nru hatchling-1.18.0/src/hatchling/ouroboros.py hatchling-1.21.0/src/hatchling/ouroboros.py --- hatchling-1.18.0/src/hatchling/ouroboros.py 2020-02-02 00:00:00.000000000 +0000 +++ hatchling-1.21.0/src/hatchling/ouroboros.py 2020-02-02 00:00:00.000000000 +0000 @@ -23,14 +23,18 @@ return literal_eval(match.group(1)) -def get_requires_for_build_sdist(config_settings: dict[str, Any] | None = None) -> list[str]: # type: ignore[no-redef] +def get_requires_for_build_sdist( # type: ignore[no-redef] + config_settings: dict[str, Any] | None = None, # noqa: ARG001 +) -> list[str]: """ https://peps.python.org/pep-0517/#get-requires-for-build-sdist """ return read_dependencies() -def get_requires_for_build_wheel(config_settings: dict[str, Any] | None = None) -> list[str]: # type: ignore[no-redef] +def get_requires_for_build_wheel( # type: ignore[no-redef] + config_settings: dict[str, Any] | None = None, # noqa: ARG001 +) -> list[str]: """ https://peps.python.org/pep-0517/#get-requires-for-build-wheel """ @@ -38,7 +42,7 @@ def get_requires_for_build_editable( # type: ignore[no-redef] - config_settings: dict[str, Any] | None = None + config_settings: dict[str, Any] | None = None, # noqa: ARG001 ) -> list[str]: """ https://peps.python.org/pep-0660/#get-requires-for-build-editable diff -Nru hatchling-1.18.0/src/hatchling/plugin/manager.py hatchling-1.21.0/src/hatchling/plugin/manager.py --- hatchling-1.18.0/src/hatchling/plugin/manager.py 2020-02-02 00:00:00.000000000 +0000 +++ hatchling-1.21.0/src/hatchling/plugin/manager.py 2020-02-02 00:00:00.000000000 +0000 @@ -68,16 +68,17 @@ classes: dict[str, type] = {} - for registered_classes in self.registration_method(): - if not isinstance(registered_classes, list): - registered_classes = [registered_classes] - + for raw_registered_classes in self.registration_method(): + registered_classes = ( + raw_registered_classes if isinstance(raw_registered_classes, list) else [raw_registered_classes] + ) for registered_class in registered_classes: name = getattr(registered_class, self.identifier, None) if not name: # no cov message = f'Class `{registered_class.__name__}` does not have a {name} attribute.' raise ValueError(message) - elif name in classes: # no cov + + if name in classes: # no cov message = ( f'Class `{registered_class.__name__}` defines its name as `{name}` but ' f'that name is already used by `{classes[name].__name__}`.' diff -Nru hatchling-1.18.0/src/hatchling/plugin/utils.py hatchling-1.21.0/src/hatchling/plugin/utils.py --- hatchling-1.18.0/src/hatchling/plugin/utils.py 2020-02-02 00:00:00.000000000 +0000 +++ hatchling-1.21.0/src/hatchling/plugin/utils.py 2020-02-02 00:00:00.000000000 +0000 @@ -37,11 +37,12 @@ if not subclasses: message = f'Unable to find a subclass of `{plugin_class.__name__}` in `{script_name}`: {path}' raise ValueError(message) - elif len(subclasses) > 1: + + if len(subclasses) > 1: message = ( f'Multiple subclasses of `{plugin_class.__name__}` found in `{script_name}`, ' f'select one by defining a function named `{plugin_finder}`: {path}' ) raise ValueError(message) - else: - return subclasses[0] + + return subclasses[0] diff -Nru hatchling-1.18.0/src/hatchling/utils/context.py hatchling-1.21.0/src/hatchling/utils/context.py --- hatchling-1.18.0/src/hatchling/utils/context.py 2020-02-02 00:00:00.000000000 +0000 +++ hatchling-1.21.0/src/hatchling/utils/context.py 2020-02-02 00:00:00.000000000 +0000 @@ -25,13 +25,28 @@ def format_path(cls, path: str, modifier: str) -> str: if not modifier: return os.path.normpath(path) - elif modifier == 'uri': + + modifiers = modifier.split(':')[::-1] + while modifiers and modifiers[-1] == 'parent': + path = os.path.dirname(path) + modifiers.pop() + + if not modifiers: + return path + + if len(modifiers) > 1: + message = f'Expected a single path modifier and instead got: {", ".join(reversed(modifiers))}' + raise ValueError(message) + + modifier = modifiers[0] + if modifier == 'uri': return path_to_uri(path) - elif modifier == 'real': + + if modifier == 'real': return os.path.realpath(path) - else: - message = f'Unknown path modifier: {modifier}' - raise ValueError(message) + + message = f'Unknown path modifier: {modifier}' + raise ValueError(message) class DefaultContextFormatter(ContextFormatter): @@ -49,19 +64,19 @@ 'root': self.__format_root, } - def __format_directory_separator(self, value: str, data: str) -> str: + def __format_directory_separator(self, value: str, data: str) -> str: # noqa: ARG002, PLR6301 return os.sep - def __format_path_separator(self, value: str, data: str) -> str: + def __format_path_separator(self, value: str, data: str) -> str: # noqa: ARG002, PLR6301 return os.pathsep - def __format_root(self, value: str, data: str) -> str: + def __format_root(self, value: str, data: str) -> str: # noqa: ARG002 return self.format_path(self.__root, data) - def __format_home(self, value: str, data: str) -> str: + def __format_home(self, value: str, data: str) -> str: # noqa: ARG002 return self.format_path(os.path.expanduser('~'), data) - def __format_env(self, value: str, data: str) -> str: + def __format_env(self, value: str, data: str) -> str: # noqa: ARG002, PLR6301 if not data: message = 'The `env` context formatting field requires a modifier' raise ValueError(message) @@ -69,11 +84,12 @@ env_var, separator, default = data.partition(':') if env_var in os.environ: return os.environ[env_var] - elif not separator: + + if not separator: message = f'Nonexistent environment variable must set a default: {env_var}' raise ValueError(message) - else: - return default + + return default class Context: @@ -122,7 +138,7 @@ def vformat(self, format_string: str, args: Sequence[Any], kwargs: Mapping[str, Any]) -> str: # We override to increase the recursion limit from 2 to 10 # - # TODO remove type ignore after https://github.com/python/typeshed/pull/9228 + # TODO: remove type ignore after https://github.com/python/typeshed/pull/9228 used_args = set() # type: ignore[var-annotated] result, _ = self._vformat(format_string, args, kwargs, used_args, 10) self.check_unused_args(used_args, args, kwargs) @@ -132,19 +148,19 @@ if key in self.__formatters: # Avoid hard look-up and rely on `None` to indicate that the field is undefined return kwargs.get(str(key)) - else: - try: - return super().get_value(key, args, kwargs) - except KeyError: - message = f'Unknown context field `{key}`' - raise ValueError(message) from None + + try: + return super().get_value(key, args, kwargs) + except KeyError: + message = f'Unknown context field `{key}`' + raise ValueError(message) from None def format_field(self, value: Any, format_spec: str) -> Any: formatter, _, data = format_spec.partition(':') if formatter in self.__formatters: return self.__formatters[formatter](value, data) - else: - return super().format_field(value, format_spec) + + return super().format_field(value, format_spec) def parse(self, format_string: str) -> Iterable: for literal_text, field_name, format_spec, conversion in super().parse(format_string): diff -Nru hatchling-1.18.0/src/hatchling/utils/fs.py hatchling-1.21.0/src/hatchling/utils/fs.py --- hatchling-1.18.0/src/hatchling/utils/fs.py 2020-02-02 00:00:00.000000000 +0000 +++ hatchling-1.21.0/src/hatchling/utils/fs.py 2020-02-02 00:00:00.000000000 +0000 @@ -18,6 +18,6 @@ def path_to_uri(path: str) -> str: if os.sep == '/': - return f'file://{os.path.abspath(path)}' - else: - return f'file:///{os.path.abspath(path).replace(os.sep, "/")}' + return f'file://{os.path.abspath(path).replace(" ", "%20")}' + + return f'file:///{os.path.abspath(path).replace(" ", "%20").replace(os.sep, "/")}' diff -Nru hatchling-1.18.0/src/hatchling/version/core.py hatchling-1.21.0/src/hatchling/version/core.py --- hatchling-1.18.0/src/hatchling/version/core.py 2020-02-02 00:00:00.000000000 +0000 +++ hatchling-1.21.0/src/hatchling/version/core.py 2020-02-02 00:00:00.000000000 +0000 @@ -18,7 +18,7 @@ self.__path = os.path.normpath(os.path.join(root, relative_path)) self.__cached_read_data: tuple | None = None - def read(self, pattern: str | bool) -> str: + def read(self, *, pattern: str | bool) -> str: if not os.path.isfile(self.__path): message = f'file does not exist: {self.__relative_path}' raise OSError(message) @@ -43,7 +43,7 @@ return self.__cached_read_data[0] def set_version(self, version: str) -> None: - old_version, file_contents, (start, end) = self.__cached_read_data # type: ignore + _old_version, file_contents, (start, end) = self.__cached_read_data # type: ignore with open(self.__path, 'w', encoding='utf-8') as f: f.write(f'{file_contents[:start]}{version}{file_contents[end:]}') diff -Nru hatchling-1.18.0/src/hatchling/version/scheme/plugin/interface.py hatchling-1.21.0/src/hatchling/version/scheme/plugin/interface.py --- hatchling-1.18.0/src/hatchling/version/scheme/plugin/interface.py 2020-02-02 00:00:00.000000000 +0000 +++ hatchling-1.21.0/src/hatchling/version/scheme/plugin/interface.py 2020-02-02 00:00:00.000000000 +0000 @@ -7,29 +7,25 @@ """ Example usage: - === ":octicons-file-code-16: plugin.py" + ```python tab="plugin.py" + from hatchling.version.scheme.plugin.interface import VersionSchemeInterface - ```python - from hatchling.version.scheme.plugin.interface import VersionSchemeInterface + class SpecialVersionScheme(VersionSchemeInterface): + PLUGIN_NAME = 'special' + ... + ``` - class SpecialVersionScheme(VersionSchemeInterface): - PLUGIN_NAME = 'special' - ... - ``` - - === ":octicons-file-code-16: hooks.py" + ```python tab="hooks.py" + from hatchling.plugin import hookimpl - ```python - from hatchling.plugin import hookimpl + from .plugin import SpecialVersionScheme - from .plugin import SpecialVersionScheme - - @hookimpl - def hatch_register_version_scheme(): - return SpecialVersionScheme - ``` + @hookimpl + def hatch_register_version_scheme(): + return SpecialVersionScheme + ``` """ PLUGIN_NAME = '' @@ -49,17 +45,9 @@ @property def config(self) -> dict: """ - === ":octicons-file-code-16: pyproject.toml" - - ```toml - [tool.hatch.version] - ``` - - === ":octicons-file-code-16: hatch.toml" - - ```toml - [version] - ``` + ```toml config-example + [tool.hatch.version] + ``` """ return self.__config diff -Nru hatchling-1.18.0/src/hatchling/version/scheme/standard.py hatchling-1.21.0/src/hatchling/version/scheme/standard.py --- hatchling-1.18.0/src/hatchling/version/scheme/standard.py 2020-02-02 00:00:00.000000000 +0000 +++ hatchling-1.21.0/src/hatchling/version/scheme/standard.py 2020-02-02 00:00:00.000000000 +0000 @@ -15,7 +15,12 @@ PLUGIN_NAME = 'standard' - def update(self, desired_version: str, original_version: str, version_data: dict) -> str: + def update( + self, + desired_version: str, + original_version: str, + version_data: dict, # noqa: ARG002 + ) -> str: from packaging.version import Version original = Version(original_version) @@ -28,11 +33,11 @@ reset_version_parts(original, release=update_release(original, [original.major + 1])) elif version == 'minor': reset_version_parts(original, release=update_release(original, [original.major, original.minor + 1])) - elif version in ('micro', 'patch', 'fix'): + elif version in {'micro', 'patch', 'fix'}: reset_version_parts( original, release=update_release(original, [original.major, original.minor, original.micro + 1]) ) - elif version in ('a', 'b', 'c', 'rc', 'alpha', 'beta', 'pre', 'preview'): + elif version in {'a', 'b', 'c', 'rc', 'alpha', 'beta', 'pre', 'preview'}: phase, number = parse_letter_version(version, 0) if original.pre: current_phase, current_number = parse_letter_version(*original.pre) @@ -40,7 +45,7 @@ number = current_number + 1 reset_version_parts(original, pre=(phase, number)) - elif version in ('post', 'rev', 'r'): + elif version in {'post', 'rev', 'r'}: number = 0 if original.post is None else original.post + 1 reset_version_parts(original, post=parse_letter_version(version, number)) elif version == 'dev': @@ -55,17 +60,17 @@ if self.config.get('validate-bump', True) and next_version <= original: message = f'Version `{version}` is not higher than the original version `{original_version}`' raise ValueError(message) - else: - return str(next_version) + + return str(next_version) return str(original) def reset_version_parts(version: Version, **kwargs: Any) -> None: # https://github.com/pypa/packaging/blob/20.9/packaging/version.py#L301-L310 - internal_version = version._version - parts: dict[str, Any] = {'epoch': 0} - ordered_part_names = ('release', 'pre', 'post', 'dev', 'local') + internal_version = version._version # noqa: SLF001 + parts: dict[str, Any] = {} + ordered_part_names = ('epoch', 'release', 'pre', 'post', 'dev', 'local') reset = False for part_name in ordered_part_names: @@ -77,13 +82,12 @@ else: parts[part_name] = getattr(internal_version, part_name) - version._version = type(internal_version)(**parts) + version._version = type(internal_version)(**parts) # noqa: SLF001 def update_release(original_version: Version, new_release_parts: list[int]) -> tuple[int, ...]: # Retain release length - for _ in range(len(original_version.release) - len(new_release_parts)): - new_release_parts.append(0) + new_release_parts.extend(0 for _ in range(len(original_version.release) - len(new_release_parts))) return tuple(new_release_parts) diff -Nru hatchling-1.18.0/src/hatchling/version/source/code.py hatchling-1.21.0/src/hatchling/version/source/code.py --- hatchling-1.18.0/src/hatchling/version/source/code.py 2020-02-02 00:00:00.000000000 +0000 +++ hatchling-1.21.0/src/hatchling/version/source/code.py 2020-02-02 00:00:00.000000000 +0000 @@ -16,7 +16,8 @@ if not relative_path: message = 'option `path` must be specified' raise ValueError(message) - elif not isinstance(relative_path, str): + + if not isinstance(relative_path, str): message = 'option `path` must be a string' raise TypeError(message) @@ -54,10 +55,10 @@ sys.path[:] = old_search_paths # Execute the expression to determine the version - version = eval(expression, vars(module)) + version = eval(expression, vars(module)) # noqa: PGH001, S307 return {'version': version} - def set_version(self, version: str, version_data: dict) -> None: + def set_version(self, version: str, version_data: dict) -> None: # noqa: ARG002, PLR6301 message = 'Cannot rewrite loaded code' raise NotImplementedError(message) diff -Nru hatchling-1.18.0/src/hatchling/version/source/env.py hatchling-1.21.0/src/hatchling/version/source/env.py --- hatchling-1.18.0/src/hatchling/version/source/env.py 2020-02-02 00:00:00.000000000 +0000 +++ hatchling-1.21.0/src/hatchling/version/source/env.py 2020-02-02 00:00:00.000000000 +0000 @@ -13,7 +13,8 @@ if not variable: message = 'option `variable` must be specified' raise ValueError(message) - elif not isinstance(variable, str): + + if not isinstance(variable, str): message = 'option `variable` must be a string' raise TypeError(message) @@ -23,6 +24,6 @@ return {'version': os.environ[variable]} - def set_version(self, version: str, version_data: dict) -> None: + def set_version(self, version: str, version_data: dict) -> None: # noqa: ARG002, PLR6301 message = 'Cannot set environment variables' raise NotImplementedError(message) diff -Nru hatchling-1.18.0/src/hatchling/version/source/plugin/interface.py hatchling-1.21.0/src/hatchling/version/source/plugin/interface.py --- hatchling-1.18.0/src/hatchling/version/source/plugin/interface.py 2020-02-02 00:00:00.000000000 +0000 +++ hatchling-1.21.0/src/hatchling/version/source/plugin/interface.py 2020-02-02 00:00:00.000000000 +0000 @@ -7,29 +7,25 @@ """ Example usage: - === ":octicons-file-code-16: plugin.py" + ```python tab="plugin.py" + from hatchling.version.source.plugin.interface import VersionSourceInterface - ```python - from hatchling.version.source.plugin.interface import VersionSourceInterface + class SpecialVersionSource(VersionSourceInterface): + PLUGIN_NAME = 'special' + ... + ``` - class SpecialVersionSource(VersionSourceInterface): - PLUGIN_NAME = 'special' - ... - ``` - - === ":octicons-file-code-16: hooks.py" + ```python tab="hooks.py" + from hatchling.plugin import hookimpl - ```python - from hatchling.plugin import hookimpl + from .plugin import SpecialVersionSource - from .plugin import SpecialVersionSource - - @hookimpl - def hatch_register_version_source(): - return SpecialVersionSource - ``` + @hookimpl + def hatch_register_version_source(): + return SpecialVersionSource + ``` """ PLUGIN_NAME = '' @@ -49,17 +45,9 @@ @property def config(self) -> dict: """ - === ":octicons-file-code-16: pyproject.toml" - - ```toml - [tool.hatch.version] - ``` - - === ":octicons-file-code-16: hatch.toml" - - ```toml - [version] - ``` + ```toml config-example + [tool.hatch.version] + ``` """ return self.__config diff -Nru hatchling-1.18.0/src/hatchling/version/source/regex.py hatchling-1.21.0/src/hatchling/version/source/regex.py --- hatchling-1.18.0/src/hatchling/version/source/regex.py 2020-02-02 00:00:00.000000000 +0000 +++ hatchling-1.21.0/src/hatchling/version/source/regex.py 2020-02-02 00:00:00.000000000 +0000 @@ -10,7 +10,8 @@ if not relative_path: message = 'option `path` must be specified' raise ValueError(message) - elif not isinstance(relative_path, str): + + if not isinstance(relative_path, str): message = 'option `path` must be a string' raise TypeError(message) @@ -20,9 +21,9 @@ raise TypeError(message) version_file = VersionFile(self.root, relative_path) - version = version_file.read(pattern) + version = version_file.read(pattern=pattern) return {'version': version, 'version_file': version_file} - def set_version(self, version: str, version_data: dict) -> None: + def set_version(self, version: str, version_data: dict) -> None: # noqa: PLR6301 version_data['version_file'].set_version(version) diff -Nru hatchling-1.18.0/tests/downstream/integrate.py hatchling-1.21.0/tests/downstream/integrate.py --- hatchling-1.18.0/tests/downstream/integrate.py 2020-02-02 00:00:00.000000000 +0000 +++ hatchling-1.21.0/tests/downstream/integrate.py 2020-02-02 00:00:00.000000000 +0000 @@ -22,11 +22,11 @@ def handle_remove_readonly(func, path, exc): # no cov # PermissionError: [WinError 5] Access is denied: '...\\.git\\...' - if func in (os.rmdir, os.remove, os.unlink) and exc[1].errno == errno.EACCES: - os.chmod(path, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO) # noqa: S103 + if func in {os.rmdir, os.remove, os.unlink} and exc[1].errno == errno.EACCES: + os.chmod(path, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO) func(path) else: - raise + raise exc class EnvVars(dict): @@ -82,26 +82,28 @@ exe_dir = os.path.join(venv_dir, 'Scripts' if ON_WINDOWS else 'bin') if os.path.isdir(exe_dir): return exe_dir + # PyPy - elif ON_WINDOWS: + if ON_WINDOWS: exe_dir = os.path.join(venv_dir, 'bin') if os.path.isdir(exe_dir): return exe_dir - else: - message = f'Unable to locate executables directory within: {venv_dir}' - raise OSError(message) + + message = f'Unable to locate executables directory within: {venv_dir}' + raise OSError(message) + # Debian - elif os.path.isdir(os.path.join(venv_dir, 'local')): + if os.path.isdir(os.path.join(venv_dir, 'local')): exe_dir = os.path.join(venv_dir, 'local', 'bin') if os.path.isdir(exe_dir): return exe_dir - else: - message = f'Unable to locate executables directory within: {venv_dir}' - raise OSError(message) - else: + message = f'Unable to locate executables directory within: {venv_dir}' raise OSError(message) + message = f'Unable to locate executables directory within: {venv_dir}' + raise OSError(message) + def main(): original_backend_path = os.path.dirname(os.path.dirname(HERE)) @@ -112,7 +114,7 @@ # Increment the minor version version_file = os.path.join(backend_path, 'src', 'hatchling', '__about__.py') - with open(version_file) as f: + with open(version_file, encoding='utf-8') as f: lines = f.readlines() for i, line in enumerate(lines): @@ -126,29 +128,27 @@ message = 'No version found' raise ValueError(message) - with open(version_file, 'w') as f: + with open(version_file, 'w', encoding='utf-8') as f: f.writelines(lines) print('<<<<< Building backend >>>>>') subprocess.check_call([sys.executable, '-m', 'build', '--wheel', '-o', links_dir, backend_path]) - subprocess.check_call( - [ - sys.executable, - '-m', - 'pip', - 'download', - '-q', - '--disable-pip-version-check', - '--no-python-version-warning', - '-d', - links_dir, - os.path.join(links_dir, os.listdir(links_dir)[0]), - ] - ) + subprocess.check_call([ + sys.executable, + '-m', + 'pip', + 'download', + '-q', + '--disable-pip-version-check', + '--no-python-version-warning', + '-d', + links_dir, + os.path.join(links_dir, os.listdir(links_dir)[0]), + ]) constraints = [] constraints_file = os.path.join(build_dir, 'constraints.txt') - with open(constraints_file, 'w') as f: + with open(constraints_file, 'w', encoding='utf-8') as f: f.write('\n'.join(constraints)) for project in os.listdir(HERE): @@ -162,14 +162,14 @@ # Not yet ported if os.path.isfile(potential_project_file): - with open(potential_project_file) as f: + with open(potential_project_file, encoding='utf-8') as f: project_config.update(tomli.loads(f.read())) if not python_version_supported(project_config): print('--> Unsupported version of Python, skipping') continue - with open(os.path.join(project_dir, 'data.json')) as f: + with open(os.path.join(project_dir, 'data.json'), encoding='utf-8') as f: test_data = json.loads(f.read()) with temp_dir() as d: @@ -197,7 +197,7 @@ if not os.path.isfile(project_file): sys.exit('--> Missing file: pyproject.toml') - with open(project_file) as f: + with open(project_file, encoding='utf-8') as f: project_config.update(tomli.loads(f.read())) for requirement in project_config.get('build-system', {}).get('requires', []): @@ -225,31 +225,27 @@ env_vars['PIP_CONSTRAINT'] = constraints_file with EnvVars(env_vars, ignore=('__PYVENV_LAUNCHER__', 'PYTHONHOME')): print('--> Installing project') - subprocess.check_call( - [ - shutil.which('pip'), - 'install', - '-q', - '--disable-pip-version-check', - '--no-python-version-warning', - '--find-links', - links_dir, - '--no-deps', - repo_dir, - ] - ) + subprocess.check_call([ + shutil.which('pip'), + 'install', + '-q', + '--disable-pip-version-check', + '--no-python-version-warning', + '--find-links', + links_dir, + '--no-deps', + repo_dir, + ]) print('--> Installing dependencies') - subprocess.check_call( - [ - shutil.which('pip'), - 'install', - '-q', - '--disable-pip-version-check', - '--no-python-version-warning', - repo_dir, - ] - ) + subprocess.check_call([ + shutil.which('pip'), + 'install', + '-q', + '--disable-pip-version-check', + '--no-python-version-warning', + repo_dir, + ]) print('--> Testing package') for statement in test_data['statements']: