Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Python resolution when there are metadata hooks #1176

Merged
merged 1 commit into from
Dec 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/history/hatch.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

- Ensure that the `dependency_hash` method of the `environment` interface is called after `sync_dependencies` for cases where the hash is only known at that point, such as for dependency lockers
- Only acknowledge the `HATCH_PYTHON_VARIANT_*` environment variables for Python resolution for supported platforms and architectures
- Fix Python resolution when there are metadata hooks with unsatisfied dependencies

## [1.9.0](https://github.com/pypa/hatch/releases/tag/hatch-v1.9.0) - 2023-12-19 ## {: #hatch-v1.9.0 }

Expand Down
7 changes: 6 additions & 1 deletion src/hatch/env/virtual.py
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,12 @@ def _get_available_distribution(self, python_version: str = '') -> str | None:
return None

for available_distribution in available_distributions:
if not self.metadata.core.python_constraint.contains(available_distribution):
minor_version = (
available_distribution.replace('pypy', '', 1)
if available_distribution.startswith('pypy')
else available_distribution
)
if not self._python_constraint.contains(minor_version):
continue

return available_distribution
Expand Down
89 changes: 88 additions & 1 deletion tests/cli/run/test_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from hatch.python.resolve import get_compatible_distributions
from hatch.utils.fs import Path
from hatch.utils.structures import EnvVars
from hatchling.utils.constants import DEFAULT_CONFIG_FILE
from hatchling.utils.constants import DEFAULT_BUILD_SCRIPT, DEFAULT_CONFIG_FILE


@pytest.fixture(scope='module')
Expand Down Expand Up @@ -2235,3 +2235,90 @@ def test_update_python_max_compatible(hatch, helpers, temp_dir, config_file, moc
assert env_path.name == project_path.name

assert str(env_path) in str(output_file.read_text())


@pytest.mark.requires_internet
def test_python_installation_with_metadata_hook(
hatch, helpers, temp_dir, config_file, mocker, available_python_version
):
config_file.model.template.plugins['default']['tests'] = False
config_file.save()

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()

project = Project(project_path)
config = dict(project.raw_config)
config['build-system']['requires'].append('foo')
config['tool']['hatch']['metadata'] = {'hooks': {'custom': {'dependencies': ['binary']}}}
project.save_config(config)

helpers.update_project_environment(
project,
'default',
{'skip-install': True, 'python': available_python_version, **project.config.envs['default']},
)

build_script = project_path / DEFAULT_BUILD_SCRIPT
build_script.write_text(
helpers.dedent(
"""
from hatchling.metadata.plugin.interface import MetadataHookInterface

class CustomMetadataHook(MetadataHookInterface):
def update(self, metadata):
import binary
"""
)
)

mocker.patch('hatch.env.virtual.VirtualEnvironment._interpreter_is_compatible', return_value=False)
manager = PythonManager(data_path / 'env' / 'virtual' / '.pythons')
assert not manager.get_installed()

with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):
result = hatch(
'run', 'python', '-c', "import os,sys;open('test.txt', 'a').write(sys.executable+os.linesep[-1])"
)

assert result.exit_code == 0, result.output
assert result.output == helpers.dedent(
f"""
Creating environment: default
Installing Python distribution: {available_python_version}
Checking dependencies
"""
)
output_file = project_path / 'test.txt'
assert output_file.is_file()

env_data_path = data_path / 'env' / 'virtual'
assert env_data_path.is_dir()

project_data_path = env_data_path / project_path.name
assert project_data_path.is_dir()

storage_dirs = list(project_data_path.iterdir())
assert len(storage_dirs) == 1

storage_path = storage_dirs[0]
assert len(storage_path.name) == 8

env_dirs = list(storage_path.iterdir())
assert len(env_dirs) == 1

env_path = env_dirs[0]

assert env_path.name == project_path.name

assert str(env_path) in str(output_file.read_text())

assert list(manager.get_installed()) == [available_python_version]