Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-hatch for openSUSE:Factory checked in at 2026-01-29 17:46:57 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-hatch (Old) and /work/SRC/openSUSE:Factory/.python-hatch.new.1995 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-hatch" Thu Jan 29 17:46:57 2026 rev:24 rq:1329804 version:1.16.3 Changes: -------- --- /work/SRC/openSUSE:Factory/python-hatch/python-hatch.changes 2026-01-07 16:01:21.954995394 +0100 +++ /work/SRC/openSUSE:Factory/.python-hatch.new.1995/python-hatch.changes 2026-01-29 17:49:29.740930120 +0100 @@ -1,0 +2,16 @@ +Wed Jan 28 13:25:08 UTC 2026 - Markéta Machová <[email protected]> + +- Update to 1.16.3 + * Env var for keep-env when an exception occurs during environment + creation to enable debugging. + * Fix issue with self-referential dependencies not being recognized. + * Fix incomplete environments created when an exception occurs during + creation. + * Fix dependency-groups not working with when environment is not + marked as builder. + * Change Keyring to take expect repository URL instead of repository + name. +- Drop python39 build requirement +- Add shell.patch to fix visible regression with 1.16.3 + +------------------------------------------------------------------- Old: ---- hatch-v1.16.2.tar.gz New: ---- hatch-v1.16.3.tar.gz shell.patch ----------(New B)---------- New:- Drop python39 build requirement - Add shell.patch to fix visible regression with 1.16.3 ----------(New E)---------- ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-hatch.spec ++++++ --- /var/tmp/diff_new_pack.qjqw31/_old 2026-01-29 17:49:30.848977122 +0100 +++ /var/tmp/diff_new_pack.qjqw31/_new 2026-01-29 17:49:30.848977122 +0100 @@ -31,7 +31,7 @@ %endif %{?sle15_python_module_pythons} Name: python-hatch%{psuffix} -Version: 1.16.2 +Version: 1.16.3 Release: 0 Summary: Modern, extensible Python project management License: MIT @@ -53,6 +53,8 @@ Source22: https://files.pythonhosted.org/packages/py3/s/setuptools/setuptools-80.9.0-py3-none-any.whl Source23: https://files.pythonhosted.org/packages/py3/t/trove_classifiers/trove_classifiers-2025.12.1.14-py3-none-any.whl Source24: https://files.pythonhosted.org/packages/py3/u/urllib3/urllib3-2.6.2-py3-none-any.whl +# PATCH-FIX-UPSTREAM https://github.com/pypa/hatch/pull/2165 keep_env TypeError +Patch0: shell.patch BuildRequires: %{python_module base >= 3.10} BuildRequires: %{python_module hatch-vcs >= 0.3} BuildRequires: %{python_module hatchling >= 1.27} @@ -95,12 +97,8 @@ BuildRequires: %{python_module pytest} BuildRequires: %{python_module trustme} BuildRequires: cargo -%if 0%{?suse_version} >= 1699 -BuildRequires: python39-base %endif -%else BuildArch: noarch -%endif %python_subpackages %description @@ -172,6 +170,10 @@ donttest+=" or test_workspace_documentation_generation" donttest+=" or test_workspace_development_workflow" donttest+=" or test_workspace_overrides_matrix_conditional_members" +# multiple tests fail with rich 14.3 https://github.com/pypa/hatch/issues/2170 +rm tests/cli/env/test_show.py tests/cli/python/test_show.py tests/cli/dep/show/test_table.py +# flaky test +donttest+=" or test_workspace_overrides_combined_conditions" %pytest -n auto -v -k "not ($donttest)" %endif ++++++ hatch-v1.16.2.tar.gz -> hatch-v1.16.3.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hatch-hatch-v1.16.2/.github/workflows/test.yml new/hatch-hatch-v1.16.3/.github/workflows/test.yml --- old/hatch-hatch-v1.16.2/.github/workflows/test.yml 2025-12-06 19:55:09.000000000 +0100 +++ new/hatch-hatch-v1.16.3/.github/workflows/test.yml 2026-01-21 02:32:44.000000000 +0100 @@ -24,13 +24,13 @@ fail-fast: false matrix: os: [ubuntu-latest, windows-latest, macos-latest] - python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] + python-version: ["3.10", "3.11", "3.12", "3.13", "3.14", "3.14t"] steps: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hatch-hatch-v1.16.2/docs/history/hatch.md new/hatch-hatch-v1.16.3/docs/history/hatch.md --- old/hatch-hatch-v1.16.2/docs/history/hatch.md 2025-12-06 19:55:09.000000000 +0100 +++ new/hatch-hatch-v1.16.3/docs/history/hatch.md 2026-01-21 02:32:44.000000000 +0100 @@ -8,6 +8,19 @@ ## Unreleased +## [1.16.3](https://github.com/pypa/hatch/releases/tag/hatch-v1.16.3) - 2026-01-20 ## {: #hatch-v1.16.3 } + +***Added:*** + +- Env var for keep-env when an exception occurs during environment creation to enable debugging. + +***Fixed:*** + +- Fix issue with self-referential dependencies not being recognized. +- Fix incomplete environments created when an exception occurs during creation. +- Fix dependency-groups not working with when environment is not marked as builder. +- Change Keyring to take expect repository URL instead of repository name. + ## [1.16.2](https://github.com/pypa/hatch/releases/tag/hatch-v1.16.2) - 2025-12-06 ## {: #hatch-v1.16.2 } ***Fixed:*** diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hatch-hatch-v1.16.2/docs/how-to/environment/dependency-resolution.md new/hatch-hatch-v1.16.3/docs/how-to/environment/dependency-resolution.md --- old/hatch-hatch-v1.16.2/docs/how-to/environment/dependency-resolution.md 2025-12-06 19:55:09.000000000 +0100 +++ new/hatch-hatch-v1.16.3/docs/how-to/environment/dependency-resolution.md 2026-01-21 02:32:44.000000000 +0100 @@ -18,8 +18,8 @@ ```toml config-example [tool.hatch.envs.default.env-vars] -UV_EXTRA_INDEX_URL = "https://token:{env:GITLAB_API_TOKEN}@gitlab.com/api/v4/groups/<group1_path>/-/packages/pypi/simple/" -UV_INDEX_URL = "https://token:{env:GITLAB_API_TOKEN}@gitlab.com/api/v4/groups/<group2_path>/-/packages/pypi/simple/ https://pypi.org/simple/" +UV_INDEX = "https://token:{env:GITLAB_API_TOKEN}@gitlab.com/api/v4/groups/<group1_path>/-/packages/pypi/simple/ https://token:{env:GITLAB_API_TOKEN}@gitlab.com/api/v4/groups/<group2_path>/-/packages/pypi/simple/" +UV_DEFAULT_INDEX = "https://pypi.org/simple/" ``` !!! tip diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hatch-hatch-v1.16.2/docs/plugins/build-hook/reference.md new/hatch-hatch-v1.16.3/docs/plugins/build-hook/reference.md --- old/hatch-hatch-v1.16.2/docs/plugins/build-hook/reference.md 2025-12-06 19:55:09.000000000 +0100 +++ new/hatch-hatch-v1.16.3/docs/plugins/build-hook/reference.md 2026-01-21 02:32:44.000000000 +0100 @@ -14,6 +14,7 @@ - [hatch-jupyter-builder](https://github.com/jupyterlab/hatch-jupyter-builder) - used for packages in the Project Jupyter ecosystem - [hatch-mypyc](https://github.com/ofek/hatch-mypyc) - compiles code with [Mypyc](https://github.com/mypyc/mypyc) - [hatch-odoo](https://github.com/acsone/hatch-odoo) - package Odoo add-ons into the appropriate namespace +- [hatch-project-name](https://github.com/valentinoli/hatch-project-name/) - writes the project name to a file - [scikit-build-core](https://github.com/scikit-build/scikit-build-core) - build extension modules with CMake ## Overview diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hatch-hatch-v1.16.2/hatch.toml new/hatch-hatch-v1.16.3/hatch.toml --- old/hatch-hatch-v1.16.2/hatch.toml 2025-12-06 19:55:09.000000000 +0100 +++ new/hatch-hatch-v1.16.3/hatch.toml 2026-01-21 02:32:44.000000000 +0100 @@ -118,6 +118,7 @@ ] [envs.release] +workspace.members = ["backend/"] detached = true installer = "uv" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hatch-hatch-v1.16.2/src/hatch/cli/__init__.py new/hatch-hatch-v1.16.3/src/hatch/cli/__init__.py --- old/hatch-hatch-v1.16.2/src/hatch/cli/__init__.py 2025-12-06 19:55:09.000000000 +0100 +++ new/hatch-hatch-v1.16.3/src/hatch/cli/__init__.py 2026-01-21 02:32:44.000000000 +0100 @@ -92,7 +92,18 @@ ) @click.version_option(version=__version__, prog_name="Hatch") @click.pass_context -def hatch(ctx: click.Context, env_name, project, verbose, quiet, color, interactive, data_dir, cache_dir, config_file): +def hatch( + ctx: click.Context, + env_name, + project, + verbose, + quiet, + color, + interactive, + data_dir, + cache_dir, + config_file, +): """ \b _ _ _ _ @@ -112,7 +123,6 @@ interactive = False app = Application(ctx.exit, verbosity=verbose - quiet, enable_color=color, interactive=interactive) - app.env_active = os.environ.get(AppEnvVars.ENV_ACTIVE) if ( app.env_active diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hatch-hatch-v1.16.2/src/hatch/cli/application.py new/hatch-hatch-v1.16.3/src/hatch/cli/application.py --- old/hatch-hatch-v1.16.2/src/hatch/cli/application.py 2025-12-06 19:55:09.000000000 +0100 +++ new/hatch-hatch-v1.16.3/src/hatch/cli/application.py 2026-01-21 02:32:44.000000000 +0100 @@ -47,8 +47,8 @@ def get_environment(self, env_name: str | None = None) -> EnvironmentInterface: return self.project.get_environment(env_name) - def prepare_environment(self, environment: EnvironmentInterface): - self.project.prepare_environment(environment) + def prepare_environment(self, environment: EnvironmentInterface, *, keep_env: bool = False): + self.project.prepare_environment(environment, keep_env=keep_env) def run_shell_commands(self, context: ExecutionContext) -> None: with context.env.command_context(): @@ -91,6 +91,7 @@ *, ignore_compat: bool = False, display_header: bool = False, + keep_env: bool = False, ) -> Generator[ExecutionContext, None, None]: if self.verbose or len(environments) > 1: display_header = True @@ -117,7 +118,7 @@ context = ExecutionContext(environment) yield context - self.prepare_environment(environment) + self.prepare_environment(environment, keep_env=keep_env) self.execute_context(context) if incompatible: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hatch-hatch-v1.16.2/src/hatch/cli/env/create.py new/hatch-hatch-v1.16.3/src/hatch/cli/env/create.py --- old/hatch-hatch-v1.16.2/src/hatch/cli/env/create.py 2025-12-06 19:55:09.000000000 +0100 +++ new/hatch-hatch-v1.16.3/src/hatch/cli/env/create.py 2026-01-21 02:32:44.000000000 +0100 @@ -1,9 +1,12 @@ from __future__ import annotations +import os from typing import TYPE_CHECKING import click +from hatch.config.constants import AppEnvVars + if TYPE_CHECKING: from hatch.cli.application import Application @@ -35,7 +38,7 @@ app.abort(f"Environment `{env}` is incompatible: {e}") - app.project.prepare_environment(environment) + app.project.prepare_environment(environment, keep_env=bool(os.environ.get(AppEnvVars.KEEP_ENV))) if incompatible: num_incompatible = len(incompatible) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hatch-hatch-v1.16.2/src/hatch/cli/env/run.py new/hatch-hatch-v1.16.3/src/hatch/cli/env/run.py --- old/hatch-hatch-v1.16.2/src/hatch/cli/env/run.py 2025-12-06 19:55:09.000000000 +0100 +++ new/hatch-hatch-v1.16.3/src/hatch/cli/env/run.py 2026-01-21 02:32:44.000000000 +0100 @@ -1,9 +1,12 @@ from __future__ import annotations +import os from typing import TYPE_CHECKING import click +from hatch.config.constants import AppEnvVars + if TYPE_CHECKING: from hatch.cli.application import Application @@ -135,6 +138,7 @@ environments, ignore_compat=ignore_compat or matrix_selected, display_header=matrix_selected, + keep_env=bool(os.environ.get(AppEnvVars.KEEP_ENV)), ): if context.env.name == "system": context.env.exists = lambda: True # type: ignore[method-assign] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hatch-hatch-v1.16.2/src/hatch/cli/run/__init__.py new/hatch-hatch-v1.16.3/src/hatch/cli/run/__init__.py --- old/hatch-hatch-v1.16.2/src/hatch/cli/run/__init__.py 2025-12-06 19:55:09.000000000 +0100 +++ new/hatch-hatch-v1.16.3/src/hatch/cli/run/__init__.py 2026-01-21 02:32:44.000000000 +0100 @@ -90,21 +90,30 @@ if "python" not in config and (requires_python := metadata.get("requires-python")) is not None: import re import sys + import sysconfig from packaging.specifiers import SpecifierSet from hatch.python.distributions import DISTRIBUTIONS current_version = ".".join(map(str, sys.version_info[:2])) + if bool(sysconfig.get_config_var("Py_GIL_DISABLED")): + current_version += "t" + + # Strip "t" suffix for distribution lookup since DISTRIBUTIONS keys don't include it + current_version_base = current_version.rstrip("t") distributions = [name for name in DISTRIBUTIONS if re.match(r"^\d+\.\d+$", name)] - distributions.sort(key=lambda name: name != current_version) + distributions.sort(key=lambda name: name != current_version_base) python_constraint = SpecifierSet(requires_python) for distribution in distributions: # Try an artificially high patch version to account for # common cases like `>=3.11.4` or `>=3.10,<3.11` if python_constraint.contains(f"{distribution}.100"): - config["python"] = distribution + # Only set config["python"] if it doesn't match the current Python's base version + # This allows free-threaded builds (e.g. 3.14t) to match their base version (3.14) + if distribution != current_version_base: + config["python"] = distribution break else: app.abort(f"Unable to satisfy Python version constraint: {requires_python}") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hatch-hatch-v1.16.2/src/hatch/config/constants.py new/hatch-hatch-v1.16.3/src/hatch/config/constants.py --- old/hatch-hatch-v1.16.2/src/hatch/config/constants.py 2025-12-06 19:55:09.000000000 +0100 +++ new/hatch-hatch-v1.16.3/src/hatch/config/constants.py 2026-01-21 02:32:44.000000000 +0100 @@ -9,6 +9,7 @@ # https://no-color.org NO_COLOR = "NO_COLOR" FORCE_COLOR = "FORCE_COLOR" + KEEP_ENV = "HATCH_KEEP_ENV" class ConfigEnvVars: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hatch-hatch-v1.16.2/src/hatch/env/internal/test.py new/hatch-hatch-v1.16.3/src/hatch/env/internal/test.py --- old/hatch-hatch-v1.16.2/src/hatch/env/internal/test.py 2025-12-06 19:55:09.000000000 +0100 +++ new/hatch-hatch-v1.16.3/src/hatch/env/internal/test.py 2026-01-21 02:32:44.000000000 +0100 @@ -21,5 +21,5 @@ "cov-combine": "coverage combine", "cov-report": "coverage report", }, - "matrix": [{"python": ["3.14", "3.13", "3.12", "3.11", "3.10"]}], + "matrix": [{"python": ["3.14", "3.14t", "3.13", "3.12", "3.11", "3.10"]}], } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hatch-hatch-v1.16.2/src/hatch/env/plugin/interface.py new/hatch-hatch-v1.16.3/src/hatch/env/plugin/interface.py --- old/hatch-hatch-v1.16.2/src/hatch/env/plugin/interface.py 2025-12-06 19:55:09.000000000 +0100 +++ new/hatch-hatch-v1.16.3/src/hatch/env/plugin/interface.py 2026-01-21 02:32:44.000000000 +0100 @@ -369,6 +369,14 @@ else: all_dependencies_complex.append(Dependency(str(dep))) + if self.dependency_groups and not self.skip_install: + from hatch.utils.dep import get_complex_dependency_group + + for dependency_group in self.dependency_groups: + all_dependencies_complex.extend( + get_complex_dependency_group(self.app.project.dependency_groups, dependency_group) + ) + if self.builder: from hatch.project.constants import BuildEnvVars @@ -387,7 +395,7 @@ # Ensure these are checked last to speed up initial environment creation since # they will already be installed along with the project - if self.dev_mode: + if self.dev_mode or self.features or self.dependency_groups: all_dependencies_complex.extend(self.project_dependencies_complex) return all_dependencies_complex @@ -1235,9 +1243,9 @@ # Now we have the necessary information to perform an optimized glob search for members members_found = False - for member_path in find_members(root, relative_path.split(os.sep)): + for member_path in find_members(shared_prefix, relative_path.split(os.sep)): # Check if member should be excluded - relative_member_path = os.path.relpath(member_path, root) + relative_member_path = os.path.relpath(member_path, shared_prefix) should_exclude = False for exclude_pattern in exclude_patterns: if fnmatch.fnmatch(relative_member_path, exclude_pattern) or fnmatch.fnmatch( diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hatch-hatch-v1.16.2/src/hatch/env/virtual.py new/hatch-hatch-v1.16.3/src/hatch/env/virtual.py --- old/hatch-hatch-v1.16.2/src/hatch/env/virtual.py 2025-12-06 19:55:09.000000000 +0100 +++ new/hatch-hatch-v1.16.3/src/hatch/env/virtual.py 2026-01-21 02:32:44.000000000 +0100 @@ -2,6 +2,7 @@ import os import sys +import sysconfig from contextlib import contextmanager, nullcontext, suppress from functools import cached_property from os.path import isabs @@ -15,6 +16,8 @@ from hatch.utils.structures import EnvVars from hatch.venv.core import UVVirtualEnv, VirtualEnv +FREETHREADED_BUILD = bool(sysconfig.get_config_var("Py_GIL_DISABLED")) + if TYPE_CHECKING: from collections.abc import Callable, Iterable @@ -118,7 +121,7 @@ or is_default_environment(env_name, self.app.project.config.internal_envs[env_name]) ): uv_env = self.app.project.get_environment(env_name) - self.app.project.prepare_environment(uv_env) + self.app.project.prepare_environment(uv_env, keep_env=bool(os.environ.get(AppEnvVars.KEEP_ENV))) with uv_env: return self.platform.modules.shutil.which("uv") @@ -204,11 +207,14 @@ all_install_args = [] - workspace_deps = [str(dep.path) for dep in self.local_dependencies_complex if dep.path] + workspace_deps = [dep for dep in self.local_dependencies_complex if dep.path] workspace_names = {dep.name.lower() for dep in self.local_dependencies_complex if dep.path} - for dep_path in workspace_deps: - all_install_args.extend(["--editable", dep_path]) + for dep in workspace_deps: + if dep.editable: + all_install_args.extend(["--editable", dep.path]) + else: + all_install_args.append(dep.path) standard_dependencies = [] @@ -279,7 +285,12 @@ @cached_property def _preferred_python_version(self): - return f"{sys.version_info.major}.{sys.version_info.minor}" + version = f"{sys.version_info.major}.{sys.version_info.minor}" + + if FREETHREADED_BUILD: + version += "t" + + return version @cached_property def parent_python(self): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hatch-hatch-v1.16.2/src/hatch/project/core.py new/hatch-hatch-v1.16.3/src/hatch/project/core.py --- old/hatch-hatch-v1.16.2/src/hatch/project/core.py 2025-12-06 19:55:09.000000000 +0100 +++ new/hatch-hatch-v1.16.3/src/hatch/project/core.py 2026-01-21 02:32:44.000000000 +0100 @@ -180,43 +180,57 @@ self.app, ) + @staticmethod + @contextmanager + def managed_environment( + environment: EnvironmentInterface, *, keep_env: bool = False + ) -> Generator[EnvironmentInterface, None, None]: + """Context manager that removes environment on error unless keep_env is True.""" + try: + yield environment + except Exception: + if not keep_env and environment.exists(): + environment.remove() + raise + # Ensure that this method is clearly written since it is # used for documenting the life cycle of environments. - def prepare_environment(self, environment: EnvironmentInterface): + def prepare_environment(self, environment: EnvironmentInterface, *, keep_env: bool): if not environment.exists(): - self.env_metadata.reset(environment) + with self.managed_environment(environment, keep_env=keep_env): + self.env_metadata.reset(environment) - with environment.app_status_creation(): - environment.create() + with environment.app_status_creation(): + environment.create() - if not environment.skip_install: - if environment.pre_install_commands: - with environment.app_status_pre_installation(): - self.app.run_shell_commands( - ExecutionContext( - environment, - shell_commands=environment.pre_install_commands, - source="pre-install", - show_code_on_error=True, + if not environment.skip_install: + if environment.pre_install_commands: + with environment.app_status_pre_installation(): + self.app.run_shell_commands( + ExecutionContext( + environment, + shell_commands=environment.pre_install_commands, + source="pre-install", + show_code_on_error=True, + ) ) - ) - with environment.app_status_project_installation(): - if environment.dev_mode: - environment.install_project_dev_mode() - else: - environment.install_project() - - if environment.post_install_commands: - with environment.app_status_post_installation(): - self.app.run_shell_commands( - ExecutionContext( - environment, - shell_commands=environment.post_install_commands, - source="post-install", - show_code_on_error=True, + with environment.app_status_project_installation(): + if environment.dev_mode: + environment.install_project_dev_mode() + else: + environment.install_project() + + if environment.post_install_commands: + with environment.app_status_post_installation(): + self.app.run_shell_commands( + ExecutionContext( + environment, + shell_commands=environment.post_install_commands, + source="post-install", + show_code_on_error=True, + ) ) - ) with environment.app_status_dependency_state_check(): new_dep_hash = environment.dependency_hash() @@ -233,7 +247,7 @@ self.env_metadata.update_dependency_hash(environment, new_dep_hash) - def prepare_build_environment(self, *, targets: list[str] | None = None) -> None: + def prepare_build_environment(self, *, targets: list[str] | None = None, keep_env: bool = False) -> None: from hatch.project.constants import BUILD_BACKEND, BuildEnvVars from hatch.utils.structures import EnvVars @@ -249,7 +263,7 @@ except Exception as e: # noqa: BLE001 self.app.abort(f"Environment `{self.build_env.name}` is incompatible: {e}") - self.prepare_environment(self.build_env) + self.prepare_environment(self.build_env, keep_env=keep_env) additional_dependencies: list[str] = [] with self.app.status("Inspecting build dependencies"): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hatch-hatch-v1.16.2/src/hatch/publish/auth.py new/hatch-hatch-v1.16.3/src/hatch/publish/auth.py --- old/hatch-hatch-v1.16.2/src/hatch/publish/auth.py 2025-12-06 19:55:09.000000000 +0100 +++ new/hatch-hatch-v1.16.3/src/hatch/publish/auth.py 2026-01-21 02:32:44.000000000 +0100 @@ -46,7 +46,8 @@ import keyring - password = keyring.get_password(self._repo, self.username) + keyring_service = self._repo_config["url"] + password = keyring.get_password(keyring_service, self.username) if password is not None: return password @@ -111,4 +112,5 @@ if self.__password_was_read: import keyring - keyring.set_password(self._repo, self.__username, self.__password) + keyring_service = self._repo_config["url"] + keyring.set_password(keyring_service, self.__username, self.__password) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hatch-hatch-v1.16.2/tests/cli/env/test_create.py new/hatch-hatch-v1.16.3/tests/cli/env/test_create.py --- old/hatch-hatch-v1.16.2/tests/cli/env/test_create.py 2025-12-06 19:55:09.000000000 +0100 +++ new/hatch-hatch-v1.16.3/tests/cli/env/test_create.py 2026-01-21 02:32:44.000000000 +0100 @@ -1076,7 +1076,9 @@ data_path.mkdir() project = Project(project_path) - helpers.update_project_environment(project, "default", {"dev-mode": False, **project.config.envs["default"]}) + helpers.update_project_environment( + project, "default", {"dev-mode": False, "extra-dependencies": ["binary"], **project.config.envs["default"]} + ) helpers.update_project_environment(project, "test", {}) with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}): @@ -1088,6 +1090,7 @@ Creating environment: test Installing project Checking dependencies + Syncing dependencies """ ) @@ -1116,8 +1119,8 @@ ) requirements = extract_installed_requirements(output.splitlines()) - assert len(requirements) == 1 - assert requirements[0].lower() == f"my-app @ {project_path.as_uri().lower()}" + assert len(requirements) == 2 + assert f"my-app @ {project_path.as_uri().lower()}" in [req.lower() for req in requirements] @pytest.mark.requires_internet @@ -2032,3 +2035,81 @@ assert requirements[1].lower() == f"-e {project_path.as_uri().lower()}/baz" assert requirements[2].lower() == f"-e {project_path.as_uri().lower()}/foo" assert requirements[3].lower() == f"-e {project_path.as_uri().lower()}" + + [email protected]_internet +def test_workspace_members_always_editable_with_dev_mode_false( + hatch, helpers, temp_dir, platform, uv_on_path, extract_installed_requirements +): + """Verify workspace members are always installed as editable even when dev-mode=false.""" + project_name = "My.App" + + with temp_dir.as_cwd(): + result = hatch("new", project_name) + assert result.exit_code == 0, result.output + + project_path = temp_dir / "my-app" + data_path = temp_dir / "data" + data_path.mkdir() + + # Create workspace members + members = ["member-a", "member-b"] + for member in members: + with project_path.as_cwd(): + result = hatch("new", member) + assert result.exit_code == 0, result.output + + project = Project(project_path) + helpers.update_project_environment( + project, + "default", + { + "dev-mode": False, + "workspace": {"members": members}, + **project.config.envs["default"], + }, + ) + + with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}): + result = hatch("env", "create", "default") + + assert result.exit_code == 0, result.output + + env_data_path = data_path / "env" / "virtual" + project_data_path = env_data_path / project_path.name + storage_dirs = list(project_data_path.iterdir()) + storage_path = storage_dirs[0] + env_dirs = list(storage_path.iterdir()) + env_path = env_dirs[0] + + with UVVirtualEnv(env_path, platform): + output = platform.run_command([uv_on_path, "pip", "freeze"], check=True, capture_output=True).stdout.decode( + "utf-8" + ) + requirements = extract_installed_requirements(output.splitlines()) + + # Find project and member requirements - be more precise + my_app_reqs = [ + r + for r in requirements + if (r.lower().startswith("-e file:") and r.lower().endswith("/my-app")) + or (r.lower().startswith("my-app @") and "/member-" not in r.lower()) + ] + member_a_reqs = [r for r in requirements if "member-a" in r.lower() and "/member-a" in r.lower()] + member_b_reqs = [r for r in requirements if "member-b" in r.lower() and "/member-b" in r.lower()] + + assert len(my_app_reqs) == 1, f"Expected 1 my-app requirement, got {len(my_app_reqs)}: {my_app_reqs}" + assert len(member_a_reqs) == 1, f"Expected 1 member-a requirement, got {len(member_a_reqs)}: {member_a_reqs}" + assert len(member_b_reqs) == 1, f"Expected 1 member-b requirement, got {len(member_b_reqs)}: {member_b_reqs}" + + my_app_req = my_app_reqs[0] + member_a_req = member_a_reqs[0] + member_b_req = member_b_reqs[0] + + # Project should NOT be editable (dev-mode=false) + assert not my_app_req.lower().startswith("-e"), f"Project should not be editable: {my_app_req}" + assert my_app_req.lower().startswith("my-app @"), f"Project should be non-editable install: {my_app_req}" + + # Workspace members MUST be editable (always) + assert member_a_req.lower().startswith("-e"), f"Member A should be editable: {member_a_req}" + assert member_b_req.lower().startswith("-e"), f"Member B should be editable: {member_b_req}" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hatch-hatch-v1.16.2/tests/cli/env/test_show.py new/hatch-hatch-v1.16.3/tests/cli/env/test_show.py --- old/hatch-hatch-v1.16.2/tests/cli/env/test_show.py 2025-12-06 19:55:09.000000000 +0100 +++ new/hatch-hatch-v1.16.3/tests/cli/env/test_show.py 2026-01-21 02:32:44.000000000 +0100 @@ -72,6 +72,7 @@ "hatch-build", "hatch-static-analysis", "hatch-test.py3.14", + "hatch-test.py3.14t", "hatch-test.py3.13", "hatch-test.py3.12", "hatch-test.py3.11", diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hatch-hatch-v1.16.2/tests/cli/run/test_run.py new/hatch-hatch-v1.16.3/tests/cli/run/test_run.py --- old/hatch-hatch-v1.16.2/tests/cli/run/test_run.py 2025-12-06 19:55:09.000000000 +0100 +++ new/hatch-hatch-v1.16.3/tests/cli/run/test_run.py 2026-01-21 02:32:44.000000000 +0100 @@ -1,5 +1,6 @@ import os import sys +import sysconfig import pytest @@ -11,6 +12,8 @@ from hatch.utils.structures import EnvVars from hatchling.utils.constants import DEFAULT_BUILD_SCRIPT, DEFAULT_CONFIG_FILE +FREE_THREADED_BUILD = bool(sysconfig.get_config_var("Py_GIL_DISABLED")) + @pytest.fixture(scope="module") def available_python_version(): @@ -1214,6 +1217,9 @@ data_path.mkdir() known_version = "".join(map(str, sys.version_info[:2])) + if FREE_THREADED_BUILD: + known_version += "t" + project = Project(project_path) helpers.update_project_environment(project, "default", {"skip-install": True, **project.config.envs["default"]}) helpers.update_project_environment(project, "test", {"matrix": [{"python": [known_version, "9000"]}]}) @@ -1222,15 +1228,18 @@ result = hatch( "run", "test:python", "-c", "import os,sys;open('test.txt', 'a').write(sys.executable+os.linesep[-1])" ) - padding = "─" - if len(known_version) < 3: - padding += "─" + if FREE_THREADED_BUILD: + pre_padding = "" + else: + pre_padding = "─" + if len(known_version) < 3: + padding += "─" assert result.exit_code == 0, result.output assert result.output == helpers.dedent( f""" - ────────────────────────────────── test.py{known_version} ─────────────────────────────────{padding} + ─────────────────────────────────{pre_padding} test.py{known_version} ─────────────────────────────────{padding} Creating environment: test.py{known_version} Checking dependencies @@ -2575,6 +2584,9 @@ # Use the current minor version so that the current Python # will be used and distributions don't have to be downloaded major, minor = sys.version_info[:2] + python_version = f"{major}.{minor}" + if FREE_THREADED_BUILD: + python_version += "t" script.write_text( helpers.dedent( @@ -2583,7 +2595,7 @@ # requires-python = ">9000" # # [tool.hatch] - # python = "{major}.{minor}" + # python = "{python_version}" # /// import pathlib import sys diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hatch-hatch-v1.16.2/tests/env/plugin/test_interface.py new/hatch-hatch-v1.16.3/tests/env/plugin/test_interface.py --- old/hatch-hatch-v1.16.2/tests/env/plugin/test_interface.py 2025-12-06 19:55:09.000000000 +0100 +++ new/hatch-hatch-v1.16.3/tests/env/plugin/test_interface.py 2026-01-21 02:32:44.000000000 +0100 @@ -1541,6 +1541,309 @@ # Should have my-app[test] which will cause pip to install pytest assert any("pytest" in dep.lower() for dep in all_deps_str) + def test_dev_mode_true_returns_editable(self, temp_dir, isolated_data_dir, platform, temp_application): + """Verify dev-mode=true creates editable local dependency.""" + # Create a pyproject.toml file so skip_install defaults to False + pyproject = temp_dir / "pyproject.toml" + pyproject.write_text(""" + [project] + name = "my-app" + version = "0.0.1" + + [tool.hatch.envs.default] + dev-mode = true + """) + + project = Project(temp_dir) + environment = MockEnvironment( + temp_dir, + project.metadata, + "default", + project.config.envs["default"], + {}, + isolated_data_dir, + isolated_data_dir, + platform, + 0, + temp_application, + ) + + local_deps = environment.local_dependencies_complex + + assert len(local_deps) == 1 + assert local_deps[0].editable is True + + def test_dev_mode_false_returns_non_editable(self, temp_dir, isolated_data_dir, platform, temp_application): + """Verify dev-mode=false creates non-editable local dependency.""" + # Create a pyproject.toml file so skip_install defaults to False + pyproject = temp_dir / "pyproject.toml" + pyproject.write_text(""" + [project] + name = "my-app" + version = "0.0.1" + + [tool.hatch.envs.default] + dev-mode = false + """) + + project = Project(temp_dir) + environment = MockEnvironment( + temp_dir, + project.metadata, + "default", + project.config.envs["default"], + {}, + isolated_data_dir, + isolated_data_dir, + platform, + 0, + temp_application, + ) + + local_deps = environment.local_dependencies_complex + + assert len(local_deps) == 1 + assert local_deps[0].editable is False + + def test_skip_install_returns_empty(self, temp_dir, isolated_data_dir, platform, temp_application): + """Verify skip-install=true returns empty local dependencies.""" + pyproject = temp_dir / "pyproject.toml" + pyproject.write_text(""" + [project] + name = "my-app" + version = "0.0.1" + + [tool.hatch.envs.default] + skip-install = true + """) + + project = Project(temp_dir) + environment = MockEnvironment( + temp_dir, + project.metadata, + "default", + project.config.envs["default"], + {}, + isolated_data_dir, + isolated_data_dir, + platform, + 0, + temp_application, + ) + + local_deps = environment.local_dependencies_complex + + assert len(local_deps) == 0 + + def test_workspace_members_always_editable(self, temp_dir, isolated_data_dir, platform, temp_application): + """Verify workspace members are always editable regardless of dev-mode.""" + pyproject = temp_dir / "pyproject.toml" + pyproject.write_text(""" + [project] + name = "my-app" + version = "0.0.1" + + [tool.hatch.envs.default] + dev-mode = false + """) + + project = Project(temp_dir) + environment = MockEnvironment( + temp_dir, + project.metadata, + "default", + project.config.envs["default"], + {}, + isolated_data_dir, + isolated_data_dir, + platform, + 0, + temp_application, + ) + + local_deps = environment.local_dependencies_complex + + # Project itself should respect dev-mode=false + assert len(local_deps) == 1 + assert local_deps[0].editable is False + + def test_dependency_group_resolution(self, temp_dir, isolated_data_dir, platform, temp_application): + """Test dependency group resolution.""" + pyproject = temp_dir / "pyproject.toml" + pyproject.write_text(""" + [project] + name = "my-app" + version = "0.0.1" + + [dependency-groups] + test = ["pytest"] + + [tool.hatch.envs.default] + dependency-groups = ["test"] + """) + + project = Project(temp_dir) + project.set_app(temp_application) + temp_application.project = project + environment = MockEnvironment( + temp_dir, + project.metadata, + "default", + project.config.envs["default"], + {}, + isolated_data_dir, + isolated_data_dir, + platform, + 0, + temp_application, + ) + + deps = environment.project_dependencies_complex + assert any("pytest" in str(d) for d in deps) + + def test_dependency_group_resolution_builder_false_dev_mode_false( + self, temp_dir, isolated_data_dir, platform, temp_application + ): + """Test dependency group resolution in non-builder non-dev-mode environments.""" + pyproject = temp_dir / "pyproject.toml" + pyproject.write_text(""" + [project] + name = "my-app" + version = "0.0.1" + + [dependency-groups] + test = ["pytest"] + + [tool.hatch.envs.default] + builder = false + dev-mode = false + dependency-groups = ["test"] + """) + + project = Project(temp_dir) + project.set_app(temp_application) + temp_application.project = project + environment = MockEnvironment( + temp_dir, + project.metadata, + "default", + project.config.envs["default"], + {}, + isolated_data_dir, + isolated_data_dir, + platform, + 0, + temp_application, + ) + + assert any("pytest" in str(d) for d in environment.project_dependencies_complex) + assert any("pytest" in str(d) for d in environment.dependencies_complex) + + def test_dependency_group_resolution_builder_true_dev_mode_false( + self, temp_dir, isolated_data_dir, platform, temp_application + ): + """Test dependency group resolution in builder non-dev-mode environments.""" + pyproject = temp_dir / "pyproject.toml" + pyproject.write_text(""" + [project] + name = "my-app" + version = "0.0.1" + + [dependency-groups] + test = ["pytest"] + + [tool.hatch.envs.default] + builder = true + dev-mode = false + dependency-groups = ["test"] + """) + + project = Project(temp_dir) + project.set_app(temp_application) + temp_application.project = project + environment = MockEnvironment( + temp_dir, + project.metadata, + "default", + project.config.envs["default"], + {}, + isolated_data_dir, + isolated_data_dir, + platform, + 0, + temp_application, + ) + + assert any("pytest" in str(d) for d in environment.project_dependencies_complex) + assert any("pytest" in str(d) for d in environment.dependencies_complex) + + def test_dependency_group_resolution_builder_true_dev_mode_true( + self, temp_dir, isolated_data_dir, platform, temp_application + ): + """Test dependency group resolution in builder dev-mode environments.""" + pyproject = temp_dir / "pyproject.toml" + pyproject.write_text(""" + [project] + name = "my-app" + version = "0.0.1" + + [dependency-groups] + test = ["pytest"] + + [tool.hatch.envs.default] + builder = true + dev-mode = true + dependency-groups = ["test"] + """) + + project = Project(temp_dir) + project.set_app(temp_application) + temp_application.project = project + environment = MockEnvironment( + temp_dir, + project.metadata, + "default", + project.config.envs["default"], + {}, + isolated_data_dir, + isolated_data_dir, + platform, + 0, + temp_application, + ) + + assert any("pytest" in str(d) for d in environment.project_dependencies_complex) + assert any("pytest" in str(d) for d in environment.dependencies_complex) + + def test_additional_dependencies_as_strings(self, temp_dir, isolated_data_dir, platform, temp_application): + """Test additional_dependencies with string values.""" + pyproject = temp_dir / "pyproject.toml" + pyproject.write_text(""" + [project] + name = "my-app" + version = "0.0.1" + """) + + project = Project(temp_dir) + project.set_app(temp_application) + temp_application.project = project + environment = MockEnvironment( + temp_dir, + project.metadata, + "default", + project.config.envs["default"], + {}, + isolated_data_dir, + isolated_data_dir, + platform, + 0, + temp_application, + ) + + environment.additional_dependencies = ["extra-dep"] + deps = environment.dependencies_complex + assert any("extra-dep" in str(d) for d in deps) + class TestScripts: @pytest.mark.parametrize("field", ["scripts", "extra-scripts"]) @@ -2275,6 +2578,37 @@ assert list(environment.expand_command("foo")) == [command] + def test_verbosity_flag_adjustment_invalid(self, temp_dir, isolated_data_dir, platform, temp_application): + """Test verbosity flag with invalid adjustment.""" + pyproject = temp_dir / "pyproject.toml" + pyproject.write_text(""" + [project] + name = "my-app" + version = "0.0.1" + + [tool.hatch.envs.default] + scripts.test = "pytest {verbosity:flag:invalid}" + """) + + project = Project(temp_dir) + project.set_app(temp_application) + temp_application.project = project + environment = MockEnvironment( + temp_dir, + project.metadata, + "default", + project.config.envs["default"], + {}, + isolated_data_dir, + isolated_data_dir, + platform, + 0, + temp_application, + ) + + with pytest.raises(TypeError, match="Verbosity flag adjustment must be an integer"): + list(environment.expand_command("test")) + def test_args_undefined(self, isolation, isolated_data_dir, platform, global_application): config = { "project": {"name": "my_app", "version": "0.0.1"}, @@ -2991,3 +3325,369 @@ "pkg-feature-22", "pkg-feature-32", ] + + +class TestDependencyHash: + def test_hash_includes_local_dependencies(self, temp_dir, isolated_data_dir, platform, temp_application): + """Verify dependency hash includes local dependencies.""" + pyproject = temp_dir / "pyproject.toml" + pyproject.write_text(""" +[project] +name = "my-app" +version = "0.0.1" +""") + + config = {"project": {"name": "my_app", "version": "0.0.1"}} + project = Project(temp_dir, config=config) + project.set_app(temp_application) + temp_application.project = project + environment = MockEnvironment( + temp_dir, + project.metadata, + "default", + project.config.envs["default"], + {}, + isolated_data_dir, + isolated_data_dir, + platform, + 0, + temp_application, + ) + + hash_value = environment.dependency_hash() + assert hash_value + assert len(hash_value) > 0 + + def test_hash_stable_when_dependencies_unchanged(self, temp_dir, isolated_data_dir, platform, temp_application): + """Verify dependency hash is stable when dependencies don't change.""" + pyproject = temp_dir / "pyproject.toml" + pyproject.write_text(""" +[project] +name = "my-app" +version = "0.0.1" +""") + + config = {"project": {"name": "my_app", "version": "0.0.1"}} + project = Project(temp_dir, config=config) + project.set_app(temp_application) + temp_application.project = project + environment = MockEnvironment( + temp_dir, + project.metadata, + "default", + project.config.envs["default"], + {}, + isolated_data_dir, + isolated_data_dir, + platform, + 0, + temp_application, + ) + + hash1 = environment.dependency_hash() + hash2 = environment.dependency_hash() + + assert hash1 == hash2 + + def test_hash_changes_with_extra_dependencies(self, temp_dir, isolated_data_dir, platform, temp_application): + """Verify dependency hash changes when extra-dependencies are added.""" + pyproject = temp_dir / "pyproject.toml" + pyproject.write_text(""" +[project] +name = "my-app" +version = "0.0.1" +""") + + config_no_deps = {"project": {"name": "my_app", "version": "0.0.1"}} + project_no_deps = Project(temp_dir, config=config_no_deps) + project_no_deps.set_app(temp_application) + temp_application.project = project_no_deps + env_no_deps = MockEnvironment( + temp_dir, + project_no_deps.metadata, + "default", + project_no_deps.config.envs["default"], + {}, + isolated_data_dir, + isolated_data_dir, + platform, + 0, + temp_application, + ) + hash_no_deps = env_no_deps.dependency_hash() + + config_with_deps = { + "project": {"name": "my_app", "version": "0.0.1"}, + "tool": {"hatch": {"envs": {"default": {"extra-dependencies": ["pytest"]}}}}, + } + project_with_deps = Project(temp_dir, config=config_with_deps) + project_with_deps.set_app(temp_application) + temp_application.project = project_with_deps + env_with_deps = MockEnvironment( + temp_dir, + project_with_deps.metadata, + "default", + project_with_deps.config.envs["default"], + {}, + isolated_data_dir, + isolated_data_dir, + platform, + 0, + temp_application, + ) + hash_with_deps = env_with_deps.dependency_hash() + + assert hash_no_deps != hash_with_deps + + +class TestLocalDependenciesComplex: + def test_dev_mode_true_returns_editable(self, temp_dir, isolated_data_dir, platform, temp_application): + """Verify dev-mode=true creates editable local dependency.""" + pyproject = temp_dir / "pyproject.toml" + pyproject.write_text(""" +[project] +name = "my-app" +version = "0.0.1" +""") + + project = Project(temp_dir) + project.set_app(temp_application) + temp_application.project = project + environment = MockEnvironment( + temp_dir, + project.metadata, + "default", + project.config.envs["default"], + {}, + isolated_data_dir, + isolated_data_dir, + platform, + 0, + temp_application, + ) + + local_deps = environment.local_dependencies_complex + assert len(local_deps) == 1 + assert local_deps[0].editable is True + + def test_dev_mode_false_returns_non_editable(self, temp_dir, isolated_data_dir, platform, temp_application): + """Verify dev-mode=false creates non-editable local dependency.""" + pyproject = temp_dir / "pyproject.toml" + pyproject.write_text(""" +[project] +name = "my-app" +version = "0.0.1" + +[tool.hatch.envs.default] +dev-mode = false +""") + + project = Project(temp_dir) + project.set_app(temp_application) + temp_application.project = project + environment = MockEnvironment( + temp_dir, + project.metadata, + "default", + project.config.envs["default"], + {}, + isolated_data_dir, + isolated_data_dir, + platform, + 0, + temp_application, + ) + + local_deps = environment.local_dependencies_complex + assert len(local_deps) == 1 + assert local_deps[0].editable is False + + def test_workspace_members_always_editable(self, temp_dir, isolated_data_dir, platform, temp_application): + """Verify workspace members are always editable regardless of dev-mode.""" + pyproject = temp_dir / "pyproject.toml" + pyproject.write_text(""" +[project] +name = "my-app" +version = "0.0.1" + +[tool.hatch.envs.default] +dev-mode = false +workspace.members = ["member"] +""") + + member_dir = temp_dir / "member" + member_dir.mkdir() + (member_dir / "pyproject.toml").write_text(""" +[project] +name = "member" +version = "0.0.1" +""") + + project = Project(temp_dir) + project.set_app(temp_application) + temp_application.project = project + environment = MockEnvironment( + temp_dir, + project.metadata, + "default", + project.config.envs["default"], + {}, + isolated_data_dir, + isolated_data_dir, + platform, + 0, + temp_application, + ) + + local_deps = environment.local_dependencies_complex + assert len(local_deps) == 2 + project_dep = next(d for d in local_deps if d.name == "my-app") + member_dep = next(d for d in local_deps if d.name == "member") + assert project_dep.editable is False + assert member_dep.editable is True + + +class TestDynamicDependencies: + def test_dynamic_dependencies_resolved(self, temp_dir, isolated_data_dir, platform, temp_application): + """Verify dynamic dependencies are resolved correctly.""" + pyproject = temp_dir / "pyproject.toml" + pyproject.write_text(""" +[project] +name = "my-app" +version = "0.0.1" +dynamic = ["dependencies"] + +[tool.hatch.metadata] +allow-direct-references = true +""") + + project = Project(temp_dir) + project.set_app(temp_application) + temp_application.project = project + environment = MockEnvironment( + temp_dir, + project.metadata, + "default", + project.config.envs["default"], + {}, + isolated_data_dir, + isolated_data_dir, + platform, + 0, + temp_application, + ) + + assert "dependencies" in environment.metadata.dynamic + + +class TestBuildSystemIntegration: + def test_builder_includes_build_requirements(self, temp_dir, isolated_data_dir, platform, temp_application): + """Verify builder environment includes build system requirements.""" + pyproject = temp_dir / "pyproject.toml" + pyproject.write_text(""" +[build-system] +requires = ["hatchling", "build-dep"] + +[project] +name = "my-app" +version = "0.0.1" + +[tool.hatch.envs.build] +builder = true +""") + + project = Project(temp_dir) + project.set_app(temp_application) + temp_application.project = project + environment = MockEnvironment( + temp_dir, + project.metadata, + "build", + project.config.envs["build"], + {}, + isolated_data_dir, + isolated_data_dir, + platform, + 0, + temp_application, + ) + + deps = environment.dependencies + assert any("hatchling" in d for d in deps) + assert any("build-dep" in d for d in deps) + + +class TestEnvironmentLifecycle: + def test_app_status_contexts(self, temp_dir, isolated_data_dir, platform, temp_application): + """Verify environment lifecycle status contexts work correctly.""" + pyproject = temp_dir / "pyproject.toml" + pyproject.write_text(""" +[project] +name = "my-app" +version = "0.0.1" +""") + + project = Project(temp_dir) + project.set_app(temp_application) + temp_application.project = project + environment = MockEnvironment( + temp_dir, + project.metadata, + "default", + project.config.envs["default"], + {}, + isolated_data_dir, + isolated_data_dir, + platform, + 0, + temp_application, + ) + + with environment.app_status_creation(): + pass + with environment.app_status_pre_installation(): + pass + with environment.app_status_post_installation(): + pass + with environment.app_status_project_installation(): + pass + with environment.app_status_dependency_state_check(): + pass + with environment.app_status_dependency_installation_check(): + pass + with environment.app_status_dependency_synchronization(): + pass + + +class TestFileSystemContext: + def test_join_creates_new_context(self, temp_dir, isolated_data_dir, platform, temp_application): + """Test FileSystemContext.join creates proper paths.""" + from hatch.env.plugin.interface import FileSystemContext + + pyproject = temp_dir / "pyproject.toml" + pyproject.write_text(""" +[project] +name = "my-app" +version = "0.0.1" +""") + + project = Project(temp_dir) + project.set_app(temp_application) + temp_application.project = project + environment = MockEnvironment( + temp_dir, + project.metadata, + "default", + project.config.envs["default"], + {}, + isolated_data_dir, + isolated_data_dir, + platform, + 0, + temp_application, + ) + + ctx = FileSystemContext(environment, local_path=temp_dir, env_path="/env") + new_ctx = ctx.join("subdir") + assert "subdir" in str(new_ctx.local_path) + assert "subdir" in new_ctx.env_path ++++++ shell.patch ++++++ >From 9068758886e57751ff71a59520dcf576c7deea0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CF=87risto=CF=86e=20=D0=94=D0=B5=D0=BC=D0=BA=D0=BE?= <[email protected]> Date: Fri, 23 Jan 2026 17:44:54 +0100 Subject: [PATCH] Fix #2164 keep_env type error for hatch shell (#2165) * Fix #2164 * Fix ruff --------- Co-authored-by: Cary Hawkins <[email protected]> --- src/hatch/cli/shell/__init__.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/hatch/cli/shell/__init__.py b/src/hatch/cli/shell/__init__.py index 815e71d98..5edc1f1ca 100644 --- a/src/hatch/cli/shell/__init__.py +++ b/src/hatch/cli/shell/__init__.py @@ -1,9 +1,12 @@ from __future__ import annotations +import os from typing import TYPE_CHECKING import click +from hatch.config.constants import AppEnvVars + if TYPE_CHECKING: from hatch.cli.application import Application @@ -42,7 +45,7 @@ def shell(app: Application, env_name: str | None, name: str, path: str): # no c with app.project.ensure_cwd(): environment = app.project.get_environment(chosen_env) - app.project.prepare_environment(environment) + app.project.prepare_environment(environment, keep_env=bool(os.environ.get(AppEnvVars.KEEP_ENV))) first_run_indicator = app.cache_dir / "shell" / "first_run" if not first_run_indicator.is_file():
