mirror of
https://github.com/enpaul/tox-poetry-installer.git
synced 2025-10-28 07:00:43 +00:00
Compare commits
42 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b9b0eba90f | |||
| df8312f5ee | |||
| 1f102b16cb | |||
| a7cde7a9ab | |||
| b0bee11272 | |||
| accb4c3278 | |||
| ea183553c4 | |||
| 5c5536581b | |||
| b6ef671e67 | |||
| 99c10482fc | |||
| b32a212e82 | |||
| b6415888d9 | |||
| eba268a127 | |||
| d8d483e849 | |||
| c14313b7b0 | |||
| d1f161e0fa | |||
| 2961b55c9a | |||
| d4fb7046d8 | |||
| 8c4e596316 | |||
| 50f6e3d151 | |||
| 106d1bf6cf | |||
| b57b78d4e2 | |||
| 33e81f742a | |||
| 3a262d718c | |||
| 5979ec7a8a | |||
| c7bb3d35ea | |||
| 961e6f6acd | |||
| ed039de674 | |||
| db0cf6ce0c | |||
| e8d3f4fcac | |||
| 653622fd35 | |||
| de4c3515ec | |||
| 01bfdd74bd | |||
| 3e98ec81eb | |||
| b6534f86d0 | |||
| 74e3ed01c0 | |||
| f944843278 | |||
| 7c761073b3 | |||
| 6075ea6a3e | |||
| fdee2d9a8d | |||
| 699fb347da | |||
| a3bfd2687a |
8
.github/workflows/ci.yaml
vendored
8
.github/workflows/ci.yaml
vendored
@@ -23,8 +23,8 @@ jobs:
|
|||||||
uses: actions/setup-python@v1
|
uses: actions/setup-python@v1
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python.version }}
|
python-version: ${{ matrix.python.version }}
|
||||||
- name: Install tox
|
- name: Install project
|
||||||
run: pip install "tox>=3.20.0,<3.21.0" --upgrade
|
run: pip install .
|
||||||
- name: Run tests via ${{ matrix.python.toxenv }}
|
- name: Run tests via ${{ matrix.python.toxenv }}
|
||||||
run: tox -e ${{ matrix.python.toxenv }}
|
run: tox -e ${{ matrix.python.toxenv }}
|
||||||
Check:
|
Check:
|
||||||
@@ -35,7 +35,7 @@ jobs:
|
|||||||
uses: actions/setup-python@v1
|
uses: actions/setup-python@v1
|
||||||
with:
|
with:
|
||||||
python-version: 3.8
|
python-version: 3.8
|
||||||
- name: Install tox requirements
|
- name: Install project
|
||||||
run: pip install "tox>=3.20.0,<3.21.0" --upgrade
|
run: pip install .
|
||||||
- name: Run meta checks
|
- name: Run meta checks
|
||||||
run: tox -e static -e static-tests -e security
|
run: tox -e static -e static-tests -e security
|
||||||
|
|||||||
259
README.md
259
README.md
@@ -15,7 +15,10 @@ dependencies to be installed using [Poetry](https://python-poetry.org/) from its
|
|||||||
|
|
||||||
* [Installation](#installation)
|
* [Installation](#installation)
|
||||||
* [Quick Start](#quick-start)
|
* [Quick Start](#quick-start)
|
||||||
* [Usage Examples](#usage-examples)
|
* [Reference and Usage](#reference-and-usage)
|
||||||
|
* [Config Option Reference](#config-option-reference)
|
||||||
|
* [Error Reference](#error-reference)
|
||||||
|
* [Example Config](#example-config)
|
||||||
* [Known Drawbacks and Problems](#known-drawbacks-and-problems)
|
* [Known Drawbacks and Problems](#known-drawbacks-and-problems)
|
||||||
* [Why would I use this?](#why-would-i-use-this) (What problems does this solve?)
|
* [Why would I use this?](#why-would-i-use-this) (What problems does this solve?)
|
||||||
* [Developing](#developing)
|
* [Developing](#developing)
|
||||||
@@ -27,7 +30,9 @@ dependencies to be installed using [Poetry](https://python-poetry.org/) from its
|
|||||||
Related resources:
|
Related resources:
|
||||||
* [Poetry Python Project Manager](https://python-poetry.org/)
|
* [Poetry Python Project Manager](https://python-poetry.org/)
|
||||||
* [Tox Automation Project](https://tox.readthedocs.io/en/latest/)
|
* [Tox Automation Project](https://tox.readthedocs.io/en/latest/)
|
||||||
* [Tox plugins](https://tox.readthedocs.io/en/latest/plugins.html)
|
* [Poetry Dev-Dependencies Tox Plugin](https://github.com/sinoroc/tox-poetry-dev-dependencies)
|
||||||
|
* [Poetry Tox Plugin](https://github.com/tkukushkin/tox-poetry)
|
||||||
|
* [Other Tox plugins](https://tox.readthedocs.io/en/latest/plugins.html)
|
||||||
|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
@@ -58,183 +63,130 @@ If using Pip, ensure that the plugin is installed to the same environment as Tox
|
|||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
To require a Tox environment install all it's dependencies from the Poetry lockfile, add the
|
To add dependencies from the lockfile to a Tox environment, add the option `locked_deps`
|
||||||
`require_locked_deps = true` option to the environment configuration and remove all version
|
to the environment configuration and list names of dependencies (with no version
|
||||||
specifiers from the dependency list. The versions to install will be taken from the lockfile
|
specifier) under it:
|
||||||
directly:
|
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
[testenv]
|
[testenv]
|
||||||
description = Run the tests
|
description = Some very cool tests
|
||||||
require_locked_deps = true
|
locked_deps =
|
||||||
deps =
|
|
||||||
pytest
|
|
||||||
pytest-cov
|
|
||||||
black
|
black
|
||||||
pylint
|
pylint
|
||||||
mypy
|
mypy
|
||||||
commands = ...
|
commands = ...
|
||||||
```
|
```
|
||||||
|
|
||||||
To require specific dependencies be installed from the Poetry lockfile, and let the rest be
|
The standard `deps` option can be used in parallel with the `locked_deps` option to
|
||||||
installed using the default Tox installation backend, add the suffix `@poetry` to the dependencies.
|
install unlocked dependencies (dependencies not in the lockfile) alongside locked
|
||||||
In the example below the `pytest`, `pytest-cov`, and `black` dependencies will be installed from
|
dependencies:
|
||||||
the lockfile while `pylint` and `mypy` will be installed using the versions specified here:
|
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
[testenv]
|
[testenv]
|
||||||
description = Run the tests
|
description = Some very cool tests
|
||||||
require_locked_deps = true
|
locked_deps =
|
||||||
|
black
|
||||||
|
pylint
|
||||||
|
mypy
|
||||||
deps =
|
deps =
|
||||||
pytest@poetry
|
pytest == 6.1.1
|
||||||
pytest-cov@poetry
|
pytest-cov >= 2.10, <2.11
|
||||||
black@poetry
|
|
||||||
pylint >=2.5.0
|
|
||||||
mypy == 0.770
|
|
||||||
commands = ...
|
commands = ...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Alternatively, to quickly install all Poetry dev-dependencies to a Tox environment, add the
|
||||||
|
`install_dev_deps = true` option to the environment configuration.
|
||||||
|
|
||||||
**Note:** Regardless of the settings outlined above, all dependencies of the project package (the
|
**Note:** Regardless of the settings outlined above, all dependencies of the project package (the
|
||||||
one Tox is testing) will always be installed from the lockfile.
|
one Tox is testing) will always be installed from the lockfile.
|
||||||
|
|
||||||
|
|
||||||
## Usage Examples
|
## Reference and Usage
|
||||||
|
|
||||||
After installing the plugin to a project your Tox automation is already benefiting from the
|
### Config Option Reference
|
||||||
lockfile: when Tox installs your project package to one of your environments, all the dependencies
|
|
||||||
of your project package will be installed using the versions specified in the lockfile. This
|
|
||||||
happens automatically and requires no configuration changes.
|
|
||||||
|
|
||||||
But what about the rest of your Tox environment dependencies?
|
All options listed below are Tox environment options and can be applied to one or more
|
||||||
|
environment sections of the `tox.ini` file. They cannot be applied to the global Tox
|
||||||
|
configuration section.
|
||||||
|
|
||||||
Let's use an example `tox.ini` file, below, that defines two environments: the main `testenv` for
|
**NOTE:** Environment settings applied to the main `testenv` environment will be
|
||||||
running the project tests and `testenv:check` for running some other helpful tools:
|
inherited by child environments (i.e. `testenv:foo`) unless they are explicitly
|
||||||
|
overridden by the child environment's configuration.
|
||||||
|
|
||||||
|
| Option | Type | Default | Usage |
|
||||||
|
|:----------------------|:----------------|:--------|:-----------------------------------------------|
|
||||||
|
| `locked_deps` | Multi-line list | `[]` | Names of packages in the Poetry lockfile to install to the Tox environment. All dependencies specified here (and their dependencies) will be installed to the Tox environment using the version the Poetry lockfile specifies for them. |
|
||||||
|
| `require_locked_deps` | Bool | `false` | Indicates whether the environment should allow unlocked dependencies (dependencies not in the Poetry lockfile) to be installed alongside locked dependencies. If `true` then installation of unlocked dependencies will be blocked and an error will be raised if the `deps` option specifies any values. |
|
||||||
|
| `install_dev_deps` | Bool | `false` | Indicates whether all Poetry development dependencies should be installed to the environment. Provides a quick and easy way to install all dev-dependencies without needing to specify them individually. |
|
||||||
|
|
||||||
|
### Error Reference
|
||||||
|
|
||||||
|
* `LockedDepVersionConflictError` - Indicates that a locked dependency included a PEP-508 version
|
||||||
|
specifier (i.e. `pytest >=6.0, <6.1`). Locked dependencies always take their version from the
|
||||||
|
Poetry lockfile so specifying a specific version for a locked dependency is not supported.
|
||||||
|
* `LockedDepNotFoundError` - Indicates that a locked dependency could not be found in the Poetry
|
||||||
|
lockfile. This can be solved by [adding the dependency using Poetry](https://python-poetry.org/docs/cli/#add).
|
||||||
|
* `ExtraNotFoundError` - Indicates that the Tox `extras` option specified a project extra that
|
||||||
|
Poetry does not know about. This may be due to a misconfigured `pyproject.toml` or out of date
|
||||||
|
lockfile.
|
||||||
|
* `LockedDepsRequiredError` - Indicates that an environment with `require_locked_deps = true` also
|
||||||
|
specified unlocked dependencies using Tox's `deps` option. This can be solved by either setting
|
||||||
|
`require_locked_deps = false` (the default) or removing the `deps` option from the environment
|
||||||
|
configuration.
|
||||||
|
|
||||||
|
### Example Config
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
[tox]
|
[tox]
|
||||||
envlist = py37, static
|
envlist = py, foo, bar, baz
|
||||||
isolated_build = true
|
isolated_build = true
|
||||||
|
|
||||||
|
# The base testenv will always use locked dependencies and only ever installs the project package
|
||||||
|
# (and its dependencies) and the two pytest dependencies listed below
|
||||||
[testenv]
|
[testenv]
|
||||||
description = Run the tests
|
description = Some very cool tests
|
||||||
deps =
|
|
||||||
pytest == 5.3.0
|
|
||||||
commands = ...
|
|
||||||
|
|
||||||
[testenv:check]
|
|
||||||
description = Static formatting and quality enforcement
|
|
||||||
deps =
|
|
||||||
pylint >=2.4.4,<2.6.0
|
|
||||||
mypy == 0.770
|
|
||||||
black --pre
|
|
||||||
commands = ...
|
|
||||||
```
|
|
||||||
|
|
||||||
Let's focus on the `testenv:check` environment first. In this project there's no reason that any
|
|
||||||
of these tools should be a different version than what a human developer is using when installing
|
|
||||||
from the lockfile. We can require that these dependencies be installed from the lockfile by adding
|
|
||||||
the option `require_locked_deps = true` to the environment config, but this will cause an error:
|
|
||||||
|
|
||||||
```ini
|
|
||||||
[testenv:check]
|
|
||||||
description = Static formatting and quality enforcement
|
|
||||||
require_locked_deps = true
|
require_locked_deps = true
|
||||||
deps =
|
locked_deps =
|
||||||
pylint >=2.4.4,<2.6.0
|
pytest
|
||||||
mypy == 0.770
|
pytest-cov
|
||||||
black --pre
|
|
||||||
commands = ...
|
commands = ...
|
||||||
```
|
|
||||||
|
|
||||||
Running Tox using this config gives us this error:
|
# This environment also requires locked dependencies, but the "skip_install" setting means that
|
||||||
|
# the project dependencies will not be installed to the environment from the lockfile
|
||||||
```
|
[testenv:foo]
|
||||||
tox_poetry_installer.LockedDepVersionConflictError: Locked dependency 'pylint >=2.4.4,<2.6.0' cannot include version specifier
|
description = FOObarbaz
|
||||||
```
|
skip_install = true
|
||||||
|
|
||||||
This is because we told the Tox environment to require all dependencies be locked, but then also
|
|
||||||
specified a specific version constraint for Pylint. With the `require_locked_deps = true` setting
|
|
||||||
Tox expects all dependencies to take their version from the lockfile, so when it gets conflicting
|
|
||||||
information it errors. We can fix this by simply removing all version specifiers from the
|
|
||||||
environment dependency list:
|
|
||||||
|
|
||||||
```ini
|
|
||||||
[testenv:check]
|
|
||||||
description = Static formatting and quality enforcement
|
|
||||||
require_locked_deps = true
|
require_locked_deps = true
|
||||||
deps =
|
locked_deps =
|
||||||
pylint
|
requests
|
||||||
|
toml
|
||||||
|
ruamel.yaml
|
||||||
|
commands = ...
|
||||||
|
|
||||||
|
# This environment allows unlocked dependencies to be installed ad-hoc. Below, the "mypy" and
|
||||||
|
# "pylint" dependencies (and their dependencies) will be installed from the Poetry lockfile but the
|
||||||
|
# "black" dependency will be installed using the default Tox backend. Note, this environment does
|
||||||
|
# not specify "require_locked_deps = true" to allow the unlocked "black" dependency without raising
|
||||||
|
# an error.
|
||||||
|
[testenv:bar]
|
||||||
|
description = fooBARbaz
|
||||||
|
locked_deps =
|
||||||
mypy
|
mypy
|
||||||
|
pylint
|
||||||
|
deps =
|
||||||
black
|
black
|
||||||
commands = ...
|
commands = ...
|
||||||
```
|
|
||||||
|
|
||||||
Now all the dependencies will be installed from the lockfile. If Poetry updates the lockfile with
|
# This environment requires locked dependencies but does not specify any. Instead it specifies the
|
||||||
a new version then that updated version will be automatically installed when the Tox environment is
|
# "install_dev_deps = true" option which will cause all of the Poetry dev-dependencies to be
|
||||||
recreated.
|
# installed from the lockfile.
|
||||||
|
[testenv:baz]
|
||||||
Now let's look at the `testenv` environment. Let's make the same changes to the `testenv`
|
description = foobarBAZ
|
||||||
environment that we made to `testenv:check` above; remove the PyTest version and add
|
install_dev_deps = true
|
||||||
`require_locked_deps = true`. Then imagine that we want to add the
|
|
||||||
[Requests](https://requests.readthedocs.io/en/master/) library to the test environment: we
|
|
||||||
can add `requests` as a dependency of the test environment, but this will cause an error:
|
|
||||||
|
|
||||||
```ini
|
|
||||||
[testenv]
|
|
||||||
description = Run the tests
|
|
||||||
require_locked_deps = true
|
require_locked_deps = true
|
||||||
deps =
|
|
||||||
pytest
|
|
||||||
requests
|
|
||||||
commands = ...
|
commands = ...
|
||||||
```
|
```
|
||||||
|
|
||||||
Running Tox with this config gives us this error:
|
|
||||||
|
|
||||||
```
|
|
||||||
tox_poetry_installer.LockedDepNotFoundError: No version of locked dependency 'requests' found in the project lockfile
|
|
||||||
```
|
|
||||||
|
|
||||||
This is because `requests` is not in our lockfile yet. Tox will refuse to install a dependency
|
|
||||||
that isn't in the lockfile to an an environment that specifies `require_locked_deps = true`. We
|
|
||||||
can fix this by running `poetry add requests --dev` to add it to the lockfile.
|
|
||||||
|
|
||||||
Now let's combine dependencies from the lockfile with dependencies that are
|
|
||||||
specified in-line in the Tox environment configuration.
|
|
||||||
[This isn't generally recommended](#why-would-i-use-this), but it is a valid use case and
|
|
||||||
fully supported by this plugin. Let's modify the `testenv` configuration to install PyTest
|
|
||||||
from the lockfile but then install an older version of the Requests library.
|
|
||||||
|
|
||||||
The first thing to do is remove the `require_locked_deps = true` setting so that we can install
|
|
||||||
Requests as an unlocked dependency. Then we can add our version specifier to the `requests`
|
|
||||||
entry in the dependency list:
|
|
||||||
|
|
||||||
```ini
|
|
||||||
[testenv]
|
|
||||||
description = Run the tests
|
|
||||||
deps =
|
|
||||||
pytest
|
|
||||||
requests >=2.2.0,<2.10.0
|
|
||||||
commands = ...
|
|
||||||
```
|
|
||||||
|
|
||||||
However we still want `pytest` to be installed from the lockfile, so the final step is to tell Tox
|
|
||||||
to install it from the lockfile by adding the suffix `@poetry` to the `pytest` entry in the
|
|
||||||
dependency list:
|
|
||||||
|
|
||||||
```ini
|
|
||||||
[testenv]
|
|
||||||
description = Run the tests
|
|
||||||
deps =
|
|
||||||
pytest@poetry
|
|
||||||
requests >=2.2.0,<2.10.0
|
|
||||||
commands = ...
|
|
||||||
```
|
|
||||||
|
|
||||||
Now when the `testenv` environment is created it will install PyTest (and all of its dependencies)
|
|
||||||
from the lockfile while it will install Requests (and all of its dependencies) using the default
|
|
||||||
Tox installation backend.
|
|
||||||
|
|
||||||
|
|
||||||
## Known Drawbacks and Problems
|
## Known Drawbacks and Problems
|
||||||
|
|
||||||
@@ -247,18 +199,11 @@ Tox installation backend.
|
|||||||
* [`indexserver`](https://tox.readthedocs.io/en/latest/config.html#conf-indexserver)
|
* [`indexserver`](https://tox.readthedocs.io/en/latest/config.html#conf-indexserver)
|
||||||
* [`usedevelop`](https://tox.readthedocs.io/en/latest/config.html#conf-indexserver)
|
* [`usedevelop`](https://tox.readthedocs.io/en/latest/config.html#conf-indexserver)
|
||||||
|
|
||||||
* The [`extras`](https://tox.readthedocs.io/en/latest/config.html#conf-extras) setting in `tox.ini`
|
* Tox will not automatically detect changes to the locked dependencies and so
|
||||||
does not work. Optional dependencies of the project package will not be installed to Tox
|
environments will not be automatically rebuilt when locked dependencies are changed.
|
||||||
environments. (See the [road map](#roadmap))
|
When changing the locked dependencies (or their versions) the environments will need to
|
||||||
|
be manually rebuilt using either the `-r`/`--recreate` CLI option or the
|
||||||
* The plugin currently depends on `poetry<1.1.0`. This can be a different version than Poetry being
|
`recreate = true` option in `tox.ini`.
|
||||||
used for actual project development. (See the [road map](#roadmap))
|
|
||||||
|
|
||||||
* Tox environments automatically inherit their settings from the main `testenv` environment. This
|
|
||||||
means that if the `require_locked_deps = true` is specified for the `testenv` environment then
|
|
||||||
all environments will also require locked dependencies. This can be overwritten by explicitly
|
|
||||||
specifying `require_locked_deps = false` on child environments where unlocked dependencies are
|
|
||||||
needed.
|
|
||||||
|
|
||||||
* There are a handful of packages that cannot be installed from the lockfile, whether as specific
|
* There are a handful of packages that cannot be installed from the lockfile, whether as specific
|
||||||
dependencies or as transient dependencies (dependencies of dependencies). This is due to
|
dependencies or as transient dependencies (dependencies of dependencies). This is due to
|
||||||
@@ -373,7 +318,7 @@ dependencies:
|
|||||||
[testenv]
|
[testenv]
|
||||||
description = Some very cool tests
|
description = Some very cool tests
|
||||||
require_locked_deps = true
|
require_locked_deps = true
|
||||||
deps =
|
locked_deps =
|
||||||
foo
|
foo
|
||||||
bar
|
bar
|
||||||
baz
|
baz
|
||||||
@@ -390,7 +335,8 @@ automatically install these new versions without needing any changes to the conf
|
|||||||
|
|
||||||
## Developing
|
## Developing
|
||||||
|
|
||||||
This project requires Poetry version 1.0+, see the [installation instructions here](https://python-poetry.org/docs/#installation).
|
This project requires a developer to have Poetry version 1.0+ installed on their workstation, see
|
||||||
|
the [installation instructions here](https://python-poetry.org/docs/#installation).
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Clone the repository...
|
# Clone the repository...
|
||||||
@@ -443,7 +389,7 @@ for usage in production environments.
|
|||||||
|
|
||||||
- [X] Verify that primary package dependencies (from the `.package` env) are installed
|
- [X] Verify that primary package dependencies (from the `.package` env) are installed
|
||||||
correctly using the Poetry backend.
|
correctly using the Poetry backend.
|
||||||
- [ ] Support the [`extras`](https://tox.readthedocs.io/en/latest/config.html#conf-extras)
|
- [X] Support the [`extras`](https://tox.readthedocs.io/en/latest/config.html#conf-extras)
|
||||||
Tox configuration option ([#4](https://github.com/enpaul/tox-poetry-installer/issues/4))
|
Tox configuration option ([#4](https://github.com/enpaul/tox-poetry-installer/issues/4))
|
||||||
- [X] Add per-environment Tox configuration option to fall back to default installation
|
- [X] Add per-environment Tox configuration option to fall back to default installation
|
||||||
backend.
|
backend.
|
||||||
@@ -451,12 +397,13 @@ for usage in production environments.
|
|||||||
Poetry backend. ([#5](https://github.com/enpaul/tox-poetry-installer/issues/5))
|
Poetry backend. ([#5](https://github.com/enpaul/tox-poetry-installer/issues/5))
|
||||||
- [X] Add trivial tests to ensure the project metadata is consistent between the pyproject.toml
|
- [X] Add trivial tests to ensure the project metadata is consistent between the pyproject.toml
|
||||||
and the module constants.
|
and the module constants.
|
||||||
- [ ] Update to use [poetry-core](https://github.com/python-poetry/poetry-core) and
|
- [X] Update to use [poetry-core](https://github.com/python-poetry/poetry-core) and
|
||||||
improve robustness of the Tox and Poetry module imports
|
improve robustness of the Tox and Poetry module imports
|
||||||
to avoid potentially breaking API changes in upstream packages. ([#2](https://github.com/enpaul/tox-poetry-installer/issues/2))
|
to avoid potentially breaking API changes in upstream packages. ([#2](https://github.com/enpaul/tox-poetry-installer/issues/2))
|
||||||
- [ ] Find and implement a way to mitigate the [UNSAFE_DEPENDENCIES issue](https://github.com/python-poetry/poetry/issues/1584) in Poetry.
|
- [ ] Find and implement a way to mitigate the [UNSAFE_DEPENDENCIES issue](https://github.com/python-poetry/poetry/issues/1584) in Poetry.
|
||||||
([#6](https://github.com/enpaul/tox-poetry-installer/issues/6))
|
([#6](https://github.com/enpaul/tox-poetry-installer/issues/6))
|
||||||
- [ ] Fix logging to make proper use of Tox's logging reporter infrastructure
|
- [ ] Fix logging to make proper use of Tox's logging reporter infrastructure ([#3](https://github.com/enpaul/tox-poetry-installer/issues/3))
|
||||||
|
- [X] Add configuration option for installing all dev-dependencies to a testenv ([#14](https://github.com/enpaul/tox-poetry-installer/issues/14))
|
||||||
|
|
||||||
### Path to Stable
|
### Path to Stable
|
||||||
|
|
||||||
@@ -464,6 +411,6 @@ Everything in Beta plus...
|
|||||||
|
|
||||||
- [ ] Add tests for each feature version of Tox between 2.3 and 3.20
|
- [ ] Add tests for each feature version of Tox between 2.3 and 3.20
|
||||||
- [ ] Add tests for Python-3.6, 3.7, and 3.8
|
- [ ] Add tests for Python-3.6, 3.7, and 3.8
|
||||||
- [ ] Add Github Actions based CI
|
- [X] Add Github Actions based CI
|
||||||
- [ ] Add CI for CPython, PyPy, and Conda
|
- [ ] Add CI for CPython, PyPy, and Conda
|
||||||
- [ ] Add CI for Linux and Windows
|
- [ ] Add CI for Linux and Windows
|
||||||
|
|||||||
856
poetry.lock
generated
856
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,14 +1,17 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "tox-poetry-installer"
|
name = "tox-poetry-installer"
|
||||||
version = "0.2.4"
|
version = "0.5.0"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
authors = ["Ethan Paul <24588726+enpaul@users.noreply.github.com>"]
|
authors = ["Ethan Paul <24588726+enpaul@users.noreply.github.com>"]
|
||||||
description = "Tox plugin to install Tox environment dependencies using the Poetry backend and lockfile"
|
description = "Tox plugin to install Tox environment dependencies using the Poetry backend and lockfile"
|
||||||
repository = "https://github.com/enpaul/tox-poetry-installer/"
|
repository = "https://github.com/enpaul/tox-poetry-installer/"
|
||||||
packages = [
|
packages = [
|
||||||
{include = "tox_poetry_installer.py"},
|
{include = "tox_poetry_installer"},
|
||||||
{include = "tests/*.py", format = "sdist"}
|
{include = "tests/*.py", format = "sdist"}
|
||||||
]
|
]
|
||||||
|
include = [
|
||||||
|
"tox_poetry_installer/py.typed"
|
||||||
|
]
|
||||||
keywords = ["tox", "poetry", "plugin"]
|
keywords = ["tox", "poetry", "plugin"]
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
classifiers = [
|
classifiers = [
|
||||||
@@ -31,6 +34,7 @@ poetry_installer = "tox_poetry_installer"
|
|||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = "^3.6"
|
python = "^3.6"
|
||||||
poetry = "^1.0.0"
|
poetry = "^1.0.0"
|
||||||
|
poetry-core = "^1.0.0"
|
||||||
tox = "^2.3.0 || ^3.0.0"
|
tox = "^2.3.0 || ^3.0.0"
|
||||||
|
|
||||||
[tool.poetry.dev-dependencies]
|
[tool.poetry.dev-dependencies]
|
||||||
@@ -48,5 +52,5 @@ toml = "^0.10.1"
|
|||||||
tox = "^3.20.0"
|
tox = "^3.20.0"
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["poetry>=1.0.0"]
|
requires = ["poetry-core>=1.0.0"]
|
||||||
build-backend = "poetry.masonry.api"
|
build-backend = "poetry.core.masonry.api"
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ from pathlib import Path
|
|||||||
|
|
||||||
import toml
|
import toml
|
||||||
|
|
||||||
import tox_poetry_installer
|
from tox_poetry_installer import __about__
|
||||||
|
|
||||||
|
|
||||||
def test_metadata():
|
def test_metadata():
|
||||||
@@ -16,16 +16,14 @@ def test_metadata():
|
|||||||
with (Path(__file__).resolve().parent / ".." / "pyproject.toml").open() as infile:
|
with (Path(__file__).resolve().parent / ".." / "pyproject.toml").open() as infile:
|
||||||
pyproject = toml.load(infile, _dict=dict)
|
pyproject = toml.load(infile, _dict=dict)
|
||||||
|
|
||||||
assert pyproject["tool"]["poetry"]["name"] == tox_poetry_installer.__title__
|
assert pyproject["tool"]["poetry"]["name"] == __about__.__title__
|
||||||
assert pyproject["tool"]["poetry"]["version"] == tox_poetry_installer.__version__
|
assert pyproject["tool"]["poetry"]["version"] == __about__.__version__
|
||||||
assert pyproject["tool"]["poetry"]["license"] == tox_poetry_installer.__license__
|
assert pyproject["tool"]["poetry"]["license"] == __about__.__license__
|
||||||
assert (
|
assert pyproject["tool"]["poetry"]["description"] == __about__.__summary__
|
||||||
pyproject["tool"]["poetry"]["description"] == tox_poetry_installer.__summary__
|
assert pyproject["tool"]["poetry"]["repository"] == __about__.__url__
|
||||||
)
|
|
||||||
assert pyproject["tool"]["poetry"]["repository"] == tox_poetry_installer.__url__
|
|
||||||
assert (
|
assert (
|
||||||
all(
|
all(
|
||||||
item in tox_poetry_installer.__authors__
|
item in __about__.__authors__
|
||||||
for item in pyproject["tool"]["poetry"]["authors"]
|
for item in pyproject["tool"]["poetry"]["authors"]
|
||||||
)
|
)
|
||||||
is True
|
is True
|
||||||
@@ -33,7 +31,7 @@ def test_metadata():
|
|||||||
assert (
|
assert (
|
||||||
all(
|
all(
|
||||||
item in pyproject["tool"]["poetry"]["authors"]
|
item in pyproject["tool"]["poetry"]["authors"]
|
||||||
for item in tox_poetry_installer.__authors__
|
for item in __about__.__authors__
|
||||||
)
|
)
|
||||||
is True
|
is True
|
||||||
)
|
)
|
||||||
|
|||||||
52
tox.ini
52
tox.ini
@@ -5,37 +5,48 @@ isolated_build = true
|
|||||||
[testenv]
|
[testenv]
|
||||||
description = Run the tests
|
description = Run the tests
|
||||||
require_locked_deps = true
|
require_locked_deps = true
|
||||||
deps =
|
locked_deps =
|
||||||
pytest
|
pytest
|
||||||
pytest-cov
|
pytest-cov
|
||||||
toml
|
toml
|
||||||
commands =
|
commands =
|
||||||
pytest --cov tox_poetry_installer --cov-config {toxinidir}/.coveragerc tests/ --cov-report term-missing
|
pytest --cov {envsitepackagesdir}/tox_poetry_installer --cov-config {toxinidir}/.coveragerc --cov-report term-missing tests/
|
||||||
|
|
||||||
[testenv:static]
|
[testenv:static]
|
||||||
description = Static formatting and quality enforcement
|
description = Static formatting and quality enforcement
|
||||||
require_locked_deps = true
|
|
||||||
basepython = python3.8
|
basepython = python3.8
|
||||||
|
platform = linux
|
||||||
ignore_errors = true
|
ignore_errors = true
|
||||||
deps =
|
require_locked_deps = true
|
||||||
|
locked_deps =
|
||||||
pylint
|
pylint
|
||||||
mypy
|
mypy
|
||||||
black
|
black
|
||||||
reorder-python-imports
|
reorder-python-imports
|
||||||
pre-commit
|
pre-commit
|
||||||
|
allowlist_externals =
|
||||||
|
bash
|
||||||
commands =
|
commands =
|
||||||
black {toxinidir}/tox_poetry_installer.py
|
black {toxinidir}/tox_poetry_installer/
|
||||||
reorder-python-imports {toxinidir}/tox_poetry_installer.py
|
# Oh man this is a doozy. If submodules are ever added to this plugin this will break, but I'm
|
||||||
|
# frustrated enough at this point that I'll need to take another look at it later to fix that.
|
||||||
|
# reorder-python-imports doesn't support handling directories on the CLI
|
||||||
|
# (https://github.com/asottile/reorder_python_imports/pull/76) and because the command is
|
||||||
|
# invoked directly (see comment below) we need file globbing to work around it.
|
||||||
|
# The "--unclassifiable-application-module" is a work around for reorder-python-imports not
|
||||||
|
# properly detecting the top-level module when run in a bash-wrapped command like this.
|
||||||
|
bash -c "reorder-python-imports {toxinidir}/tox_poetry_installer/*.py --unclassifiable-application-module tox_poetry_installer"
|
||||||
pre-commit run --all-files
|
pre-commit run --all-files
|
||||||
pylint --rcfile {toxinidir}/.pylintrc {toxinidir}/tox_poetry_installer.py
|
pylint --rcfile {toxinidir}/.pylintrc {toxinidir}/tox_poetry_installer/
|
||||||
mypy --ignore-missing-imports --no-strict-optional {toxinidir}/tox_poetry_installer.py
|
mypy --ignore-missing-imports --no-strict-optional {toxinidir}/tox_poetry_installer/
|
||||||
|
|
||||||
[testenv:static-tests]
|
[testenv:static-tests]
|
||||||
description = Static formatting and quality enforcement for the tests
|
description = Static formatting and quality enforcement for the tests
|
||||||
require_locked_deps = true
|
|
||||||
basepython = python3.8
|
basepython = python3.8
|
||||||
|
platform = linux
|
||||||
ingore_errors = true
|
ingore_errors = true
|
||||||
deps =
|
require_locked_deps = true
|
||||||
|
locked_deps =
|
||||||
pylint
|
pylint
|
||||||
mypy
|
mypy
|
||||||
black
|
black
|
||||||
@@ -44,23 +55,26 @@ allowlist_externals =
|
|||||||
bash
|
bash
|
||||||
commands =
|
commands =
|
||||||
black {toxinidir}/tests/
|
black {toxinidir}/tests/
|
||||||
|
# These bash-wrapped commands hurt my face, but these tools expect directories to be valid
|
||||||
|
# python modules, which the "tests/" directory is not. Since tox calls all commands directly
|
||||||
|
# (which is good) file globbing doesn't work. To make file globbing work they need to be wrapped
|
||||||
|
# in a bash call (which is bad).
|
||||||
bash -c "reorder-python-imports {toxinidir}/tests/*.py --unclassifiable-application-module tox_poetry_installer"
|
bash -c "reorder-python-imports {toxinidir}/tests/*.py --unclassifiable-application-module tox_poetry_installer"
|
||||||
bash -c "pylint --rcfile {toxinidir}/.pylintrc {toxinidir}/tests/*.py"
|
bash -c "pylint --rcfile {toxinidir}/.pylintrc {toxinidir}/tests/*.py"
|
||||||
bash -c "mypy --ignore-missing-imports --no-strict-optional {toxinidir}/tests/*.py"
|
bash -c "mypy --ignore-missing-imports --no-strict-optional {toxinidir}/tests/*.py"
|
||||||
|
|
||||||
[testenv:security]
|
[testenv:security]
|
||||||
description = Security checks
|
description = Security checks
|
||||||
require_locked_deps = true
|
|
||||||
basepython = python3.8
|
basepython = python3.8
|
||||||
ignore_errors = true
|
platform = linux
|
||||||
skip_install = true
|
ingore_errors = true
|
||||||
deps =
|
require_locked_deps = true
|
||||||
|
locked_deps =
|
||||||
bandit
|
bandit
|
||||||
safety
|
safety
|
||||||
poetry
|
poetry
|
||||||
allowlist_externals =
|
|
||||||
bash
|
|
||||||
commands =
|
commands =
|
||||||
bandit --quiet {toxinidir}/tox_poetry_installer.py
|
bandit --recursive --quiet {toxinidir}/tox_poetry_installer/
|
||||||
bash -c "bandit --quiet --skip B101 {toxinidir}/tests/*.py"
|
bandit --recursive --quiet --skip B101 {toxinidir}/tests/
|
||||||
bash -c "poetry export --format requirements.txt --without-hashes --dev | safety check --stdin --bare"
|
poetry export --format requirements.txt --output {envtmpdir}/requirements.txt --without-hashes --dev
|
||||||
|
safety check --bare --file {envtmpdir}/requirements.txt
|
||||||
|
|||||||
@@ -1,301 +0,0 @@
|
|||||||
"""Tox plugin for installing environments using Poetry
|
|
||||||
|
|
||||||
This plugin makes use of the ``tox_testenv_install_deps`` Tox plugin hook to replace the default
|
|
||||||
installation functionality to install dependencies from the Poetry lockfile for the project. It
|
|
||||||
does this by using ``poetry`` to read in the lockfile, identify necessary dependencies, and then
|
|
||||||
use Poetry's ``PipInstaller`` class to install those packages into the Tox environment.
|
|
||||||
"""
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import Dict
|
|
||||||
from typing import List
|
|
||||||
from typing import NamedTuple
|
|
||||||
from typing import Sequence
|
|
||||||
from typing import Set
|
|
||||||
from typing import Tuple
|
|
||||||
|
|
||||||
from poetry.core.packages import Package as PoetryPackage
|
|
||||||
from poetry.factory import Factory as PoetryFactory
|
|
||||||
from poetry.installation.pip_installer import PipInstaller as PoetryPipInstaller
|
|
||||||
from poetry.io.null_io import NullIO as PoetryNullIO
|
|
||||||
from poetry.poetry import Poetry
|
|
||||||
from poetry.puzzle.provider import Provider as PoetryProvider
|
|
||||||
from poetry.utils.env import VirtualEnv as PoetryVirtualEnv
|
|
||||||
from tox import hookimpl
|
|
||||||
from tox import reporter
|
|
||||||
from tox.action import Action as ToxAction
|
|
||||||
from tox.config import DepConfig as ToxDepConfig
|
|
||||||
from tox.config import Parser as ToxParser
|
|
||||||
from tox.venv import VirtualEnv as ToxVirtualEnv
|
|
||||||
|
|
||||||
|
|
||||||
__title__ = "tox-poetry-installer"
|
|
||||||
__summary__ = "Tox plugin to install Tox environment dependencies using the Poetry backend and lockfile"
|
|
||||||
__version__ = "0.2.4"
|
|
||||||
__url__ = "https://github.com/enpaul/tox-poetry-installer/"
|
|
||||||
__license__ = "MIT"
|
|
||||||
__authors__ = ["Ethan Paul <24588726+enpaul@users.noreply.github.com>"]
|
|
||||||
|
|
||||||
|
|
||||||
# Valid PEP508 version delimiters. These are used to test whether a given string (specifically a
|
|
||||||
# dependency name) is just a package name or also includes a version identifier.
|
|
||||||
_PEP508_VERSION_DELIMITERS: Tuple[str, ...] = ("~=", "==", "!=", ">", "<")
|
|
||||||
|
|
||||||
# Prefix all reporter messages should include to indicate that they came from this module in the
|
|
||||||
# console output.
|
|
||||||
_REPORTER_PREFIX = f"[{__title__}]:"
|
|
||||||
|
|
||||||
# Suffix that indicates an env dependency should be treated as a locked dependency and thus be
|
|
||||||
# installed from the lockfile. Will be automatically stripped off of a dependency name during
|
|
||||||
# sorting so that the resulting string is just the valid package name. This becomes optional when
|
|
||||||
# the "require_locked_deps" option is true for an environment; in that case a bare dependency like
|
|
||||||
# 'foo' is treated the same as an explicitly locked dependency like 'foo@poetry'
|
|
||||||
_MAGIC_SUFFIX_MARKER = "@poetry"
|
|
||||||
|
|
||||||
|
|
||||||
class _SortedEnvDeps(NamedTuple):
|
|
||||||
unlocked_deps: List[ToxDepConfig]
|
|
||||||
locked_deps: List[ToxDepConfig]
|
|
||||||
|
|
||||||
|
|
||||||
class ToxPoetryInstallerException(Exception):
|
|
||||||
"""Error while installing locked dependencies to the test environment"""
|
|
||||||
|
|
||||||
|
|
||||||
class LockedDepVersionConflictError(ToxPoetryInstallerException):
|
|
||||||
"""Locked dependencies cannot specify an alternate version for installation"""
|
|
||||||
|
|
||||||
|
|
||||||
class LockedDepNotFoundError(ToxPoetryInstallerException):
|
|
||||||
"""Locked dependency was not found in the lockfile"""
|
|
||||||
|
|
||||||
|
|
||||||
def _sort_env_deps(venv: ToxVirtualEnv) -> _SortedEnvDeps:
|
|
||||||
"""Sorts the environment dependencies by lock status
|
|
||||||
|
|
||||||
Lock status determines whether a given environment dependency will be installed from the
|
|
||||||
lockfile using the Poetry backend, or whether this plugin will skip it and allow it to be
|
|
||||||
installed using the default pip-based backend (an unlocked dependency).
|
|
||||||
|
|
||||||
.. note:: A locked dependency must follow a required format. To avoid reinventing the wheel
|
|
||||||
(no pun intended) this module does not have any infrastructure for parsing PEP-508
|
|
||||||
version specifiers, and so requires locked dependencies to be specified with no
|
|
||||||
version (the installed version being taken from the lockfile). If a dependency is
|
|
||||||
specified as locked and its name is also a PEP-508 string then an error will be
|
|
||||||
raised.
|
|
||||||
"""
|
|
||||||
|
|
||||||
reporter.verbosity1(
|
|
||||||
f"{_REPORTER_PREFIX} sorting {len(venv.envconfig.deps)} env dependencies by lock requirement"
|
|
||||||
)
|
|
||||||
unlocked_deps = []
|
|
||||||
locked_deps = []
|
|
||||||
|
|
||||||
for dep in venv.envconfig.deps:
|
|
||||||
if venv.envconfig.require_locked_deps:
|
|
||||||
reporter.verbosity1(
|
|
||||||
f"{_REPORTER_PREFIX} lock required for env, treating '{dep.name}' as locked env dependency"
|
|
||||||
)
|
|
||||||
dep.name = dep.name.replace(_MAGIC_SUFFIX_MARKER, "")
|
|
||||||
locked_deps.append(dep)
|
|
||||||
else:
|
|
||||||
if dep.name.endswith(_MAGIC_SUFFIX_MARKER):
|
|
||||||
reporter.verbosity1(
|
|
||||||
f"{_REPORTER_PREFIX} specification includes marker '{_MAGIC_SUFFIX_MARKER}', treating '{dep.name}' as locked env dependency"
|
|
||||||
)
|
|
||||||
dep.name = dep.name.replace(_MAGIC_SUFFIX_MARKER, "")
|
|
||||||
locked_deps.append(dep)
|
|
||||||
else:
|
|
||||||
reporter.verbosity1(
|
|
||||||
f"{_REPORTER_PREFIX} specification does not include marker '{_MAGIC_SUFFIX_MARKER}', treating '{dep.name}' as unlocked env dependency"
|
|
||||||
)
|
|
||||||
unlocked_deps.append(dep)
|
|
||||||
|
|
||||||
reporter.verbosity1(
|
|
||||||
f"{_REPORTER_PREFIX} identified {len(locked_deps)} locked env dependencies for installation from poetry lockfile: {[item.name for item in locked_deps]}"
|
|
||||||
)
|
|
||||||
reporter.verbosity1(
|
|
||||||
f"{_REPORTER_PREFIX} identified {len(unlocked_deps)} unlocked env dependencies for installation using default backend: {[item.name for item in unlocked_deps]}"
|
|
||||||
)
|
|
||||||
|
|
||||||
return _SortedEnvDeps(locked_deps=locked_deps, unlocked_deps=unlocked_deps)
|
|
||||||
|
|
||||||
|
|
||||||
def _install_to_venv(
|
|
||||||
poetry: Poetry, venv: ToxVirtualEnv, packages: Sequence[PoetryPackage]
|
|
||||||
):
|
|
||||||
"""Install a bunch of packages to a virtualenv
|
|
||||||
|
|
||||||
:param poetry: Poetry object the packages were sourced from
|
|
||||||
:param venv: Tox virtual environment to install the packages to
|
|
||||||
:param packages: List of packages to install to the virtual environment
|
|
||||||
"""
|
|
||||||
installer = PoetryPipInstaller(
|
|
||||||
env=PoetryVirtualEnv(path=Path(venv.envconfig.envdir)),
|
|
||||||
io=PoetryNullIO(),
|
|
||||||
pool=poetry.pool,
|
|
||||||
)
|
|
||||||
|
|
||||||
for dependency in packages:
|
|
||||||
reporter.verbosity1(f"{_REPORTER_PREFIX} installing {dependency}")
|
|
||||||
installer.install(dependency)
|
|
||||||
|
|
||||||
|
|
||||||
def _find_transients(poetry: Poetry, dependency_name: str) -> Set[PoetryPackage]:
|
|
||||||
"""Using a poetry object identify all dependencies of a specific dependency
|
|
||||||
|
|
||||||
:param poetry: Populated poetry object which can be used to build a populated locked
|
|
||||||
repository object.
|
|
||||||
:param dependency_name: Bare name (without version) of the dependency to fetch the transient
|
|
||||||
dependencies of.
|
|
||||||
:returns: List of packages that need to be installed for the requested dependency.
|
|
||||||
|
|
||||||
.. note:: The package corresponding to the dependency named by ``dependency_name`` is included
|
|
||||||
in the list of returned packages.
|
|
||||||
"""
|
|
||||||
packages: Dict[str, PoetryPackage] = {
|
|
||||||
package.name: package
|
|
||||||
for package in poetry.locker.locked_repository(True).packages
|
|
||||||
}
|
|
||||||
|
|
||||||
try:
|
|
||||||
top_level = packages[dependency_name]
|
|
||||||
|
|
||||||
def find_deps_of_deps(name: str) -> List[PoetryPackage]:
|
|
||||||
if name in PoetryProvider.UNSAFE_PACKAGES:
|
|
||||||
reporter.warning(
|
|
||||||
f"{_REPORTER_PREFIX} installing '{name}' using Poetry is not supported; skipping"
|
|
||||||
)
|
|
||||||
return []
|
|
||||||
transients = [packages[name]]
|
|
||||||
for dep in packages[name].requires:
|
|
||||||
transients += find_deps_of_deps(dep.name)
|
|
||||||
return transients
|
|
||||||
|
|
||||||
return set(find_deps_of_deps(top_level.name))
|
|
||||||
|
|
||||||
except KeyError:
|
|
||||||
if any(
|
|
||||||
delimiter in dependency_name for delimiter in _PEP508_VERSION_DELIMITERS
|
|
||||||
):
|
|
||||||
raise LockedDepVersionConflictError(
|
|
||||||
f"Locked dependency '{dependency_name}' cannot include version specifier"
|
|
||||||
) from None
|
|
||||||
raise LockedDepNotFoundError(
|
|
||||||
f"No version of locked dependency '{dependency_name}' found in the project lockfile"
|
|
||||||
) from None
|
|
||||||
|
|
||||||
|
|
||||||
def _install_env_dependencies(venv: ToxVirtualEnv, poetry: Poetry):
|
|
||||||
env_deps = _sort_env_deps(venv)
|
|
||||||
|
|
||||||
dependencies: List[PoetryPackage] = []
|
|
||||||
for dep in env_deps.locked_deps:
|
|
||||||
try:
|
|
||||||
dependencies += _find_transients(poetry, dep.name.lower())
|
|
||||||
except ToxPoetryInstallerException as err:
|
|
||||||
venv.status = "lockfile installation failed"
|
|
||||||
reporter.error(f"{_REPORTER_PREFIX} {err}")
|
|
||||||
raise err
|
|
||||||
|
|
||||||
reporter.verbosity1(
|
|
||||||
f"{_REPORTER_PREFIX} identified {len(dependencies)} actual dependencies from {len(venv.envconfig.deps)} specified env dependencies"
|
|
||||||
)
|
|
||||||
|
|
||||||
reporter.verbosity1(
|
|
||||||
f"{_REPORTER_PREFIX} updating env config with {len(env_deps.unlocked_deps)} unlocked env dependencies for installation using the default backend"
|
|
||||||
)
|
|
||||||
venv.envconfig.deps = env_deps.unlocked_deps
|
|
||||||
|
|
||||||
reporter.verbosity0(
|
|
||||||
f"{_REPORTER_PREFIX} ({venv.name}) installing {len(dependencies)} env dependencies from lockfile"
|
|
||||||
)
|
|
||||||
_install_to_venv(poetry, venv, dependencies)
|
|
||||||
|
|
||||||
|
|
||||||
def _install_package_dependencies(venv: ToxVirtualEnv, poetry: Poetry):
|
|
||||||
reporter.verbosity1(
|
|
||||||
f"{_REPORTER_PREFIX} performing installation of project dependencies"
|
|
||||||
)
|
|
||||||
|
|
||||||
primary_dependencies = poetry.locker.locked_repository(False).packages
|
|
||||||
|
|
||||||
reporter.verbosity1(
|
|
||||||
f"{_REPORTER_PREFIX} identified {len(primary_dependencies)} dependencies of project '{poetry.package.name}'"
|
|
||||||
)
|
|
||||||
|
|
||||||
reporter.verbosity0(
|
|
||||||
f"{_REPORTER_PREFIX} ({venv.name}) installing {len(primary_dependencies)} project dependencies from lockfile"
|
|
||||||
)
|
|
||||||
_install_to_venv(poetry, venv, primary_dependencies)
|
|
||||||
|
|
||||||
|
|
||||||
@hookimpl
|
|
||||||
def tox_addoption(parser: ToxParser):
|
|
||||||
"""Add required configuration options to the tox INI file
|
|
||||||
|
|
||||||
Adds the ``require_locked_deps`` configuration option to the venv to check whether all
|
|
||||||
dependencies should be treated as locked or not.
|
|
||||||
"""
|
|
||||||
|
|
||||||
parser.add_testenv_attribute(
|
|
||||||
name="require_locked_deps",
|
|
||||||
type="bool",
|
|
||||||
default=False,
|
|
||||||
help="Require all dependencies in the environment be installed using the Poetry lockfile",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@hookimpl
|
|
||||||
def tox_testenv_install_deps(venv: ToxVirtualEnv, action: ToxAction):
|
|
||||||
"""Install the dependencies for the current environment
|
|
||||||
|
|
||||||
Loads the local Poetry environment and the corresponding lockfile then pulls the dependencies
|
|
||||||
specified by the Tox environment. Finally these dependencies are installed into the Tox
|
|
||||||
environment using the Poetry ``PipInstaller`` backend.
|
|
||||||
|
|
||||||
:param venv: Tox virtual environment object with configuration for the local Tox environment.
|
|
||||||
:param action: Tox action object
|
|
||||||
"""
|
|
||||||
|
|
||||||
if action.name == venv.envconfig.config.isolated_build_env:
|
|
||||||
# Skip running the plugin for the packaging environment. PEP-517 front ends can handle
|
|
||||||
# that better than we can, so let them do their thing. More to the point: if you're having
|
|
||||||
# problems in the packaging env that this plugin would solve, god help you.
|
|
||||||
reporter.verbosity1(
|
|
||||||
f"{_REPORTER_PREFIX} skipping isolated build env '{action.name}'"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
try:
|
|
||||||
poetry = PoetryFactory().create_poetry(venv.envconfig.config.toxinidir)
|
|
||||||
except RuntimeError:
|
|
||||||
# Support running the plugin when the current tox project does not use Poetry for its
|
|
||||||
# environment/dependency management.
|
|
||||||
#
|
|
||||||
# ``RuntimeError`` is dangerous to blindly catch because it can be (and in Poetry's case,
|
|
||||||
# is) raised in many different places for different purposes.
|
|
||||||
reporter.verbosity1(
|
|
||||||
f"{_REPORTER_PREFIX} project does not use Poetry for env management, skipping installation of locked dependencies"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
reporter.verbosity1(
|
|
||||||
f"{_REPORTER_PREFIX} loaded project pyproject.toml from {poetry.file}"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Handle the installation of any locked env dependencies from the lockfile
|
|
||||||
_install_env_dependencies(venv, poetry)
|
|
||||||
|
|
||||||
# Handle the installation of the package dependencies from the lockfile if the package is
|
|
||||||
# being installed to this venv; otherwise skip installing the package dependencies
|
|
||||||
if not venv.envconfig.skip_install and not venv.envconfig.config.skipsdist:
|
|
||||||
_install_package_dependencies(venv, poetry)
|
|
||||||
else:
|
|
||||||
if venv.envconfig.skip_install:
|
|
||||||
reporter.verbosity1(
|
|
||||||
f"{_REPORTER_PREFIX} env specifies 'skip_install = true', skipping installation of project package"
|
|
||||||
)
|
|
||||||
elif venv.envconfig.config.skipsdist:
|
|
||||||
reporter.verbosity1(
|
|
||||||
f"{_REPORTER_PREFIX} config specifies 'skipsdist = true', skipping installation of project package"
|
|
||||||
)
|
|
||||||
7
tox_poetry_installer/__about__.py
Normal file
7
tox_poetry_installer/__about__.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# pylint: disable=missing-docstring
|
||||||
|
__title__ = "tox-poetry-installer"
|
||||||
|
__summary__ = "Tox plugin to install Tox environment dependencies using the Poetry backend and lockfile"
|
||||||
|
__version__ = "0.5.0"
|
||||||
|
__url__ = "https://github.com/enpaul/tox-poetry-installer/"
|
||||||
|
__license__ = "MIT"
|
||||||
|
__authors__ = ["Ethan Paul <24588726+enpaul@users.noreply.github.com>"]
|
||||||
3
tox_poetry_installer/__init__.py
Normal file
3
tox_poetry_installer/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# pylint: disable=missing-docstring
|
||||||
|
from tox_poetry_installer.hooks import tox_addoption
|
||||||
|
from tox_poetry_installer.hooks import tox_testenv_install_deps
|
||||||
19
tox_poetry_installer/constants.py
Normal file
19
tox_poetry_installer/constants.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
"""Static constants for reference
|
||||||
|
|
||||||
|
Rule of thumb: if it's an arbitrary value that will never be changed at runtime, it should go
|
||||||
|
in this module.
|
||||||
|
|
||||||
|
All constants should be type hinted.
|
||||||
|
"""
|
||||||
|
from typing import Tuple
|
||||||
|
|
||||||
|
from tox_poetry_installer import __about__
|
||||||
|
|
||||||
|
|
||||||
|
# Valid PEP508 version delimiters. These are used to test whether a given string (specifically a
|
||||||
|
# dependency name) is just a package name or also includes a version identifier.
|
||||||
|
PEP508_VERSION_DELIMITERS: Tuple[str, ...] = ("~=", "==", "!=", ">", "<")
|
||||||
|
|
||||||
|
# Prefix all reporter messages should include to indicate that they came from this module in the
|
||||||
|
# console output.
|
||||||
|
REPORTER_PREFIX = f"[{__about__.__title__}]:"
|
||||||
8
tox_poetry_installer/datatypes.py
Normal file
8
tox_poetry_installer/datatypes.py
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
"""Definitions for typehints/containers used by the plugin"""
|
||||||
|
from typing import Dict
|
||||||
|
|
||||||
|
from poetry.core.packages import Package as PoetryPackage
|
||||||
|
|
||||||
|
|
||||||
|
# Map of package names to the package object
|
||||||
|
PackageMap = Dict[str, PoetryPackage]
|
||||||
33
tox_poetry_installer/exceptions.py
Normal file
33
tox_poetry_installer/exceptions.py
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
"""Custom plugin exceptions
|
||||||
|
|
||||||
|
All exceptions should inherit from the common base exception :exc:`ToxPoetryInstallerException`.
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
ToxPoetryInstallerException
|
||||||
|
+-- LockedDepVersionConflictError
|
||||||
|
+-- LockedDepNotFoundError
|
||||||
|
+-- ExtraNotFoundError
|
||||||
|
+-- LockedDepsRequiredError
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class ToxPoetryInstallerException(Exception):
|
||||||
|
"""Error while installing locked dependencies to the test environment"""
|
||||||
|
|
||||||
|
|
||||||
|
class LockedDepVersionConflictError(ToxPoetryInstallerException):
|
||||||
|
"""Locked dependencies cannot specify an alternate version for installation"""
|
||||||
|
|
||||||
|
|
||||||
|
class LockedDepNotFoundError(ToxPoetryInstallerException):
|
||||||
|
"""Locked dependency was not found in the lockfile"""
|
||||||
|
|
||||||
|
|
||||||
|
class ExtraNotFoundError(ToxPoetryInstallerException):
|
||||||
|
"""Project package extra not defined in project's pyproject.toml"""
|
||||||
|
|
||||||
|
|
||||||
|
class LockedDepsRequiredError(ToxPoetryInstallerException):
|
||||||
|
"""Environment cannot specify unlocked dependencies when locked dependencies are required"""
|
||||||
221
tox_poetry_installer/hooks.py
Normal file
221
tox_poetry_installer/hooks.py
Normal file
@@ -0,0 +1,221 @@
|
|||||||
|
"""Main hook definition module
|
||||||
|
|
||||||
|
All implementations of tox hooks are defined here, as well as any single-use helper functions
|
||||||
|
specifically related to implementing the hooks (to keep the size/readability of the hook functions
|
||||||
|
themselves manageable).
|
||||||
|
"""
|
||||||
|
from typing import List
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from poetry.core.packages import Package as PoetryPackage
|
||||||
|
from poetry.factory import Factory as PoetryFactory
|
||||||
|
from poetry.poetry import Poetry
|
||||||
|
from tox import hookimpl
|
||||||
|
from tox import reporter
|
||||||
|
from tox.action import Action as ToxAction
|
||||||
|
from tox.config import Parser as ToxParser
|
||||||
|
from tox.venv import VirtualEnv as ToxVirtualEnv
|
||||||
|
|
||||||
|
from tox_poetry_installer import constants
|
||||||
|
from tox_poetry_installer import exceptions
|
||||||
|
from tox_poetry_installer import utilities
|
||||||
|
from tox_poetry_installer.datatypes import PackageMap
|
||||||
|
|
||||||
|
|
||||||
|
@hookimpl
|
||||||
|
def tox_addoption(parser: ToxParser):
|
||||||
|
"""Add required configuration options to the tox INI file
|
||||||
|
|
||||||
|
Adds the ``require_locked_deps`` configuration option to the venv to check whether all
|
||||||
|
dependencies should be treated as locked or not.
|
||||||
|
"""
|
||||||
|
|
||||||
|
parser.add_testenv_attribute(
|
||||||
|
name="install_dev_deps",
|
||||||
|
type="bool",
|
||||||
|
default=False,
|
||||||
|
help="Automatically install all Poetry development dependencies to the environment",
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_testenv_attribute(
|
||||||
|
name="require_locked_deps",
|
||||||
|
type="bool",
|
||||||
|
default=False,
|
||||||
|
help="Require all dependencies in the environment be installed using the Poetry lockfile",
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_testenv_attribute(
|
||||||
|
name="locked_deps",
|
||||||
|
type="line-list",
|
||||||
|
help="List of locked dependencies to install to the environment using the Poetry lockfile",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@hookimpl
|
||||||
|
def tox_testenv_install_deps(venv: ToxVirtualEnv, action: ToxAction) -> Optional[bool]:
|
||||||
|
"""Install the dependencies for the current environment
|
||||||
|
|
||||||
|
Loads the local Poetry environment and the corresponding lockfile then pulls the dependencies
|
||||||
|
specified by the Tox environment. Finally these dependencies are installed into the Tox
|
||||||
|
environment using the Poetry ``PipInstaller`` backend.
|
||||||
|
|
||||||
|
:param venv: Tox virtual environment object with configuration for the local Tox environment.
|
||||||
|
:param action: Tox action object
|
||||||
|
"""
|
||||||
|
|
||||||
|
if action.name == venv.envconfig.config.isolated_build_env:
|
||||||
|
# Skip running the plugin for the packaging environment. PEP-517 front ends can handle
|
||||||
|
# that better than we can, so let them do their thing. More to the point: if you're having
|
||||||
|
# problems in the packaging env that this plugin would solve, god help you.
|
||||||
|
reporter.verbosity1(
|
||||||
|
f"{constants.REPORTER_PREFIX} skipping isolated build env '{action.name}'"
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
poetry = PoetryFactory().create_poetry(venv.envconfig.config.toxinidir)
|
||||||
|
except RuntimeError:
|
||||||
|
# Support running the plugin when the current tox project does not use Poetry for its
|
||||||
|
# environment/dependency management.
|
||||||
|
#
|
||||||
|
# ``RuntimeError`` is dangerous to blindly catch because it can be (and in Poetry's case,
|
||||||
|
# is) raised in many different places for different purposes.
|
||||||
|
reporter.verbosity1(
|
||||||
|
f"{constants.REPORTER_PREFIX} project does not use Poetry for env management, skipping installation of locked dependencies"
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
|
||||||
|
reporter.verbosity1(
|
||||||
|
f"{constants.REPORTER_PREFIX} loaded project pyproject.toml from {poetry.file}"
|
||||||
|
)
|
||||||
|
|
||||||
|
package_map: PackageMap = {
|
||||||
|
package.name: package
|
||||||
|
for package in poetry.locker.locked_repository(True).packages
|
||||||
|
}
|
||||||
|
|
||||||
|
if venv.envconfig.require_locked_deps and venv.envconfig.deps:
|
||||||
|
raise exceptions.LockedDepsRequiredError(
|
||||||
|
f"Unlocked dependencies '{venv.envconfig.deps}' specified for environment '{venv.name}' which requires locked dependencies"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Handle the installation of any locked env dependencies from the lockfile
|
||||||
|
_install_env_dependencies(venv, poetry, package_map)
|
||||||
|
|
||||||
|
# Handle the installation of the package dependencies from the lockfile if the package is
|
||||||
|
# being installed to this venv; otherwise skip installing the package dependencies
|
||||||
|
if venv.envconfig.skip_install:
|
||||||
|
reporter.verbosity1(
|
||||||
|
f"{constants.REPORTER_PREFIX} env specifies 'skip_install = true', skipping installation of project package"
|
||||||
|
)
|
||||||
|
return venv.envconfig.require_locked_deps or None
|
||||||
|
|
||||||
|
if venv.envconfig.config.skipsdist:
|
||||||
|
reporter.verbosity1(
|
||||||
|
f"{constants.REPORTER_PREFIX} config specifies 'skipsdist = true', skipping installation of project package"
|
||||||
|
)
|
||||||
|
return venv.envconfig.require_locked_deps or None
|
||||||
|
|
||||||
|
_install_project_dependencies(venv, poetry, package_map)
|
||||||
|
|
||||||
|
return venv.envconfig.require_locked_deps or None
|
||||||
|
|
||||||
|
|
||||||
|
def _install_env_dependencies(
|
||||||
|
venv: ToxVirtualEnv, poetry: Poetry, packages: PackageMap
|
||||||
|
):
|
||||||
|
"""Install the packages for a specified testenv
|
||||||
|
|
||||||
|
Processes the tox environment config, identifies any locked environment dependencies, pulls
|
||||||
|
them from the lockfile, and installs them to the virtual environment.
|
||||||
|
|
||||||
|
:param venv: Tox virtual environment to install the packages to
|
||||||
|
:param poetry: Poetry object the packages were sourced from
|
||||||
|
:param packages: Mapping of package names to the corresponding package object
|
||||||
|
"""
|
||||||
|
|
||||||
|
dependencies: List[PoetryPackage] = []
|
||||||
|
for dep in venv.envconfig.locked_deps:
|
||||||
|
try:
|
||||||
|
dependencies += utilities.find_transients(packages, dep.lower())
|
||||||
|
except exceptions.ToxPoetryInstallerException as err:
|
||||||
|
venv.status = "lockfile installation failed"
|
||||||
|
reporter.error(f"{constants.REPORTER_PREFIX} {err}")
|
||||||
|
raise err
|
||||||
|
|
||||||
|
if venv.envconfig.install_dev_deps:
|
||||||
|
reporter.verbosity1(
|
||||||
|
f"{constants.REPORTER_PREFIX} env specifies 'install_env_deps = true', including Poetry dev dependencies"
|
||||||
|
)
|
||||||
|
|
||||||
|
dev_dependencies = [
|
||||||
|
dep
|
||||||
|
for dep in poetry.locker.locked_repository(True).packages
|
||||||
|
if dep not in poetry.locker.locked_repository(False).packages
|
||||||
|
]
|
||||||
|
|
||||||
|
reporter.verbosity1(
|
||||||
|
f"{constants.REPORTER_PREFIX} identified {len(dev_dependencies)} Poetry dev dependencies"
|
||||||
|
)
|
||||||
|
|
||||||
|
dependencies = list(set(dev_dependencies + dependencies))
|
||||||
|
|
||||||
|
reporter.verbosity1(
|
||||||
|
f"{constants.REPORTER_PREFIX} identified {len(dependencies)} total dependencies from {len(venv.envconfig.locked_deps)} locked env dependencies"
|
||||||
|
)
|
||||||
|
|
||||||
|
reporter.verbosity0(
|
||||||
|
f"{constants.REPORTER_PREFIX} ({venv.name}) installing {len(dependencies)} env dependencies from lockfile"
|
||||||
|
)
|
||||||
|
utilities.install_to_venv(poetry, venv, dependencies)
|
||||||
|
|
||||||
|
|
||||||
|
def _install_project_dependencies(
|
||||||
|
venv: ToxVirtualEnv, poetry: Poetry, packages: PackageMap
|
||||||
|
):
|
||||||
|
"""Install the dependencies of the project package
|
||||||
|
|
||||||
|
Install all primary dependencies of the project package.
|
||||||
|
|
||||||
|
:param venv: Tox virtual environment to install the packages to
|
||||||
|
:param poetry: Poetry object the packages were sourced from
|
||||||
|
:param packages: Mapping of package names to the corresponding package object
|
||||||
|
"""
|
||||||
|
reporter.verbosity1(
|
||||||
|
f"{constants.REPORTER_PREFIX} performing installation of project dependencies"
|
||||||
|
)
|
||||||
|
|
||||||
|
base_dependencies: List[PoetryPackage] = [
|
||||||
|
packages[item.name]
|
||||||
|
for item in poetry.package.requires
|
||||||
|
if not item.is_optional()
|
||||||
|
]
|
||||||
|
|
||||||
|
extra_dependencies: List[PoetryPackage] = []
|
||||||
|
for extra in venv.envconfig.extras:
|
||||||
|
try:
|
||||||
|
extra_dependencies += [
|
||||||
|
packages[item.name] for item in poetry.package.extras[extra]
|
||||||
|
]
|
||||||
|
except KeyError:
|
||||||
|
raise exceptions.ExtraNotFoundError(
|
||||||
|
f"Environment '{venv.name}' specifies project extra '{extra}' which was not found in the lockfile"
|
||||||
|
) from None
|
||||||
|
|
||||||
|
dependencies: List[PoetryPackage] = []
|
||||||
|
for dep in base_dependencies + extra_dependencies:
|
||||||
|
try:
|
||||||
|
dependencies += utilities.find_transients(packages, dep.name.lower())
|
||||||
|
except exceptions.ToxPoetryInstallerException as err:
|
||||||
|
venv.status = "lockfile installation failed"
|
||||||
|
reporter.error(f"{constants.REPORTER_PREFIX} {err}")
|
||||||
|
raise err
|
||||||
|
|
||||||
|
reporter.verbosity1(
|
||||||
|
f"{constants.REPORTER_PREFIX} identified {len(dependencies)} total dependencies from {len(poetry.package.requires)} project dependencies"
|
||||||
|
)
|
||||||
|
|
||||||
|
reporter.verbosity0(
|
||||||
|
f"{constants.REPORTER_PREFIX} ({venv.name}) installing {len(dependencies)} project dependencies from lockfile"
|
||||||
|
)
|
||||||
|
utilities.install_to_venv(poetry, venv, dependencies)
|
||||||
0
tox_poetry_installer/py.typed
Normal file
0
tox_poetry_installer/py.typed
Normal file
85
tox_poetry_installer/utilities.py
Normal file
85
tox_poetry_installer/utilities.py
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
"""Helper utility functions, usually bridging Tox and Poetry functionality"""
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Sequence
|
||||||
|
from typing import Set
|
||||||
|
|
||||||
|
from poetry.core.packages import Package as PoetryPackage
|
||||||
|
from poetry.installation.pip_installer import PipInstaller as PoetryPipInstaller
|
||||||
|
from poetry.io.null_io import NullIO as PoetryNullIO
|
||||||
|
from poetry.poetry import Poetry
|
||||||
|
from poetry.puzzle.provider import Provider as PoetryProvider
|
||||||
|
from poetry.utils.env import VirtualEnv as PoetryVirtualEnv
|
||||||
|
from tox import reporter
|
||||||
|
from tox.venv import VirtualEnv as ToxVirtualEnv
|
||||||
|
|
||||||
|
from tox_poetry_installer import constants
|
||||||
|
from tox_poetry_installer import exceptions
|
||||||
|
from tox_poetry_installer.datatypes import PackageMap
|
||||||
|
|
||||||
|
|
||||||
|
def install_to_venv(
|
||||||
|
poetry: Poetry, venv: ToxVirtualEnv, packages: Sequence[PoetryPackage]
|
||||||
|
):
|
||||||
|
"""Install a bunch of packages to a virtualenv
|
||||||
|
|
||||||
|
:param poetry: Poetry object the packages were sourced from
|
||||||
|
:param venv: Tox virtual environment to install the packages to
|
||||||
|
:param packages: List of packages to install to the virtual environment
|
||||||
|
"""
|
||||||
|
|
||||||
|
reporter.verbosity1(
|
||||||
|
f"{constants.REPORTER_PREFIX} Installing {len(packages)} packages to environment at {venv.envconfig.envdir}"
|
||||||
|
)
|
||||||
|
|
||||||
|
installer = PoetryPipInstaller(
|
||||||
|
env=PoetryVirtualEnv(path=Path(venv.envconfig.envdir)),
|
||||||
|
io=PoetryNullIO(),
|
||||||
|
pool=poetry.pool,
|
||||||
|
)
|
||||||
|
|
||||||
|
for dependency in packages:
|
||||||
|
reporter.verbosity1(f"{constants.REPORTER_PREFIX} installing {dependency}")
|
||||||
|
installer.install(dependency)
|
||||||
|
|
||||||
|
|
||||||
|
def find_transients(packages: PackageMap, dependency_name: str) -> Set[PoetryPackage]:
|
||||||
|
"""Using a poetry object identify all dependencies of a specific dependency
|
||||||
|
|
||||||
|
:param poetry: Populated poetry object which can be used to build a populated locked
|
||||||
|
repository object.
|
||||||
|
:param dependency_name: Bare name (without version) of the dependency to fetch the transient
|
||||||
|
dependencies of.
|
||||||
|
:returns: List of packages that need to be installed for the requested dependency.
|
||||||
|
|
||||||
|
.. note:: The package corresponding to the dependency named by ``dependency_name`` is included
|
||||||
|
in the list of returned packages.
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
|
||||||
|
def find_deps_of_deps(name: str, transients: PackageMap):
|
||||||
|
if name in PoetryProvider.UNSAFE_PACKAGES:
|
||||||
|
reporter.warning(
|
||||||
|
f"{constants.REPORTER_PREFIX} installing package '{name}' using Poetry is not supported; skipping installation of package '{name}'"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
transients[name] = packages[name]
|
||||||
|
for dep in packages[name].requires:
|
||||||
|
if dep.name not in transients.keys():
|
||||||
|
find_deps_of_deps(dep.name, transients)
|
||||||
|
|
||||||
|
transients: PackageMap = {}
|
||||||
|
find_deps_of_deps(packages[dependency_name].name, transients)
|
||||||
|
|
||||||
|
return set(transients.values())
|
||||||
|
except KeyError:
|
||||||
|
if any(
|
||||||
|
delimiter in dependency_name
|
||||||
|
for delimiter in constants.PEP508_VERSION_DELIMITERS
|
||||||
|
):
|
||||||
|
raise exceptions.LockedDepVersionConflictError(
|
||||||
|
f"Locked dependency '{dependency_name}' cannot include version specifier"
|
||||||
|
) from None
|
||||||
|
raise exceptions.LockedDepNotFoundError(
|
||||||
|
f"No version of locked dependency '{dependency_name}' found in the project lockfile"
|
||||||
|
) from None
|
||||||
Reference in New Issue
Block a user