Skip to content

Commit

Permalink
✨ install with pip
Browse files Browse the repository at this point in the history
#36

---------

Co-authored-by: Oleh Prypin <oleh@pryp.in>
  • Loading branch information
juftin and oprypin committed Dec 6, 2023
1 parent cde23cc commit c22b8ca
Show file tree
Hide file tree
Showing 4 changed files with 359 additions and 83 deletions.
71 changes: 69 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ your environment type set to `pip-compile` (see [Configuration](#configuration))
The `hatch-pip-compile` plugin will automatically run `pip-compile` whenever your
environment needs to be updated. Behind the scenes, this plugin creates a lockfile
at `requirements.txt` (non-default lockfiles are located at
`requirements/requirements-{env_name}.txt`). Alongside `pip-compile`, this plugin also
uses [pip-sync] to install the dependencies from the lockfile into your environment.
`requirements/requirements-{env_name}.txt`). Once the dependencies are resolved
the plugin will install the lockfile into your virtual environment.

- [lock-filename](#lock-filename) - changing the default lockfile path
- [pip-compile-constraint](#pip-compile-constraint) - syncing dependency versions across environments
Expand All @@ -84,6 +84,11 @@ type to `pip-compile` to use this plugin for the respective environment.

### Configuration Options

The plugin gives you options to configure how lockfiles are generated and how they are installed
into your environment.

#### Generating Lockfiles

| name | type | description |
| ---------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| lock-filename | `str` | The filename of the ultimate lockfile. `default` env is `requirements.txt`, non-default is `requirements/requirements-{env_name}.txt` |
Expand All @@ -92,6 +97,13 @@ type to `pip-compile` to use this plugin for the respective environment.
| pip-compile-verbose | `bool` | Set to `true` to run `pip-compile` in verbose mode instead of quiet mode, set to `false` to silence warnings |
| pip-compile-args | `list[str]` | Additional command-line arguments to pass to `pip-compile` |

#### Installing Lockfiles

| name | type | description |
| ------------------------ | ----------- | ---------------------------------------------------------------------------------------------- |
| pip-compile-installer | `str` | Whether to use `pip` or `pip-sync` to install dependencies into the project. Defaults to `pip` |
| pip-compile-install-args | `list[str]` | Additional command-line arguments to pass to `pip-compile-installer` |

#### Examples

##### lock-filename
Expand Down Expand Up @@ -272,6 +284,61 @@ Optionally, if you would like to silence any warnings set the `pip-compile-verbo
pip-compile-verbose = true
```

##### pip-compile-installer

Whether to use [pip] or [pip-sync] to install dependencies into the project. Defaults to `pip`.
When you choose the `pip` option the plugin will run `pip install -r {lockfile}` under the hood
to install the dependencies. When you choose the `pip-sync` option `pip-sync {lockfile}` is invoked
by the plugin.

The key difference between these options is that `pip-sync` will uninstall any packages that are
not in the lockfile and remove them from your environment. `pip-sync` is useful if you want to ensure
that your environment is exactly the same as the lockfile. If the environment should be used
across different Python versions and platforms `pip` is the safer option to use.

- **_pyproject.toml_**

```toml
[tool.hatch.envs.<envName>]
type = "pip-compile"
pip-compile-installer = "pip-sync"
```

- **_hatch.toml_**

```toml
[envs.<envName>]
type = "pip-compile"
pip-compile-installer = "pip-sync"
```

##### pip-compile-install-args

Extra arguments to pass to `pip-compile-installer`. For example, if you'd like to use `pip` as the
installer but want to pass the `--no-deps` flag to `pip install` you can do so with this option:

- **_pyproject.toml_**

```toml
[tool.hatch.envs.<envName>]
type = "pip-compile"
pip-compile-installer = "pip"
pip-compile-install-args = [
"--no-deps"
]
```

- **_hatch.toml_**

```toml
[envs.<envName>]
type = "pip-compile"
pip-compile-installer = "pip"
pip-compile-install-args = [
"--no-deps"
]
```

## Upgrading Dependencies

Upgrading all dependencies can be as simple as deleting your lockfile and
Expand Down
71 changes: 69 additions & 2 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ your environment type set to `pip-compile` (see [Configuration](#configuration))
The `hatch-pip-compile` plugin will automatically run `pip-compile` whenever your
environment needs to be updated. Behind the scenes, this plugin creates a lockfile
at `requirements.txt` (non-default lockfiles are located at
`requirements/requirements-{env_name}.txt`). Alongside `pip-compile`, this plugin also
uses [pip-sync] to install the dependencies from the lockfile into your environment.
`requirements/requirements-{env_name}.txt`). Once the dependencies are resolved
the plugin will install the lockfile into your virtual environment.

- [lock-filename](#lock-filename) - changing the default lockfile path
- [pip-compile-constraint](#pip-compile-constraint) - syncing dependency versions across environments
Expand All @@ -82,6 +82,11 @@ type to `pip-compile` to use this plugin for the respective environment.

### Configuration Options

The plugin gives you options to configure how lockfiles are generated and how they are installed
into your environment.

#### Generating Lockfiles

| name | type | description |
| ---------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| lock-filename | `str` | The filename of the ultimate lockfile. `default` env is `requirements.txt`, non-default is `requirements/requirements-{env_name}.txt` |
Expand All @@ -90,6 +95,13 @@ type to `pip-compile` to use this plugin for the respective environment.
| pip-compile-verbose | `bool` | Set to `true` to run `pip-compile` in verbose mode instead of quiet mode, set to `false` to silence warnings |
| pip-compile-args | `list[str]` | Additional command-line arguments to pass to `pip-compile` |

#### Installing Lockfiles

| name | type | description |
| ------------------------ | ----------- | ---------------------------------------------------------------------------------------------- |
| pip-compile-installer | `str` | Whether to use `pip` or `pip-sync` to install dependencies into the project. Defaults to `pip` |
| pip-compile-install-args | `list[str]` | Additional command-line arguments to pass to `pip-compile-installer` |

#### Examples

##### lock-filename
Expand Down Expand Up @@ -270,6 +282,61 @@ Optionally, if you would like to silence any warnings set the `pip-compile-verbo
pip-compile-verbose = true
```

##### pip-compile-installer

Whether to use [pip] or [pip-sync] to install dependencies into the project. Defaults to `pip`.
When you choose the `pip` option the plugin will run `pip install -r {lockfile}` under the hood
to install the dependencies. When you choose the `pip-sync` option `pip-sync {lockfile}` is invoked
by the plugin.

The key difference between these options is that `pip-sync` will uninstall any packages that are
not in the lockfile and remove them from your environment. `pip-sync` is useful if you want to ensure
that your environment is exactly the same as the lockfile. If the environment should be used
across different Python versions and platforms `pip` is the safer option to use.

- **_pyproject.toml_**

```toml
[tool.hatch.envs.<envName>]
type = "pip-compile"
pip-compile-installer = "pip-sync"
```

- **_hatch.toml_**

```toml
[envs.<envName>]
type = "pip-compile"
pip-compile-installer = "pip-sync"
```

##### pip-compile-install-args

Extra arguments to pass to `pip-compile-installer`. For example, if you'd like to use `pip` as the
installer but want to pass the `--no-deps` flag to `pip install` you can do so with this option:

- **_pyproject.toml_**

```toml
[tool.hatch.envs.<envName>]
type = "pip-compile"
pip-compile-installer = "pip"
pip-compile-install-args = [
"--no-deps"
]
```

- **_hatch.toml_**

```toml
[envs.<envName>]
type = "pip-compile"
pip-compile-installer = "pip"
pip-compile-install-args = [
"--no-deps"
]
```

## Upgrading Dependencies

Upgrading all dependencies can be as simple as deleting your lockfile and
Expand Down
150 changes: 150 additions & 0 deletions hatch_pip_compile/installer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
"""
Package + Dependency Installers
"""

from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import TYPE_CHECKING

if TYPE_CHECKING:
from hatch_pip_compile.plugin import PipCompileEnvironment


@dataclass
class PluginInstaller(ABC):
"""
Package Installer for the plugin
This abstract base class is used to define the interface for
how the plugin should install packages and dependencies.
"""

environment: "PipCompileEnvironment"

@abstractmethod
def install_dependencies(self) -> None:
"""
Install the dependencies
"""

def sync_dependencies(self) -> None:
"""
Sync the dependencies - same as `install_dependencies`
"""
self.install_dependencies()

def install_project(self) -> None:
"""
Install the project (`--no-deps`)
"""
with self.environment.safe_activation():
self.environment.platform.check_command(
self.environment.construct_pip_install_command(
args=["--no-deps", str(self.environment.root)]
)
)

def install_project_dev_mode(self) -> None:
"""
Install the project in editable mode (`--no-deps`)
"""
with self.environment.safe_activation():
self.environment.platform.check_command(
self.environment.construct_pip_install_command(
args=["--no-deps", "--editable", str(self.environment.root)]
)
)


class PipInstaller(PluginInstaller):
"""
Plugin Installer for `pip`
"""

def install_dependencies(self) -> None:
"""
Install the dependencies with `pip`
"""
with self.environment.safe_activation():
if not self.environment.piptools_lock_file.exists():
return
extra_args = self.environment.config.get("pip-compile-install-args", [])
args = [*extra_args, "--requirement", str(self.environment.piptools_lock_file)]
install_command = self.environment.construct_pip_install_command(args=args)
self.environment.virtual_env.platform.check_command(install_command)


class PipSyncInstaller(PluginInstaller):
"""
Plugin Installer for `pip-sync`
"""

def install_dependencies(self) -> None:
"""
Install the dependencies with `pip-sync`
In the event that there are no dependencies, pip-sync will
uninstall everything in the environment before deleting the
lockfile.
"""
self.environment.install_pip_tools()
cmd = [
self.environment.virtual_env.python_info.executable,
"-m",
"piptools",
"sync",
"--verbose"
if self.environment.config.get("pip-compile-verbose", None) is True
else "--quiet",
"--python-executable",
str(self.environment.virtual_env.python_info.executable),
]
if not self.environment.dependencies:
self.environment.piptools_lock_file.write_text("")
extra_args = self.environment.config.get("pip-compile-install-args", [])
cmd.extend(extra_args)
cmd.append(str(self.environment.piptools_lock_file))
self.environment.virtual_env.platform.check_command(cmd)
if not self.environment.dependencies:
self.environment.piptools_lock_file.unlink()

def _full_install(self) -> None:
"""
Run the full install process
1) Run pip-compile (if necessary)
2) Run pip-sync
3) (re)install project
"""
with self.environment.safe_activation():
self.environment.run_pip_compile()
self.install_dependencies()
if not self.environment.skip_install:
if self.environment.dev_mode:
super().install_project_dev_mode()
else:
super().install_project()

def sync_dependencies(self):
"""
Sync dependencies
"""
self._full_install()

def install_project(self):
"""
Install the project the first time
The same implementation as `_full_install`
due to the way `pip-sync` uninstalls our root package
"""
self._full_install()

def install_project_dev_mode(self):
"""
Install the project the first time in dev mode
The same implementation as `_full_install`
due to the way `pip-sync` uninstalls our root package
"""
self._full_install()
Loading

0 comments on commit c22b8ca

Please sign in to comment.