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

Support hatch version --force #1645

Merged
merged 7 commits into from
Aug 17, 2024
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
4 changes: 4 additions & 0 deletions backend/src/hatchling/utils/constants.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
DEFAULT_BUILD_SCRIPT = 'hatch_build.py'
DEFAULT_CONFIG_FILE = 'hatch.toml'


class VersionEnvVars:
VALIDATE_BUMP = 'HATCH_VERSION_VALIDATE_BUMP'
30 changes: 28 additions & 2 deletions backend/src/hatchling/version/scheme/plugin/interface.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from __future__ import annotations

import os
from abc import ABC, abstractmethod
from functools import cached_property


class VersionSchemeInterface(ABC): # no cov
Expand Down Expand Up @@ -51,9 +53,33 @@ def config(self) -> dict:
"""
return self.__config

@cached_property
def validate_bump(self) -> bool:
"""
This is the value of the `validate-bump` option, with the `HATCH_VERSION_VALIDATE_BUMP`
environment variable taking precedence. Validation is enabled by default.

```toml config-example
[tool.hatch.version]
validate-bump = true
```
"""
from hatchling.utils.constants import VersionEnvVars

if VersionEnvVars.VALIDATE_BUMP in os.environ:
return os.environ[VersionEnvVars.VALIDATE_BUMP] not in {'false', '0'}

validate_bump = self.config.get('validate-bump', True)
if not isinstance(validate_bump, bool):
message = 'option `validate-bump` must be a boolean'
raise TypeError(message)

return validate_bump

@abstractmethod
def update(self, desired_version: str, original_version: str, version_data: dict) -> str:
"""
This should return a normalized form of the desired version and verify that it
is higher than the original version.
This should return a normalized form of the desired version. If the
[validate_bump](reference.md#hatchling.version.scheme.plugin.interface.VersionSchemeInterface.validate_bump)
property is `True`, this method should also verify that the version is higher than the original version.
"""
2 changes: 1 addition & 1 deletion backend/src/hatchling/version/scheme/standard.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def update(
raise ValueError(message)

next_version = Version(version)
if self.config.get('validate-bump', True) and next_version <= original:
if self.validate_bump and next_version <= original:
message = f'Version `{version}` is not higher than the original version `{original_version}`'
raise ValueError(message)

Expand Down
13 changes: 7 additions & 6 deletions docs/community/contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ The usual process to make a contribution is to:
1. Check for existing related issues
2. Fork the repository and create a new branch
3. Make your changes
4. Make sure formatting, linting and tests passes.
4. Make sure formatting, linting and tests passes.
5. Add tests if possible to cover the lines you added.
6. Commit, and send a Pull Request.

Expand All @@ -15,7 +15,7 @@ Clone the `hatch` repository, `cd` into it, and create a new branch for your con

```bash
cd hatch
git checkout -b add-my-contribution
git switch -c add-my-contribution
warsaw marked this conversation as resolved.
Show resolved Hide resolved
```

## Run the tests
Expand All @@ -35,21 +35,22 @@ hatch test --cover
Run the extended test suite with coverage:

```bash
hatch run full
hatch test --cover --all
```

## Lint

Run automated formatting:

```bash
hatch fmt --formatter
hatch fmt
```

Run full linting and type checking:

```bash
hatch fmt
hatch fmt --check
hatch run types:check
```

## Docs
Expand All @@ -64,4 +65,4 @@ Build and validate the documentation website:

```bash
hatch run docs:build-check
```
```
1 change: 1 addition & 0 deletions docs/history/hatch.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
***Added:***

- The `version` and `project metadata` commands now support projects that do not use Hatchling as the build backend
- The `version` command accepts a `--force` option, allowing for downgrades when an explicit version number is given.
- Build environments can now be configured, the default build environment is `hatch-build`
- The environment interface now has the following methods and properties in order to better support builds on remote machines: `project_root`, `sep`, `pathsep`, `fs_context`

Expand Down
1 change: 1 addition & 0 deletions docs/plugins/version-scheme/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@
- PLUGIN_NAME
- root
- config
- validate_bump
- update
30 changes: 11 additions & 19 deletions src/hatch/cli/version/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,14 @@

@click.command(short_help="View or set a project's version")
@click.argument('desired_version', required=False)
@click.option(
'--force',
'-f',
is_flag=True,
help='Allow an explicit downgrading version to be given',
)
@click.pass_obj
def version(app: Application, desired_version: str | None):
def version(app: Application, *, desired_version: str | None, force: bool):
"""View or set a project's version."""
if app.project.root is None:
if app.project.chosen_name is None:
Expand All @@ -26,6 +32,7 @@ def version(app: Application, desired_version: str | None):
app.display(app.project.metadata.config['project']['version'])
return

from hatch.config.constants import VersionEnvVars
from hatch.project.constants import BUILD_BACKEND

with app.project.location.as_cwd():
Expand All @@ -40,33 +47,18 @@ def version(app: Application, desired_version: str | None):
project_metadata = app.project.build_frontend.get_core_metadata()

app.display(project_metadata['version'])
elif 'version' not in app.project.metadata.dynamic:
source = app.project.metadata.hatch.version.source

version_data = source.get_version_data()
original_version = version_data['version']

if not desired_version:
app.display(original_version)
return

updated_version = app.project.metadata.hatch.version.scheme.update(
desired_version, original_version, version_data
)
source.set_version(updated_version, version_data)

app.display_info(f'Old: {original_version}')
app.display_info(f'New: {updated_version}')
else:
from hatch.utils.runner import ExecutionContext

app.ensure_environment_plugin_dependencies()
app.project.prepare_build_environment()

context = ExecutionContext(app.project.build_env)
command = ['python', '-u', '-m', 'hatchling', 'version']
if desired_version:
command.append(desired_version)
if force:
context.env_vars[VersionEnvVars.VALIDATE_BUMP] = 'false'

context = ExecutionContext(app.project.build_env)
context.add_shell_command(command)
app.execute_context(context)
4 changes: 4 additions & 0 deletions src/hatch/config/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,7 @@ class PythonEnvVars:
CUSTOM_SOURCE_PREFIX = 'HATCH_PYTHON_CUSTOM_SOURCE_'
CUSTOM_PATH_PREFIX = 'HATCH_PYTHON_CUSTOM_PATH_'
CUSTOM_VERSION_PREFIX = 'HATCH_PYTHON_CUSTOM_VERSION_'


class VersionEnvVars:
VALIDATE_BUMP = 'HATCH_VERSION_VALIDATE_BUMP'
11 changes: 10 additions & 1 deletion tests/backend/version/scheme/test_standard.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import pytest
from packaging.version import _parse_letter_version # noqa: PLC2701

from hatch.utils.structures import EnvVars
from hatchling.utils.constants import VersionEnvVars
from hatchling.version.scheme.standard import StandardScheme


Expand All @@ -17,12 +19,19 @@ def test_specific(isolation):
assert scheme.update('9000.0.0-rc.1', '1.0', {}) == '9000.0.0rc1'


def test_specific_not_higher_allowed(isolation):
def test_specific_not_higher_allowed_config(isolation):
scheme = StandardScheme(str(isolation), {'validate-bump': False})

assert scheme.update('0.24.4', '1.0.0.dev0', {}) == '0.24.4'


def test_specific_not_higher_allowed_env_var(isolation):
scheme = StandardScheme(str(isolation), {})

with EnvVars({VersionEnvVars.VALIDATE_BUMP: 'false'}):
assert scheme.update('0.24.4', '1.0.0.dev0', {}) == '0.24.4'


def test_release(isolation):
scheme = StandardScheme(str(isolation), {})

Expand Down
45 changes: 45 additions & 0 deletions tests/cli/version/test_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,51 @@ def test_set_dynamic(hatch, helpers, temp_dir):
)


@pytest.mark.usefixtures('mock_backend_process')
def test_set_dynamic_downgrade(hatch, helpers, temp_dir):
project_name = 'My.App'

with temp_dir.as_cwd():
hatch('new', project_name)

path = temp_dir / 'my-app'
data_path = temp_dir / 'data'
data_path.mkdir()

(path / 'src' / 'my_app' / '__about__.py').write_text('__version__ = "21.1.2"')

# This one fails, because it's a downgrade without --force
with path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):
result = hatch('version', '21.1.0', catch_exceptions=True)

assert result.exit_code == 1, result.output
assert str(result.exception) == 'Version `21.1.0` is not higher than the original version `21.1.2`'

# Try again, this time with --force
with path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):
result = hatch('version', '--force', '21.1.0')

assert result.exit_code == 0, result.output
assert result.output == helpers.dedent(
"""
Inspecting build dependencies
Old: 21.1.2
New: 21.1.0
"""
)

with path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):
result = hatch('version')

assert result.exit_code == 0, result.output
assert result.output == helpers.dedent(
"""
Inspecting build dependencies
21.1.0
"""
)


def test_show_static(hatch, temp_dir):
project_name = 'My.App'

Expand Down
Loading