Skip to content

Commit

Permalink
Add tutorial for managing Python distributions
Browse files Browse the repository at this point in the history
  • Loading branch information
ofek committed May 27, 2024
1 parent c84d7b3 commit be42841
Show file tree
Hide file tree
Showing 7 changed files with 149 additions and 9 deletions.
136 changes: 136 additions & 0 deletions docs/tutorials/python/manage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
# Managing Python distributions

-----

The [`python`](../../cli/reference.md#hatch-python) command group provides a set of commands to manage Python distributions that may be used by other tools.

!!! note
When using environments, manual management is not necessary since by default Hatch will [automatically](../../plugins/environment/virtual.md#python-resolution) download and manage Python distributions internally when a requested version cannot be found.

## Location

There are two ways to control where Python distributions are installed. Both methods make it so that each installed distribution is placed in a subdirectory of the configured location named after the distribution.

1. The globally configured [default directory](../../config/hatch.md#python-installations) for Python installations.
2. The `-d`/`--dir` option of every [`python`](../../cli/reference.md#hatch-python) subcommand, which takes precedence over the default directory.

## Installation

To install a Python distribution, use the [`python install`](../../cli/reference.md#hatch-python-install) command. For example:

```
hatch python install 3.12
```

This will:

1. Download the `3.12` Python distribution
2. Unpack it into a directory named `3.12` within the configured [default directory](../../config/hatch.md#python-installations) for Python installations
3. Add the installation to the user PATH

Now its `python` executable can be used by you or other tools.

!!! note
For PATH changes to take effect in the current shell, you will need to restart it.

### Multiple

You can install multiple Python distributions at once by providing multiple distribution names. For example:

```
hatch python install 3.12 3.11 pypy3.10
```

If you would like to install all available Python distributions that are compatible with your system, use `all` as the distribution name:

```
hatch python install all
```

!!! tip
The commands for [updating](#updates) and [removing](#removal) also support this functionality.

### Private

By default, installing Python distributions will add them to the user PATH. To disable this behavior, use the `--private` flag like so:

```
hatch python install 3.12 --private
```

This when combined with the [directory option](#location) can be used to create private, isolated installations.

## Listing distributions

You can see all of the available and installed Python distributions by using the [`python show`](../../cli/reference.md#hatch-python-show) command. For example, if you already installed the `3.12` distribution you may see something like this:

```
$ hatch python show
Installed
┏━━━━━━┳━━━━━━━━━┓
┃ Name ┃ Version ┃
┡━━━━━━╇━━━━━━━━━┩
│ 3.12 │ 3.12.3 │
└──────┴─────────┘
Available
┏━━━━━━━━━━┳━━━━━━━━━┓
┃ Name ┃ Version ┃
┡━━━━━━━━━━╇━━━━━━━━━┩
│ 3.7 │ 3.7.9 │
├──────────┼─────────┤
│ 3.8 │ 3.8.19 │
├──────────┼─────────┤
│ 3.9 │ 3.9.19 │
├──────────┼─────────┤
│ 3.10 │ 3.10.14 │
├──────────┼─────────┤
│ 3.11 │ 3.11.9 │
├──────────┼─────────┤
│ pypy2.7 │ 7.3.15 │
├──────────┼─────────┤
│ pypy3.9 │ 7.3.15 │
├──────────┼─────────┤
│ pypy3.10 │ 7.3.15 │
└──────────┴─────────┘
```

## Finding installations

The Python executable of an installed distribution can be found by using the [`python find`](../../cli/reference.md#hatch-python-find) command. For example:

```
$ hatch python find 3.12
/home/.local/share/hatch/pythons/3.12/python/bin/python3
```

You can instead output its parent directory by using the `-p`/`--parent` flag:

```
$ hatch python find 3.12 --parent
/home/.local/share/hatch/pythons/3.12/python/bin
```

This is useful when other tools do not need to use the executable directly but require knowing the directory containing it.

## Updates

To update installed Python distributions, use the [`python update`](../../cli/reference.md#hatch-python-update) command. For example:

```
hatch python update 3.12 3.11 pypy3.10
```

When there are no updates available for a distribution, a warning will be displayed:

```
$ hatch python update 3.12
The latest version is already installed: 3.12.3
```

## Removal

To remove installed Python distributions, use the [`python remove`](../../cli/reference.md#hatch-python-remove) command. For example:

```
hatch python remove 3.12 3.11 pypy3.10
```
2 changes: 1 addition & 1 deletion docs/why.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ If you are using `nox` and you wish to migrate, and for some reason you [notify]

## Python management

Here we compare [Python management](cli/reference.md#hatch-python) to that of [pyenv](https://github.com/pyenv/pyenv).
Here we compare [Python management](tutorials/python/manage.md) to that of [pyenv](https://github.com/pyenv/pyenv).

- ***Cross-platform:*** Hatch allows for the same experience no matter the system whereas `pyenv` does not support Windows so you must use an [entirely different project](https://github.com/pyenv-win/pyenv-win) that tries to emulate the functionality.
- ***No build dependencies:*** Hatch guarantees that every [available distribution](cli/reference.md#hatch-python-show) is prebuilt whereas the alternative requires one to maintain a precise [build environment](https://github.com/pyenv/pyenv/wiki#suggested-build-environment) which differs by platform and potentially Python version. Another benefit to this is extremely fast installations since the distributions are simply downloaded and unpacked.
Expand Down
2 changes: 2 additions & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,8 @@ nav:
- Authentication: how-to/publish/auth.md
- Repository selection: how-to/publish/repo.md
- Tutorials:
- Python:
- Management: tutorials/python/manage.md
- Environments:
- Basic usage: tutorials/environment/basic-usage.md
- Testing:
Expand Down
2 changes: 1 addition & 1 deletion src/hatch/cli/python/find.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def find(app: Application, *, name: str, parent: bool, directory: str | None):
manager = app.get_python_manager(directory)
installed = manager.get_installed()
if name not in installed:
app.abort('Distribution not installed')
app.abort(f'Distribution not installed: {name}')

dist = installed[name]
app.display(str(dist.python_path.parent if parent else dist.python_path))
5 changes: 3 additions & 2 deletions src/hatch/cli/python/install.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,10 @@ def install(app: Application, *, names: tuple[str, ...], private: bool, update:
for name in compatible:
needs_update = False
if name in installed:
needs_update = installed[name].needs_update()
installed_dist = installed[name]
needs_update = installed_dist.needs_update()
if not needs_update:
app.display_warning(f'The latest version is already installed: {name}')
app.display_warning(f'The latest version is already installed: {installed_dist.version}')
continue

if not (update or app.confirm(f'Update {name}?')):
Expand Down
7 changes: 4 additions & 3 deletions tests/cli/python/test_find.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
def test_not_installed(hatch, helpers):
result = hatch('python', 'find', '3.10')
name = '3.10'
result = hatch('python', 'find', name)

assert result.exit_code == 1, result.output
assert result.output == helpers.dedent(
"""
Distribution not installed
f"""
Distribution not installed: {name}
"""
)

Expand Down
4 changes: 2 additions & 2 deletions tests/cli/python/test_install.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,14 +97,14 @@ def test_installation(
def test_already_installed_latest(hatch, helpers, temp_dir_data, path_append, dist_name, mocker):
install = mocker.patch('hatch.python.core.PythonManager.install')
install_dir = temp_dir_data / 'data' / 'pythons'
helpers.write_distribution(install_dir, dist_name)
installed_dist = helpers.write_distribution(install_dir, dist_name)

result = hatch('python', 'install', dist_name)

assert result.exit_code == 0, result.output
assert result.output == helpers.dedent(
f"""
The latest version is already installed: {dist_name}
The latest version is already installed: {installed_dist.version}
"""
)

Expand Down

0 comments on commit be42841

Please sign in to comment.