Commit 0768be1d by Sébastien Eustace Committed by GitHub

Merge pull request #4260 from python-poetry/dependency-groups

Dependency groups
parents 52788238 5c5af424
......@@ -48,8 +48,8 @@ authors = ["Sébastien Eustace <sebastien@eustace.io>"]
[tool.poetry.dependencies]
python = "*"
[tool.poetry.dev-dependencies]
pytest = "^3.4"
[tool.poetry.group.dev.dependencies]
pytest = "^6.0"
```
### Initialising a pre-existing project
......@@ -68,7 +68,7 @@ If you want to add dependencies to your project, you can specify them in the `to
```toml
[tool.poetry.dependencies]
pendulum = "^1.4"
pendulum = "^2.1"
```
As you can see, it takes a mapping of **package names** and **version constraints**.
......@@ -82,7 +82,7 @@ Also, instead of modifying the `pyproject.toml` file by hand, you can use the `a
$ poetry add pendulum
```
It will automatically find a suitable version constraint **and install** the package and subdependencies.
It will automatically find a suitable version constraint **and install** the package and sub-dependencies.
## Using your virtual environment
......@@ -137,8 +137,8 @@ To deactivate this virtual environment simply use `deactivate`.
### Version constraints
In our example, we are requesting the `pendulum` package with the version constraint `^1.4`.
This means any version greater or equal to 1.4.0 and less than 2.0.0 (`>=1.4.0 <2.0.0`).
In our example, we are requesting the `pendulum` package with the version constraint `^2.1`.
This means any version greater or equal to 2.1.0 and less than 3.0.0 (`>=2.1.0 <3.0.0`).
Please read [Dependency specification]({{< relref "dependency-specification" >}} "Dependency specification documentation") for more in-depth information on versions,
how versions relate to each other, and on the different ways you can specify dependencies.
......@@ -159,7 +159,7 @@ for the version constraint you have specified.
## Installing dependencies
To install the defined dependencies for your project, just run the `install` command.
To install the defined dependencies for your project, just run the [`install`]({{< relref "cli#install" >}}) command.
```bash
poetry install
......@@ -172,7 +172,7 @@ When you run this command, one of two things may happen:
If you have never run the command before and there is also no `poetry.lock` file present,
Poetry simply resolves all dependencies listed in your `pyproject.toml` file and downloads the latest version of their files.
When Poetry has finished installing, it writes all of the packages and the exact versions of them that it downloaded to the `poetry.lock` file,
When Poetry has finished installing, it writes all the packages and their exact versions that it downloaded to the `poetry.lock` file,
locking the project to those specific versions.
You should commit the `poetry.lock` file to your project repo so that all people working on the project are locked to the same versions of dependencies (more below).
......@@ -187,7 +187,7 @@ Either way, running `install` when a `poetry.lock` file is present resolves and
but Poetry uses the exact versions listed in `poetry.lock` to ensure that the package versions are consistent for everyone working on your project.
As a result you will have all dependencies requested by your `pyproject.toml` file,
but they may not all be at the very latest available versions
(some of the dependencies listed in the `poetry.lock` file may have released newer versions since the file was created).
(some dependencies listed in the `poetry.lock` file may have released newer versions since the file was created).
This is by design, it ensures that your project does not break because of unexpected changes in dependencies.
### Commit your `poetry.lock` file to version control
......
......@@ -133,20 +133,36 @@ This ensures that everyone using the library will get the same versions of the d
If there is no `poetry.lock` file, Poetry will create one after dependency resolution.
You can specify to the command that you do not want the development dependencies installed by passing
the `--no-dev` option.
If you want to exclude one or more dependency group for the installation, you can use
the `--without` option.
```bash
poetry install --no-dev
poetry install --without test,docs
```
Conversely, you can specify to the command that you only want to install the development dependencies
by passing the `--dev-only` option. Note that `--no-dev` takes priority if both options are passed.
{{% note %}}
The `--no-dev` option is now deprecated. You should use the `--without dev` notation instead.
{{% /note %}}
You can also select optional dependency groups with the `--with` option.
```bash
poetry install --dev-only
poetry install --with test,docs
```
It's also possible to only install specific dependency groups by using the `only` option.
```bash
poetry install --only test,docs
```
{{% note %}}
The `--dev-only` option is now deprecated. You should use the `--only dev` notation instead.
{{% /note %}}
See [Dependency groups]({{< relref "managing-dependencies#dependency-groups" >}}) for more information
about dependency groups.
If you want to remove old dependencies no longer present in the lock file, use the
`--remove-untracked` option.
......@@ -179,13 +195,17 @@ If you want to skip this installation, use the `--no-root` option.
poetry install --no-root
```
Installation of your project's package is also skipped when the `--dev-only`
option is passed.
Installation of your project's package is also skipped when the `--only`
option is used.
### Options
* `--no-dev`: Do not install dev dependencies.
* `--dev-only`: Only install dev dependencies.
* `--without`: The dependency groups to ignore for installation.
* `--with`: The optional dependency groups to include for installation.
* `--only`: The only dependency groups to install.
* `--default`: Only install the default dependencies.
* `--no-dev`: Do not install dev dependencies. (**Deprecated**)
* `--dev-only`: Only install dev dependencies. (**Deprecated**)
* `--no-root`: Do not install the root package (your project).
* `--dry-run`: Output the operations but do not execute anything (implicitly enables --verbose).
* `--remove-untracked`: Remove dependencies not presented in the lock file
......@@ -311,9 +331,19 @@ poetry add "requests[security,socks]~=2.22.0"
poetry add "git+https://github.com/pallets/flask.git@1.1.1[dotenv,dev]"
```
If you want to add a package to a specific group of dependencies, you can use the `--group (-G)` option:
```bash
poetry add mkdocs --group docs
```
See [Dependency groups]({{< relref "managing-dependencies#dependency-groups" >}}) for more information
about dependency groups.
### Options
* `--dev (-D)`: Add package as development dependency.
* `--group (-D)`: The group to add the dependency to.
* `--dev (-D)`: Add package as development dependency. (**Deprecated**)
* `--editable (-e)`: Add vcs/path dependencies as editable.
* `--extras (-E)`: Extras to activate for the dependency. (multiple values allowed)
* `--optional`: Add as an optional dependency.
......@@ -334,9 +364,19 @@ list of installed packages.
poetry remove pendulum
```
If you want to remove a package from a specific group of dependencies, you can use the `--group (-G)` option:
```bash
poetry remove mkdocs --group docs
```
See [Dependency groups]({{< relref "managing-dependencies#dependency-groups" >}}) for more information
about dependency groups.
### Options
* `--dev (-D)`: Removes a package from the development dependencies.
* `--group (-D)`: The group to remove the dependency from.
* `--dev (-D)`: Removes a package from the development dependencies. (**Deprecated**)
* `--dry-run` : Outputs the operations but will not execute anything (implicitly enables --verbose).
......@@ -365,6 +405,10 @@ dependencies:
### Options
* `--without`: Do not show the information of the specified groups' dependencies.
* `--with`: Show the information of the specified optional groups' dependencies as well.
* `--only`: Only show the information of dependencies belonging to the specified groups.
* `--default`: Only show the information of the default dependencies.
* `--no-dev`: Do not list the dev dependencies.
* `--tree`: List the dependencies as a tree.
* `--latest (-l)`: Show the latest version.
......
......@@ -210,21 +210,21 @@ you can shift from using "inline table" syntax, to the "standard table" syntax.
An example where this might be useful is the following:
```toml
[tool.poetry.dev-dependencies]
[tool.poetry.group.dev.dependencies]
black = {version = "19.10b0", allow-prereleases = true, python = "^3.6", markers = "platform_python_implementation == 'CPython'"}
```
As a single line, this is a lot to digest. To make this a little bit easier to
As a single line, this is a lot to digest. To make this a bit easier to
work with, you can do the following:
```toml
[tool.poetry.dev-dependencies.black]
[tool.poetry.group.dev.dependencies.black]
version = "19.10b0"
allow-prereleases = true
python = "^3.6"
markers = "platform_python_implementation == 'CPython'"
```
All of the same information is still present, and ends up providing the exact
The same information is still present, and ends up providing the exact
same specification. It's simply split into multiple, slightly more readable,
lines.
---
draft: false
layout: single
menu:
docs:
weight: 11
title: Managing dependencies
type: docs
---
# Managing dependencies
## Dependency groups
Poetry provides a way to **organize** your dependencies by **groups**. For instance, you might have
dependencies that are only needed to test your project or to build the documentation.
To declare a new dependency group, use a `tool.poetry.group.<group>` section
where `<group>` is the name of your dependency group (for instance, `test`):
```toml
[tool.poetry.group.test] # This part can be left out
[tool.poetry.group.test.dependencies]
pytest = "^6.0.0"
pytest-mock = "*"
```
{{% note %}}
All dependencies **must be compatible with each other** across groups since they will
be resolved regardless of whether they are required for installation or not (see [Installing group dependencies]({{< relref "#installing-group-dependencies" >}})).
Think of dependency groups as **labels** associated with your dependencies: they don't have any bearings
on whether their dependencies will be resolved and installed **by default**, they are simply a way to organize
the dependencies logically.
{{% /note %}}
The dependencies declared in `tool.poetry.dependencies` are part of an implicit `default` group.
```toml
[tool.poetry.dependencies] # Default dependency group
httpx = "*"
pendulum = "*"
[tool.poetry.group.test.dependencies]
pytest = "^6.0.0"
pytest-mock = "*"
```
{{% note %}}
**A note about the `dev-dependencies` section**
Any dependency declared in the `dev-dependencies` section will automatically be added to a `dev` group.
So the two following notations are equivalent:
```toml
[tool.poetry.dev-dependencies]
pytest = "^6.0.0"
pytest-mock = "*"
```
```toml
[tool.poetry.group.dev.dependencies]
pytest = "^6.0.0"
pytest-mock = "*"
```
Poetry will slowly transition away from the `dev-dependencies` notation which will soon be deprecated,
so it's advised to migrate your existing development dependencies to the new `group` notation.
{{% /note %}}
### Optional groups
A dependency group can be declared as optional. This makes sense when you have
a group of dependencies that are only required in a particular environment or for
a specific purpose.
```toml
[tool.poetry.group.docs]
optional = true
[tool.poetry.group.docs.dependencies]
mkdocs = "*"
```
Optional groups can be installed in addition to the **default** dependencies by using the `--with`
option of the [`install`]({{< relref "cli#install" >}}) command.
```bash
poetry install --with docs
```
{{% warning %}}
Optional group dependencies will **still** be resolved alongside other dependencies, so
special care should be taken to ensure they are compatible with each other.
{{% /warning %}}
### Adding a dependency to a group
The [`add`]({{< relref "cli#add" >}}) command is the preferred way to add dependencies
to a group. This is done by using the `--group (-G)` option.
```bash
poetry add pytest --group test
```
If the group does not already exist, it will be created automatically.
### Installing group dependencies
**By default**, dependencies across **all groups** will be installed when executing `poetry install`.
You can **exclude** one or more groups with the `--without` option:
```bash
poetry install --without test,docs
```
You can also opt in [optional groups]({{< relref "#optional-groups" >}}) by using the `--with` option:
```bash
poetry install --with docs
```
If you only want to install the **default**, non-grouped, dependencies, you can do so
with the `--default` option:
```bash
poetry install --default
```
Finally, in some case you might want to install **only specific groups** of dependencies
without installing the default dependencies. For that purpose, you can use
the `--only` option.
```bash
poetry install --only docs
```
### Removing dependencies from a group
The [`remove`]({{< relref "cli#remove" >}}) command supports a `--group` option
to remove packages from a specific group:
```bash
poetry remove mkdocs --group docs
```
......@@ -201,7 +201,7 @@ If no format is specified, it will default to include both `sdist` and `wheel`.
exclude = ["my_package/excluded.py"]
```
## `dependencies` and `dev-dependencies`
## dependencies and dependency groups
Poetry is configured to look for dependencies on [PyPi](https://pypi.org) by default.
Only the name and a version string are required in this case.
......@@ -211,7 +211,8 @@ Only the name and a version string are required in this case.
requests = "^2.13.0"
```
If you want to use a private repository, you can add it to your `pyproject.toml` file, like so:
If you want to use a [private repository]({{< relref "repositories#using-a-private-repository" >}}),
you can add it to your `pyproject.toml` file, like so:
```toml
[[tool.poetry.source]]
......@@ -229,6 +230,20 @@ python = "^3.6"
```
{{% /note %}}
You can organize your dependencies in [groups]({{< relref "managing-dependencies#dependency-groups" >}})
to manage them in a more granular way.
```toml
[tool.poetry.group.test.dependencies]
pytest = "*"
[tool.poetry.group.docs.dependencies]
mkdocs = "*"
```
See [Dependency groups]({{< relref "managing-dependencies#dependency-groups" >}}) for a more in-depth look
at how to manage dependency groups.
## `scripts`
This section describes the scripts or executables that will be installed when installing the package
......@@ -290,7 +305,7 @@ pip install awesome[databases]
{{% note %}}
The dependencies specified for each `extra` must already be defined as project dependencies.
Dependencies listed in the `dev-dependencies` section cannot be specified as extras.
Dependencies listed in [dependency groups]({{< relref "managing-dependencies#dependency-groups" >}}) cannot be specified as extras.
{{% /note %}}
......
......@@ -397,12 +397,19 @@ version = "1.1.0a5"
description = "Poetry PEP 517 Build Backend"
category = "main"
optional = false
python-versions = ">=3.6,<4.0"
python-versions = "^3.6"
develop = false
[package.dependencies]
dataclasses = {version = ">=0.8", markers = "python_version >= \"3.6\" and python_version < \"3.7\""}
importlib-metadata = {version = ">=1.7.0", markers = "python_version < \"3.8\""}
[package.source]
type = "git"
url = "https://github.com/python-poetry/poetry-core.git"
reference = "master"
resolved_reference = "afaa6903f654b695d9411fb548ad10630287c19f"
[[package]]
name = "pre-commit"
version = "2.12.1"
......@@ -701,7 +708,7 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pyt
[metadata]
lock-version = "1.1"
python-versions = "^3.6"
content-hash = "ac67bc6eacbb6b633f9568d69533d80456f632c7bfb9a2aa61aa9dd98e862473"
content-hash = "e38d34da68dcd63ef9f9d999f60b8e19d8da7f988031c3a6e6865d51c1fadeda"
[metadata.files]
appdirs = [
......@@ -967,10 +974,7 @@ pluggy = [
{file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"},
{file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"},
]
poetry-core = [
{file = "poetry-core-1.1.0a5.tar.gz", hash = "sha256:1b886de26026865325eae86a5d12eb154b80c0add8067c106eb706757594d85f"},
{file = "poetry_core-1.1.0a5-py3-none-any.whl", hash = "sha256:b347525c1417e9b5c6aee52967eff98c0886853a9e8ab1b9dfb2659913dd37bc"},
]
poetry-core = []
pre-commit = [
{file = "pre_commit-2.12.1-py2.py3-none-any.whl", hash = "sha256:70c5ec1f30406250b706eda35e868b87e3e4ba099af8787e3e8b4b01e84f4712"},
{file = "pre_commit-2.12.1.tar.gz", hash = "sha256:900d3c7e1bf4cf0374bb2893c24c23304952181405b4d88c9c40b72bda1bb8a9"},
......
# -*- coding: utf-8 -*-
from typing import Dict
from typing import List
......@@ -16,6 +15,13 @@ class AddCommand(InstallerCommand, InitCommand):
arguments = [argument("name", "The packages to add.", multiple=True)]
options = [
option(
"group",
"-G",
"The group to add the dependency to.",
flag=False,
default="default",
),
option("dev", "D", "Add as a development dependency."),
option("editable", "e", "Add vcs/path dependencies as editable."),
option(
......@@ -71,31 +77,55 @@ class AddCommand(InstallerCommand, InitCommand):
def handle(self) -> int:
from tomlkit import inline_table
from tomlkit import parse as parse_toml
from tomlkit import table
from poetry.core.semver.helpers import parse_constraint
from poetry.factory import Factory
packages = self.argument("name")
is_dev = self.option("dev")
if self.option("dev"):
self.line(
"<warning>The --dev option is deprecated, "
"use the `--group dev` notation instead.</warning>"
)
self.line("")
group = "dev"
else:
group = self.option("group")
if self.option("extras") and len(packages) > 1:
raise ValueError(
"You can only specify one package " "when using the --extras option"
"You can only specify one package when using the --extras option"
)
section = "dependencies"
if is_dev:
section = "dev-dependencies"
original_content = self.poetry.file.read()
content = self.poetry.file.read()
poetry_content = content["tool"]["poetry"]
if section not in poetry_content:
poetry_content[section] = {}
if group == "default":
if "dependencies" not in poetry_content:
poetry_content["dependencies"] = table()
existing_packages = self.get_existing_packages_from_input(
packages, poetry_content, section
)
section = poetry_content["dependencies"]
else:
if "group" not in poetry_content:
group_table = table()
group_table._is_super_table = True
poetry_content.value._insert_after("dependencies", "group", group_table)
groups = poetry_content["group"]
if group not in groups:
group_table = parse_toml(
f"[tool.poetry.group.{group}.dependencies]\n\n"
)["tool"]["poetry"]["group"][group]
poetry_content["group"][group] = group_table
if "dependencies" not in poetry_content["group"][group]:
poetry_content["group"][group]["dependencies"] = table()
section = poetry_content["group"][group]["dependencies"]
existing_packages = self.get_existing_packages_from_input(packages, section)
if existing_packages:
self.notify_about_existing_packages(existing_packages)
......@@ -165,18 +195,25 @@ class AddCommand(InstallerCommand, InitCommand):
if len(constraint) == 1 and "version" in constraint:
constraint = constraint["version"]
poetry_content[section][_constraint["name"]] = constraint
section[_constraint["name"]] = constraint
self.poetry.package.add_dependency(
Factory.create_dependency(
_constraint["name"],
constraint,
groups=[group],
root_dir=self.poetry.file.parent,
)
)
try:
# Write new content
self.poetry.file.write(content)
# Refresh the locker
self.poetry.set_locker(
self.poetry.locker.__class__(self.poetry.locker.lock.path, poetry_content)
)
self._installer.set_locker(self.poetry.locker)
# Cosmetic new line
self.line("")
# Update packages
self.reset_poetry()
self._installer.set_package(self.poetry.package)
self._installer.dry_run(self.option("dry-run"))
self._installer.verbose(self._io.is_verbose())
......@@ -187,31 +224,19 @@ class AddCommand(InstallerCommand, InitCommand):
self._installer.whitelist([r["name"] for r in requirements])
status = self._installer.run()
except BaseException:
# Using BaseException here as some exceptions, eg: KeyboardInterrupt, do not inherit from Exception
self.poetry.file.write(original_content)
raise
if status != 0 or self.option("dry-run"):
# Revert changes
if not self.option("dry-run"):
self.line_error(
"\n"
"<error>Failed to add packages, reverting the pyproject.toml file "
"to its original content.</error>"
)
self.poetry.file.write(original_content)
if status == 0 and not self.option("dry-run"):
self.poetry.file.write(content)
return status
def get_existing_packages_from_input(
self, packages: List[str], poetry_content: Dict, target_section: str
self, packages: List[str], section: Dict
) -> List[str]:
existing_packages = []
for name in packages:
for key in poetry_content[target_section]:
for key in section:
if key.lower() == name.lower():
existing_packages.append(name)
......
......@@ -99,7 +99,7 @@ class DebugResolveCommand(InitCommand):
packages = [op.package for op in ops]
repo = Repository(packages)
requires = package.requires + package.dev_requires
requires = package.all_requires
for pkg in repo.packages:
for require in requires:
if pkg.name == require.name:
......
......@@ -9,8 +9,38 @@ class InstallCommand(InstallerCommand):
description = "Installs the project dependencies."
options = [
option("no-dev", None, "Do not install the development dependencies."),
option("dev-only", None, "Only install the development dependencies."),
option(
"without",
None,
"The dependency groups to ignore for installation.",
flag=False,
multiple=True,
),
option(
"with",
None,
"The optional dependency groups to include for installation.",
flag=False,
multiple=True,
),
option("default", None, "Only install the default dependencies."),
option(
"only",
None,
"The only dependency groups to install.",
flag=False,
multiple=True,
),
option(
"no-dev",
None,
"Do not install the development dependencies. (<warning>Deprecated</warning>)",
),
option(
"dev-only",
None,
"Only install the development dependencies. (<warning>Deprecated</warning>)",
),
option(
"no-root", None, "Do not install the root package (the current project)."
),
......@@ -66,8 +96,51 @@ dependencies and not including the current project, run the command with the
extras.append(extra)
self._installer.extras(extras)
self._installer.dev_mode(not self.option("no-dev"))
self._installer.dev_only(self.option("dev-only"))
excluded_groups = []
included_groups = []
only_groups = []
if self.option("no-dev"):
self.line(
"<warning>The `<fg=yellow;options=bold>--no-dev</>` option is deprecated,"
"use the `<fg=yellow;options=bold>--without dev</>` notation instead.</warning>"
)
excluded_groups.append("dev")
elif self.option("dev-only"):
self.line(
"<warning>The `<fg=yellow;options=bold>--dev-only</>` option is deprecated,"
"use the `<fg=yellow;options=bold>--only dev</>` notation instead.</warning>"
)
only_groups.append("dev")
excluded_groups.extend(
[
group.strip()
for groups in self.option("without")
for group in groups.split(",")
]
)
included_groups.extend(
[
group.strip()
for groups in self.option("with")
for group in groups.split(",")
]
)
only_groups.extend(
[
group.strip()
for groups in self.option("only")
for group in groups.split(",")
]
)
if self.option("default"):
only_groups.append("default")
self._installer.only_groups(only_groups)
self._installer.without_groups(excluded_groups)
self._installer.with_groups(included_groups)
self._installer.dry_run(self.option("dry-run"))
self._installer.remove_untracked(self.option("remove-untracked"))
self._installer.verbose(self._io.is_verbose())
......@@ -77,7 +150,7 @@ dependencies and not including the current project, run the command with the
if return_code != 0:
return return_code
if self.option("no-root") or self.option("dev-only"):
if self.option("no-root") or self.option("only"):
return 0
try:
......
from typing import Any
from typing import Dict
from typing import List
from cleo.helpers import argument
from cleo.helpers import option
from ...utils.helpers import canonicalize_name
from .installer_command import InstallerCommand
......@@ -12,6 +15,7 @@ class RemoveCommand(InstallerCommand):
arguments = [argument("packages", "The packages to remove.", multiple=True)]
options = [
option("group", "G", "The group to remove the dependency from.", flag=False),
option("dev", "D", "Remove a package from the development dependencies."),
option(
"dry-run",
......@@ -30,39 +34,70 @@ list of installed packages
def handle(self) -> int:
packages = self.argument("packages")
is_dev = self.option("dev")
if self.option("dev"):
self.line(
"<warning>The --dev option is deprecated, "
"use the `--group dev` notation instead.</warning>"
)
self.line("")
group = "dev"
else:
group = self.option("group")
content = self.poetry.file.read()
poetry_content = content["tool"]["poetry"]
section = "dependencies"
if is_dev:
section = "dev-dependencies"
# Deleting entries
requirements = {}
for name in packages:
found = False
for key in poetry_content[section]:
if key.lower() == name.lower():
found = True
requirements[key] = poetry_content[section][key]
break
if not found:
raise ValueError("Package {} not found".format(name))
for key in requirements:
del poetry_content[section][key]
dependencies = (
self.poetry.package.requires
if section == "dependencies"
else self.poetry.package.dev_requires
if group is None:
removed = []
group_sections = []
for group_name, group_section in poetry_content.get("group", {}).items():
group_sections.append(
(group_name, group_section.get("dependencies", {}))
)
for group_name, section in [
("default", poetry_content["dependencies"])
] + group_sections:
removed += self._remove_packages(packages, section, group_name)
if group_name != "default":
if not section:
del poetry_content["group"][group_name]
else:
poetry_content["group"][group_name]["dependencies"] = section
elif group == "dev" and "dev-dependencies" in poetry_content:
# We need to account for the old `dev-dependencies` section
removed = self._remove_packages(
packages, poetry_content["dev-dependencies"], "dev"
)
if not poetry_content["dev-dependencies"]:
del poetry_content["dev-dependencies"]
else:
removed = self._remove_packages(
packages, poetry_content["group"][group].get("dependencies", {}), group
)
for i, dependency in enumerate(reversed(dependencies)):
if dependency.name == canonicalize_name(key):
del dependencies[-i]
if not poetry_content["group"][group]:
del poetry_content["group"][group]
if "group" in poetry_content and not poetry_content["group"]:
del poetry_content["group"]
removed = set(removed)
not_found = set(packages).difference(removed)
if not_found:
raise ValueError(
"The following packages were not found: {}".format(
", ".join(sorted(not_found))
)
)
# Refresh the locker
self.poetry.set_locker(
self.poetry.locker.__class__(self.poetry.locker.lock.path, poetry_content)
)
self._installer.set_locker(self.poetry.locker)
# Update packages
self._installer.use_executor(
......@@ -72,7 +107,7 @@ list of installed packages
self._installer.dry_run(self.option("dry-run"))
self._installer.verbose(self._io.is_verbose())
self._installer.update(True)
self._installer.whitelist(requirements)
self._installer.whitelist(removed)
status = self._installer.run()
......@@ -80,3 +115,19 @@ list of installed packages
self.poetry.file.write(content)
return status
def _remove_packages(
self, packages: List[str], section: Dict[str, Any], group_name: str
) -> List[str]:
removed = []
group = self.poetry.package.dependency_group(group_name)
section_keys = list(section.keys())
for package in packages:
for existing_package in section_keys:
if existing_package.lower() == package.lower():
del section[existing_package]
removed.append(package)
group.remove_dependency(package)
return removed
......@@ -14,6 +14,7 @@ if TYPE_CHECKING:
from poetry.core.packages.dependency import Dependency
from poetry.core.packages.package import Package
from poetry.packages.project_package import ProjectPackage
from poetry.repositories import Repository
from poetry.repositories.installed_repository import InstalledRepository
......@@ -25,7 +26,35 @@ class ShowCommand(EnvCommand):
arguments = [argument("package", "The package to inspect", optional=True)]
options = [
option("no-dev", None, "Do not list the development dependencies."),
option(
"without",
None,
"Do not show the information of the specified groups' dependencies.",
flag=False,
multiple=True,
),
option(
"with",
None,
"Show the information of the specified optional groups' dependencies as well.",
flag=False,
multiple=True,
),
option(
"default", None, "Only show the information of the default dependencies."
),
option(
"only",
None,
"Only show the information of dependencies belonging to the specified groups.",
flag=False,
multiple=True,
),
option(
"no-dev",
None,
"Do not list the development dependencies. (<warning>Deprecated</warning>)",
),
option("tree", "t", "List the dependencies as a tree."),
option("latest", "l", "Show the latest version."),
option(
......@@ -63,19 +92,58 @@ lists all packages available."""
if self.option("outdated"):
self._io.input.set_option("latest", True)
include_dev = not self.option("no-dev")
excluded_groups = []
included_groups = []
only_groups = []
if self.option("no-dev"):
self.line(
"<warning>The `<fg=yellow;options=bold>--no-dev</>` option is deprecated,"
"use the `<fg=yellow;options=bold>--without dev</>` notation instead.</warning>"
)
excluded_groups.append("dev")
excluded_groups.extend(
[
group.strip()
for groups in self.option("without")
for group in groups.split(",")
]
)
included_groups.extend(
[
group.strip()
for groups in self.option("with")
for group in groups.split(",")
]
)
only_groups.extend(
[
group.strip()
for groups in self.option("only")
for group in groups.split(",")
]
)
if self.option("default"):
only_groups.append("default")
locked_repo = self.poetry.locker.locked_repository(True)
if only_groups:
root = self.poetry.package.with_dependency_groups(only_groups, only=True)
else:
root = self.poetry.package.with_dependency_groups(
included_groups
).without_dependency_groups(excluded_groups)
# Show tree view if requested
if self.option("tree") and not package:
requires = self.poetry.package.requires
if include_dev:
requires += self.poetry.package.dev_requires
requires = root.all_requires
packages = locked_repo.packages
for package in packages:
for pkg in packages:
for require in requires:
if package.name == require.name:
self.display_package_tree(self._io, package, locked_repo)
if pkg.name == require.name:
self.display_package_tree(self._io, pkg, locked_repo)
break
return 0
......@@ -85,7 +153,7 @@ lists all packages available."""
pool = Pool(ignore_repository_names=True)
pool.add_repository(locked_repo)
solver = Solver(
self.poetry.package,
root,
pool=pool,
installed=Repository(),
locked=locked_repo,
......@@ -97,11 +165,6 @@ lists all packages available."""
required_locked_packages = set([op.package for op in ops if not op.skipped])
if self.option("no-dev"):
required_locked_packages = [
p for p in locked_packages if p.category == "main"
]
if package:
pkg = None
for locked in locked_packages:
......@@ -160,7 +223,7 @@ lists all packages available."""
current_length += 4
if show_latest:
latest = self.find_latest_package(locked, include_dev)
latest = self.find_latest_package(locked, root)
if not latest:
latest = locked
......@@ -377,7 +440,7 @@ lists all packages available."""
io.error_output.formatter.set_style(color, style)
def find_latest_package(
self, package: "Package", include_dev: bool
self, package: "Package", root: "ProjectPackage"
) -> Union["Package", bool]:
from cleo.io.null_io import NullIO
......@@ -386,13 +449,11 @@ lists all packages available."""
# find the latest version allowed in this pool
if package.source_type in ("git", "file", "directory"):
requires = self.poetry.package.requires
if include_dev:
requires = requires + self.poetry.package.dev_requires
requires = root.all_requires
for dep in requires:
if dep.name == package.name:
provider = Provider(self.poetry.package, self.poetry.pool, NullIO())
provider = Provider(root, self.poetry.pool, NullIO())
if dep.is_vcs():
return provider.search_for_vcs(dep)[0]
......
......@@ -37,7 +37,9 @@ class UpdateCommand(InstallerCommand):
if packages:
self._installer.whitelist({name: "*" for name in packages})
self._installer.dev_mode(not self.option("no-dev"))
if self.option("no-dev"):
self._installer.with_groups(["dev"])
self._installer.dry_run(self.option("dry-run"))
self._installer.execute_operations(not self.option("lock"))
......
......@@ -157,7 +157,9 @@ class PackageInfo:
poetry_package = self._get_poetry_package(path=root_dir or self._source_url)
if poetry_package:
package.extras = poetry_package.extras
package.requires = poetry_package.requires
for dependency in poetry_package.requires:
package.add_dependency(dependency)
return package
seen_requirements = set()
......@@ -191,7 +193,7 @@ class PackageInfo:
req = dependency.to_pep_508(with_extras=True)
if req not in seen_requirements:
package.requires.append(dependency)
package.add_dependency(dependency)
seen_requirements.add(req)
return package
......
......@@ -54,8 +54,10 @@ class Installer:
self._update = False
self._verbose = False
self._write_lock = True
self._dev_mode = True
self._dev_only = False
self._without_groups = None
self._with_groups = None
self._only_groups = None
self._execute_operations = True
self._lock = False
......@@ -137,21 +139,20 @@ class Installer:
def is_verbose(self) -> bool:
return self._verbose
def dev_mode(self, dev_mode: bool = True) -> "Installer":
self._dev_mode = dev_mode
def without_groups(self, groups: List[str]) -> "Installer":
self._without_groups = groups
return self
def is_dev_mode(self) -> bool:
return self._dev_mode
def dev_only(self, dev_only: bool = False) -> "Installer":
self._dev_only = dev_only
def with_groups(self, groups: List[str]) -> "Installer":
self._with_groups = groups
return self
def is_dev_only(self) -> bool:
return self._dev_only
def only_groups(self, groups: List[str]) -> "Installer":
self._only_groups = groups
return self
def update(self, update: bool = True) -> "Installer":
self._update = update
......@@ -283,13 +284,20 @@ class Installer:
# If we are only in lock mode, no need to go any further
return 0
root = self._package
if not self.is_dev_mode():
root = root.clone()
del root.dev_requires[:]
elif self.is_dev_only():
root = root.clone()
del root.requires[:]
if self._without_groups or self._with_groups or self._only_groups:
if self._with_groups:
# Default dependencies and opted-in optional dependencies
root = self._package.with_dependency_groups(self._with_groups)
elif self._without_groups:
# Default dependencies without selected groups
root = self._package.without_dependency_groups(self._without_groups)
else:
# Only selected groups
root = self._package.with_dependency_groups(
self._only_groups, only=True
)
else:
root = self._package.without_optional_dependency_groups()
if self._io.is_verbose():
self._io.write_line("")
......@@ -502,9 +510,7 @@ class Installer:
for installed in installed_repo.packages:
if locked.name == installed.name:
is_installed = True
if locked.category == "dev" and not self.is_dev_mode():
ops.append(Uninstall(locked))
elif locked.optional and locked.name not in extra_packages:
if locked.optional and locked.name not in extra_packages:
# Installed but optional and not requested in extras
ops.append(Uninstall(locked))
elif locked.version != installed.version:
......@@ -553,11 +559,6 @@ class Installer:
if package.name not in extra_packages:
op.skip("Not required")
# If the package is a dev package and dev packages
# are not requested, we skip it
if package.category == "dev" and not self.is_dev_mode():
op.skip("Dev dependencies not requested")
def _get_extra_packages(self, repo: Repository) -> List[str]:
"""
Returns all package names required by extras.
......
......@@ -30,7 +30,7 @@ packages = []
[tool.poetry.dependencies]
[tool.poetry.dev-dependencies]
[tool.poetry.group.dev.dependencies]
"""
BUILD_SYSTEM_MIN_VERSION: Optional[str] = None
......@@ -143,8 +143,13 @@ class Layout:
for dep_name, dep_constraint in self._dependencies.items():
poetry_content["dependencies"][dep_name] = dep_constraint
if self._dev_dependencies:
for dep_name, dep_constraint in self._dev_dependencies.items():
poetry_content["dev-dependencies"][dep_name] = dep_constraint
poetry_content["group"]["dev"]["dependencies"][
dep_name
] = dep_constraint
else:
del poetry_content["group"]
# Add build system
build_system = table()
......
......@@ -47,7 +47,7 @@ class Locker:
_VERSION = "1.1"
_relevant_keys = ["dependencies", "dev-dependencies", "source", "extras"]
_relevant_keys = ["dependencies", "group", "source", "extras"]
def __init__(self, lock: Union[str, Path], local_config: dict) -> None:
self._lock = TOMLFile(lock)
......@@ -128,7 +128,8 @@ class Locker:
source_resolved_reference=source.get("resolved_reference"),
)
package.description = info.get("description", "")
package.category = info["category"]
package.category = info.get("category", "main")
package.groups = info.get("groups", ["default"])
package.optional = info["optional"]
if "hashes" in lock_data["metadata"]:
# Old lock so we create dummy files from the hashes
......
......@@ -21,3 +21,5 @@ class ProjectPackage(_ProjectPackage):
else:
self._version = version
self._pretty_version = pretty_version or version.text
return self
......@@ -330,7 +330,8 @@ class Provider:
for dep in package.extras[extra]:
dep.activate()
package.requires += package.extras[extra]
for extra_dep in package.extras[extra]:
package.add_dependency(extra_dep)
dependency._constraint = package.version
dependency._pretty_constraint = package.version.text
......@@ -535,10 +536,10 @@ class Provider:
# - pypiwin32 (219); sys_platform == "win32" and python_version < "3.6"
duplicates = dict()
for dep in dependencies:
if dep.name not in duplicates:
duplicates[dep.name] = []
if dep.complete_name not in duplicates:
duplicates[dep.complete_name] = []
duplicates[dep.name].append(dep)
duplicates[dep.complete_name].append(dep)
dependencies = []
for dep_name, deps in duplicates.items():
......@@ -699,7 +700,12 @@ class Provider:
clean_dependencies.append(dep)
package.requires = clean_dependencies
package = DependencyPackage(
package.dependency, package.with_dependency_groups([], only=True)
)
for dep in clean_dependencies:
package.add_dependency(dep)
return package
......
......@@ -6,6 +6,7 @@ from contextlib import contextmanager
from typing import TYPE_CHECKING
from typing import Callable
from typing import Dict
from typing import FrozenSet
from typing import List
from typing import Optional
from typing import Tuple
......@@ -254,7 +255,7 @@ class Solver:
for dep in package.requires:
if dep not in pkg.requires:
pkg.requires.append(dep)
pkg.add_dependency(dep)
return packages, depths
......@@ -300,7 +301,7 @@ class Solver:
continue
if dep not in _package.requires:
_package.requires.append(dep)
_package.add_dependency(dep)
continue
......@@ -312,7 +313,9 @@ class Solver:
class DFSNode:
def __init__(self, id: Tuple[str, str, bool], name: str, base_name: str) -> None:
def __init__(
self, id: Tuple[str, FrozenSet[str], bool], name: str, base_name: str
) -> None:
self.id = id
self.name = name
self.base_name = base_name
......@@ -423,13 +426,15 @@ class PackageNode(DFSNode):
if not previous:
self.category = "dev"
self.groups = frozenset()
self.optional = True
else:
self.category = dep.category
self.category = "main" if "default" in dep.groups else "dev"
self.groups = dep.groups
self.optional = dep.is_optional()
super().__init__(
(package.complete_name, self.category, self.optional),
(package.complete_name, self.groups, self.optional),
package.complete_name,
package.name,
)
......@@ -470,7 +475,7 @@ class PackageNode(DFSNode):
# we merge the requirements
if any(
child.package.name == pkg.name
and child.category == dependency.category
and child.groups == dependency.groups
for child in children
):
continue
......@@ -505,14 +510,20 @@ def aggregate_package_nodes(
) -> Tuple[Package, int]:
package = nodes[0].package
depth = max(node.depth for node in nodes)
groups = []
for node in nodes:
groups.extend(node.groups)
category = (
"main" if any(node.category == "main" for node in children + nodes) else "dev"
"main" if any("default" in node.groups for node in children + nodes) else "dev"
)
optional = all(node.optional for node in children + nodes)
for node in nodes:
node.depth = depth
node.category = category
node.optional = optional
package.category = category
package.optional = optional
return package, depth
......@@ -31,7 +31,7 @@ generate-setup-file = false
[tool.poetry.dependencies]
python = "^3.6"
poetry-core = "~1.1.0a5"
poetry-core = { git = "https://github.com/python-poetry/poetry-core.git", branch = "master" }
cleo = "^1.0.0a1"
crashtest = "^0.3.0"
requests = "^2.18"
......
......@@ -116,7 +116,7 @@ def test_add_constraint_with_extras(app, repo, tester):
cachy1 = get_package("cachy", "0.1.0")
cachy1.extras = {"msgpack": [get_dependency("msgpack-python")]}
msgpack_dep = get_dependency("msgpack-python", ">=0.5 <0.6", optional=True)
cachy1.requires = [msgpack_dep]
cachy1.add_dependency(msgpack_dep)
repo.add_package(get_package("cachy", "0.2.0"))
repo.add_package(cachy1)
......@@ -144,7 +144,7 @@ Package operations: 2 installs, 0 updates, 0 removals
def test_add_constraint_dependencies(app, repo, tester):
cachy2 = get_package("cachy", "0.2.0")
msgpack_dep = get_dependency("msgpack-python", ">=0.5 <0.6")
cachy2.requires = [msgpack_dep]
cachy2.add_dependency(msgpack_dep)
repo.add_package(get_package("cachy", "0.1.0"))
repo.add_package(cachy2)
......@@ -443,7 +443,7 @@ def test_add_constraint_with_extras_option(app, repo, tester):
cachy2 = get_package("cachy", "0.2.0")
cachy2.extras = {"msgpack": [get_dependency("msgpack-python")]}
msgpack_dep = get_dependency("msgpack-python", ">=0.5 <0.6", optional=True)
cachy2.requires = [msgpack_dep]
cachy2.add_dependency(msgpack_dep)
repo.add_package(get_package("cachy", "0.1.0"))
repo.add_package(cachy2)
......@@ -667,13 +667,52 @@ def test_add_constraint_not_found_with_source(app, poetry, mocker, tester):
assert "Could not find a matching version of package cachy" == str(e.value)
def test_add_to_section_that_does_no_exist_yet(app, repo, tester):
def test_add_to_section_that_does_not_exist_yet(app, repo, tester):
repo.add_package(get_package("cachy", "0.1.0"))
repo.add_package(get_package("cachy", "0.2.0"))
tester.execute("cachy --group dev")
expected = """\
Using version ^0.2.0 for cachy
Updating dependencies
Resolving dependencies...
Writing lock file
Package operations: 1 install, 0 updates, 0 removals
• Installing cachy (0.2.0)
"""
assert expected == tester.io.fetch_output()
assert 1 == tester.command.installer.executor.installations_count
content = app.poetry.file.read()["tool"]["poetry"]
assert "cachy" in content["group"]["dev"]["dependencies"]
assert content["group"]["dev"]["dependencies"]["cachy"] == "^0.2.0"
expected = """\
[tool.poetry.group.dev.dependencies]
cachy = "^0.2.0"
"""
assert expected in content.as_string()
def test_add_to_dev_section_deprecated(app, repo, tester):
repo.add_package(get_package("cachy", "0.1.0"))
repo.add_package(get_package("cachy", "0.2.0"))
tester.execute("cachy --dev")
expected = """\
The --dev option is deprecated, use the `--group dev` notation instead.
Using version ^0.2.0 for cachy
Updating dependencies
......@@ -691,8 +730,8 @@ Package operations: 1 install, 0 updates, 0 removals
content = app.poetry.file.read()["tool"]["poetry"]
assert "cachy" in content["dev-dependencies"]
assert content["dev-dependencies"]["cachy"] == "^0.2.0"
assert "cachy" in content["group"]["dev"]["dependencies"]
assert content["group"]["dev"]["dependencies"]["cachy"] == "^0.2.0"
def test_add_should_not_select_prereleases(app, repo, tester):
......@@ -919,7 +958,7 @@ def test_add_constraint_with_extras_old_installer(app, repo, installer, old_test
cachy1 = get_package("cachy", "0.1.0")
cachy1.extras = {"msgpack": [get_dependency("msgpack-python")]}
msgpack_dep = get_dependency("msgpack-python", ">=0.5 <0.6", optional=True)
cachy1.requires = [msgpack_dep]
cachy1.add_dependency(msgpack_dep)
repo.add_package(get_package("cachy", "0.2.0"))
repo.add_package(cachy1)
......@@ -948,7 +987,7 @@ Package operations: 2 installs, 0 updates, 0 removals
def test_add_constraint_dependencies_old_installer(app, repo, installer, old_tester):
cachy2 = get_package("cachy", "0.2.0")
msgpack_dep = get_dependency("msgpack-python", ">=0.5 <0.6")
cachy2.requires = [msgpack_dep]
cachy2.add_dependency(msgpack_dep)
repo.add_package(get_package("cachy", "0.1.0"))
repo.add_package(cachy2)
......@@ -1245,7 +1284,7 @@ def test_add_constraint_with_extras_option_old_installer(
cachy2 = get_package("cachy", "0.2.0")
cachy2.extras = {"msgpack": [get_dependency("msgpack-python")]}
msgpack_dep = get_dependency("msgpack-python", ">=0.5 <0.6", optional=True)
cachy2.requires = [msgpack_dep]
cachy2.add_dependency(msgpack_dep)
repo.add_package(get_package("cachy", "0.1.0"))
repo.add_package(cachy2)
......@@ -1487,7 +1526,7 @@ def test_add_to_section_that_does_no_exist_yet_old_installer(
repo.add_package(get_package("cachy", "0.1.0"))
repo.add_package(get_package("cachy", "0.2.0"))
old_tester.execute("cachy --dev")
old_tester.execute("cachy --group dev")
expected = """\
Using version ^0.2.0 for cachy
......@@ -1508,8 +1547,8 @@ Package operations: 1 install, 0 updates, 0 removals
content = app.poetry.file.read()["tool"]["poetry"]
assert "cachy" in content["dev-dependencies"]
assert content["dev-dependencies"]["cachy"] == "^0.2.0"
assert "cachy" in content["group"]["dev"]["dependencies"]
assert content["group"]["dev"]["dependencies"]["cachy"] == "^0.2.0"
def test_add_should_not_select_prereleases_old_installer(
......
......@@ -71,8 +71,6 @@ packages = [{include = "my_package"}]
[tool.poetry.dependencies]
python = "~2.7 || ^3.6"
[tool.poetry.dev-dependencies]
"""
......@@ -143,7 +141,7 @@ packages = [{include = "my_package"}]
python = "~2.7 || ^3.6"
pendulum = "^2.0.0"
[tool.poetry.dev-dependencies]
[tool.poetry.group.dev.dependencies]
pytest = "^3.6.0"
"""
......@@ -175,8 +173,6 @@ packages = [{{include = "my_package"}}]
[tool.poetry.dependencies]
python = "^{python}"
[tool.poetry.dev-dependencies]
""".format(
python=".".join(str(c) for c in sys.version_info[:2])
)
......@@ -220,7 +216,7 @@ packages = [{include = "my_package"}]
python = "~2.7 || ^3.6"
demo = {git = "https://github.com/demo/demo.git"}
[tool.poetry.dev-dependencies]
[tool.poetry.group.dev.dependencies]
pytest = "^3.6.0"
"""
......@@ -264,7 +260,7 @@ packages = [{include = "my_package"}]
python = "~2.7 || ^3.6"
demo = {git = "https://github.com/demo/demo.git", rev = "develop"}
[tool.poetry.dev-dependencies]
[tool.poetry.group.dev.dependencies]
pytest = "^3.6.0"
"""
......@@ -308,7 +304,7 @@ packages = [{include = "my_package"}]
python = "~2.7 || ^3.6"
demo = {git = "https://github.com/demo/pyproject-demo.git"}
[tool.poetry.dev-dependencies]
[tool.poetry.group.dev.dependencies]
pytest = "^3.6.0"
"""
......@@ -355,7 +351,7 @@ packages = [{include = "my_package"}]
python = "~2.7 || ^3.6"
demo = {path = "demo"}
[tool.poetry.dev-dependencies]
[tool.poetry.group.dev.dependencies]
pytest = "^3.6.0"
"""
assert expected in tester.io.fetch_output()
......@@ -403,7 +399,7 @@ packages = [{include = "my_package"}]
python = "~2.7 || ^3.6"
demo = {path = "pyproject-demo"}
[tool.poetry.dev-dependencies]
[tool.poetry.group.dev.dependencies]
pytest = "^3.6.0"
"""
......@@ -450,7 +446,7 @@ packages = [{include = "my_package"}]
python = "~2.7 || ^3.6"
demo = {path = "demo-0.1.0-py2.py3-none-any.whl"}
[tool.poetry.dev-dependencies]
[tool.poetry.group.dev.dependencies]
pytest = "^3.6.0"
"""
......@@ -482,8 +478,6 @@ packages = [{include = "my_package"}]
[tool.poetry.dependencies]
python = "~2.7 || ^3.6"
[tool.poetry.dev-dependencies]
"""
assert expected in tester.io.fetch_output()
......@@ -518,8 +512,6 @@ packages = [{include = "my_package"}]
[tool.poetry.dependencies]
python = "~2.7 || ^3.6"
pendulum = "^2.0.0"
[tool.poetry.dev-dependencies]
"""
assert expected in tester.io.fetch_output()
......@@ -596,7 +588,7 @@ packages = [{include = "my_package"}]
[tool.poetry.dependencies]
python = "~2.7 || ^3.6"
[tool.poetry.dev-dependencies]
[tool.poetry.group.dev.dependencies]
pytest = "^3.6.0"
"""
......@@ -638,7 +630,7 @@ packages = [{include = "my_package"}]
[tool.poetry.dependencies]
python = "~2.7 || ^3.6"
[tool.poetry.dev-dependencies]
[tool.poetry.group.dev.dependencies]
pytest = "^3.6.0"
pytest-requests = "^0.2.0"
"""
......@@ -705,8 +697,6 @@ packages = [{include = "my_package"}]
[tool.poetry.dependencies]
python = "^3.6"
foo = "^1.19.2"
[tool.poetry.dev-dependencies]
"""
assert "{}\n{}".format(existing_section, expected) in pyproject_file.read_text()
......
import pytest
@pytest.fixture
def tester(command_tester_factory):
return command_tester_factory("install")
def test_group_options_are_passed_to_the_installer(tester, mocker):
"""
Group options are passed properly to the installer.
"""
mocker.patch.object(tester.command.installer, "run", return_value=1)
tester.execute("--with foo,bar --without baz --without bim --only bam")
assert tester.command.installer._with_groups == ["foo", "bar"]
assert tester.command.installer._without_groups == ["baz", "bim"]
assert tester.command.installer._only_groups == ["bam"]
import pytest
import tomlkit
from poetry.core.packages.package import Package
from poetry.factory import Factory
@pytest.fixture()
......@@ -8,6 +10,152 @@ def tester(command_tester_factory):
return command_tester_factory("remove")
def test_remove_without_specific_group_removes_from_all_groups(
tester, app, repo, command_tester_factory, installed
):
"""
Removing without specifying a group removes packages from all groups.
"""
installed.add_package(Package("foo", "2.0.0"))
repo.add_package(Package("foo", "2.0.0"))
repo.add_package(Package("baz", "1.0.0"))
content = app.poetry.file.read()
groups_content = tomlkit.parse(
"""\
[tool.poetry.group.bar.dependencies]
foo = "^2.0.0"
baz = "^1.0.0"
"""
)
content["tool"]["poetry"]["dependencies"]["foo"] = "^2.0.0"
content["tool"]["poetry"].value._insert_after(
"dependencies", "group", groups_content["tool"]["poetry"]["group"]
)
app.poetry.file.write(content)
app.poetry.package.add_dependency(Factory.create_dependency("foo", "^2.0.0"))
app.poetry.package.add_dependency(
Factory.create_dependency("foo", "^2.0.0", groups=["bar"])
)
app.poetry.package.add_dependency(
Factory.create_dependency("baz", "^1.0.0", groups=["bar"])
)
tester.execute("foo")
content = app.poetry.file.read()["tool"]["poetry"]
assert "foo" not in content["dependencies"]
assert "foo" not in content["group"]["bar"]["dependencies"]
assert "baz" in content["group"]["bar"]["dependencies"]
expected = """\
[tool.poetry.group.bar.dependencies]
baz = "^1.0.0"
"""
assert expected in content.as_string()
def test_remove_without_specific_group_removes_from_specific_groups(
tester, app, repo, command_tester_factory, installed
):
"""
Removing with a specific group given removes packages only from this group.
"""
installed.add_package(Package("foo", "2.0.0"))
repo.add_package(Package("foo", "2.0.0"))
repo.add_package(Package("baz", "1.0.0"))
content = app.poetry.file.read()
groups_content = tomlkit.parse(
"""\
[tool.poetry.group.bar.dependencies]
foo = "^2.0.0"
baz = "^1.0.0"
"""
)
content["tool"]["poetry"]["dependencies"]["foo"] = "^2.0.0"
content["tool"]["poetry"].value._insert_after(
"dependencies", "group", groups_content["tool"]["poetry"]["group"]
)
app.poetry.file.write(content)
app.poetry.package.add_dependency(Factory.create_dependency("foo", "^2.0.0"))
app.poetry.package.add_dependency(
Factory.create_dependency("foo", "^2.0.0", groups=["bar"])
)
app.poetry.package.add_dependency(
Factory.create_dependency("baz", "^1.0.0", groups=["bar"])
)
tester.execute("foo --group bar")
content = app.poetry.file.read()["tool"]["poetry"]
assert "foo" in content["dependencies"]
assert "foo" not in content["group"]["bar"]["dependencies"]
assert "baz" in content["group"]["bar"]["dependencies"]
expected = """\
[tool.poetry.group.bar.dependencies]
baz = "^1.0.0"
"""
assert expected in content.as_string()
def test_remove_does_not_live_empty_groups(
tester, app, repo, command_tester_factory, installed
):
"""
Empty groups are automatically discarded after package removal.
"""
installed.add_package(Package("foo", "2.0.0"))
repo.add_package(Package("foo", "2.0.0"))
repo.add_package(Package("baz", "1.0.0"))
content = app.poetry.file.read()
groups_content = tomlkit.parse(
"""\
[tool.poetry.group.bar.dependencies]
foo = "^2.0.0"
baz = "^1.0.0"
"""
)
content["tool"]["poetry"]["dependencies"]["foo"] = "^2.0.0"
content["tool"]["poetry"].value._insert_after(
"dependencies", "group", groups_content["tool"]["poetry"]["group"]
)
app.poetry.file.write(content)
app.poetry.package.add_dependency(Factory.create_dependency("foo", "^2.0.0"))
app.poetry.package.add_dependency(
Factory.create_dependency("foo", "^2.0.0", groups=["bar"])
)
app.poetry.package.add_dependency(
Factory.create_dependency("baz", "^1.0.0", groups=["bar"])
)
tester.execute("foo baz --group bar")
content = app.poetry.file.read()["tool"]["poetry"]
assert "foo" in content["dependencies"]
assert "foo" not in content["group"]["bar"]["dependencies"]
assert "baz" not in content["group"]["bar"]["dependencies"]
assert "[tool.poetry.group.bar]" not in content.as_string()
assert "[tool.poetry.group]" not in content.as_string()
def test_remove_command_should_not_write_changes_upon_installer_errors(
tester, app, repo, command_tester_factory, mocker
):
......
import pytest
from poetry.core.packages.dependency_group import DependencyGroup
from poetry.factory import Factory
from tests.helpers import get_package
......@@ -13,7 +14,7 @@ def test_show_basic_with_installed_packages(tester, poetry, installed):
poetry.package.add_dependency(Factory.create_dependency("cachy", "^0.1.0"))
poetry.package.add_dependency(Factory.create_dependency("pendulum", "^2.0.0"))
poetry.package.add_dependency(
Factory.create_dependency("pytest", "^3.7.3", category="dev")
Factory.create_dependency("pytest", "^3.7.3", groups=["dev"])
)
cachy_010 = get_package("cachy", "0.1.0")
......@@ -952,7 +953,7 @@ def test_show_outdated_no_dev_git_dev_dependency(tester, poetry, installed, repo
}
)
tester.execute("--outdated --no-dev")
tester.execute("--outdated --without dev")
expected = """\
cachy 0.1.0 0.2.0 Cachy package
......@@ -1071,6 +1072,159 @@ pendulum 2.0.0 Pendulum package
def test_show_non_dev_with_basic_installed_packages(tester, poetry, installed):
poetry.package.add_dependency(Factory.create_dependency("cachy", "^0.1.0"))
poetry.package.add_dependency(Factory.create_dependency("pendulum", "^2.0.0"))
poetry.package.add_dependency(
Factory.create_dependency("pytest", "*", groups=["dev"])
)
cachy_010 = get_package("cachy", "0.1.0")
cachy_010.description = "Cachy package"
pendulum_200 = get_package("pendulum", "2.0.0")
pendulum_200.description = "Pendulum package"
pytest_373 = get_package("pytest", "3.7.3")
pytest_373.description = "Pytest package"
pytest_373.category = "dev"
installed.add_package(cachy_010)
installed.add_package(pendulum_200)
installed.add_package(pytest_373)
poetry.locker.mock_lock_data(
{
"package": [
{
"name": "cachy",
"version": "0.1.0",
"description": "Cachy package",
"category": "main",
"optional": False,
"platform": "*",
"python-versions": "*",
"checksum": [],
},
{
"name": "pendulum",
"version": "2.0.0",
"description": "Pendulum package",
"category": "main",
"optional": False,
"platform": "*",
"python-versions": "*",
"checksum": [],
},
{
"name": "pytest",
"version": "3.7.3",
"description": "Pytest package",
"category": "dev",
"optional": False,
"platform": "*",
"python-versions": "*",
"checksum": [],
},
],
"metadata": {
"python-versions": "*",
"platform": "*",
"content-hash": "123456789",
"hashes": {"cachy": [], "pendulum": [], "pytest": []},
},
}
)
tester.execute("--without dev")
expected = """\
cachy 0.1.0 Cachy package
pendulum 2.0.0 Pendulum package
"""
assert expected == tester.io.fetch_output()
def test_show_with_group_only(tester, poetry, installed):
poetry.package.add_dependency(Factory.create_dependency("cachy", "^0.1.0"))
poetry.package.add_dependency(Factory.create_dependency("pendulum", "^2.0.0"))
poetry.package.add_dependency(
Factory.create_dependency("pytest", "*", groups=["dev"])
)
cachy_010 = get_package("cachy", "0.1.0")
cachy_010.description = "Cachy package"
pendulum_200 = get_package("pendulum", "2.0.0")
pendulum_200.description = "Pendulum package"
pytest_373 = get_package("pytest", "3.7.3")
pytest_373.description = "Pytest package"
pytest_373.category = "dev"
installed.add_package(cachy_010)
installed.add_package(pendulum_200)
installed.add_package(pytest_373)
poetry.locker.mock_lock_data(
{
"package": [
{
"name": "cachy",
"version": "0.1.0",
"description": "Cachy package",
"category": "main",
"optional": False,
"platform": "*",
"python-versions": "*",
"checksum": [],
},
{
"name": "pendulum",
"version": "2.0.0",
"description": "Pendulum package",
"category": "main",
"optional": False,
"platform": "*",
"python-versions": "*",
"checksum": [],
},
{
"name": "pytest",
"version": "3.7.3",
"description": "Pytest package",
"category": "dev",
"optional": False,
"platform": "*",
"python-versions": "*",
"checksum": [],
},
],
"metadata": {
"python-versions": "*",
"platform": "*",
"content-hash": "123456789",
"hashes": {"cachy": [], "pendulum": [], "pytest": []},
},
}
)
tester.execute("--only dev")
expected = """\
pytest 3.7.3 Pytest package
"""
assert expected == tester.io.fetch_output()
def test_show_with_optional_group(tester, poetry, installed):
poetry.package.add_dependency(Factory.create_dependency("cachy", "^0.1.0"))
poetry.package.add_dependency(Factory.create_dependency("pendulum", "^2.0.0"))
group = DependencyGroup("dev", optional=True)
group.add_dependency(Factory.create_dependency("pytest", "*", groups=["dev"]))
poetry.package.add_dependency_group(group)
cachy_010 = get_package("cachy", "0.1.0")
cachy_010.description = "Cachy package"
......@@ -1128,7 +1282,7 @@ def test_show_non_dev_with_basic_installed_packages(tester, poetry, installed):
}
)
tester.execute("--no-dev")
tester.execute()
expected = """\
cachy 0.1.0 Cachy package
......@@ -1137,6 +1291,16 @@ pendulum 2.0.0 Pendulum package
assert expected == tester.io.fetch_output()
tester.execute("--with dev")
expected = """\
cachy 0.1.0 Cachy package
pendulum 2.0.0 Pendulum package
pytest 3.7.3 Pytest package
"""
assert expected == tester.io.fetch_output()
def test_show_tree(tester, poetry, installed):
poetry.package.add_dependency(Factory.create_dependency("cachy", "^0.2.0"))
......@@ -1193,7 +1357,7 @@ cachy 0.2.0
def test_show_tree_no_dev(tester, poetry, installed):
poetry.package.add_dependency(Factory.create_dependency("cachy", "^0.2.0"))
poetry.package.add_dependency(
Factory.create_dependency("pytest", "^6.1.0", category="dev")
Factory.create_dependency("pytest", "^6.1.0", groups=["dev"])
)
cachy2 = get_package("cachy", "0.2.0")
......@@ -1247,7 +1411,7 @@ def test_show_tree_no_dev(tester, poetry, installed):
}
)
tester.execute("--tree --no-dev")
tester.execute("--tree --without dev")
expected = """\
cachy 0.2.0
......
......@@ -46,7 +46,7 @@ functools32 = { version = "^3.2.3", markers = "python_version ~= '2.7' and sys_p
[tool.poetry.extras]
db = [ "orator" ]
[tool.poetry.dev-dependencies]
[tool.poetry.group.dev.dependencies]
pytest = "~3.4"
......
......@@ -101,7 +101,7 @@ six = "*"
[metadata]
lock-version = "1.1"
python-versions = "^3.8"
content-hash = "0cd068218f235c162f7b74bc8faf4ce3387b82daee1c1bb7a97af034f27ee116"
content-hash = "ae61bd854548e88c090780099edd400d58e6944ce9f3fc086d2f9aa5ac487f14"
[metadata.files]
certifi = [
......
......@@ -7,7 +7,6 @@ from pathlib import Path
from poetry.console.application import Application
from poetry.core.masonry.utils.helpers import escape_name
from poetry.core.masonry.utils.helpers import escape_version
from poetry.core.packages.dependency import Dependency
from poetry.core.packages.package import Package
from poetry.core.packages.utils.link import Link
from poetry.core.toml.file import TOMLFile
......@@ -28,15 +27,18 @@ def get_package(name, version):
def get_dependency(
name, constraint=None, category="main", optional=False, allows_prereleases=False
name, constraint=None, groups=None, optional=False, allows_prereleases=False
):
return Dependency(
name,
constraint or "*",
category=category,
optional=optional,
allows_prereleases=allows_prereleases,
)
if constraint is None:
constraint = "*"
if isinstance(constraint, str):
constraint = {"version": constraint}
constraint["optional"] = optional
constraint["allow_prereleases"] = allows_prereleases
return Factory.create_dependency(name, constraint or "*", groups=groups)
def fixture(path=None):
......
......@@ -15,6 +15,7 @@ from cleo.io.outputs.buffered_output import BufferedOutput
from cleo.io.outputs.output import Verbosity
from deepdiff import DeepDiff
from poetry.core.packages.dependency_group import DependencyGroup
from poetry.core.packages.package import Package
from poetry.core.packages.project_package import ProjectPackage
from poetry.core.toml.file import TOMLFile
......@@ -277,7 +278,9 @@ def test_run_update_after_removing_dependencies(
assert 1 == installer.executor.removals_count
def _configure_run_install_dev(locker, repo, package, installed):
def _configure_run_install_dev(
locker, repo, package, installed, with_optional_group=False
):
"""
Perform common test setup for `test_run_install_*dev*()` methods.
"""
......@@ -334,13 +337,16 @@ def _configure_run_install_dev(locker, repo, package, installed):
package.add_dependency(Factory.create_dependency("A", "~1.0"))
package.add_dependency(Factory.create_dependency("B", "~1.1"))
package.add_dependency(Factory.create_dependency("C", "~1.2", category="dev"))
group = DependencyGroup("dev", optional=with_optional_group)
group.add_dependency(Factory.create_dependency("C", "~1.2", groups=["dev"]))
package.add_dependency_group(group)
def test_run_install_no_dev(installer, locker, repo, package, installed):
def test_run_install_no_group(installer, locker, repo, package, installed):
_configure_run_install_dev(locker, repo, package, installed)
installer.dev_mode(False)
installer.without_groups(["dev"])
installer.run()
assert 0 == installer.executor.installations_count
......@@ -348,10 +354,10 @@ def test_run_install_no_dev(installer, locker, repo, package, installed):
assert 1 == installer.executor.removals_count
def test_run_install_dev_only(installer, locker, repo, package, installed):
def test_run_install_group_only(installer, locker, repo, package, installed):
_configure_run_install_dev(locker, repo, package, installed)
installer.dev_only(True)
installer.only_groups(["dev"])
installer.run()
assert 0 == installer.executor.installations_count
......@@ -359,11 +365,13 @@ def test_run_install_dev_only(installer, locker, repo, package, installed):
assert 2 == installer.executor.removals_count
def test_run_install_no_dev_and_dev_only(installer, locker, repo, package, installed):
_configure_run_install_dev(locker, repo, package, installed)
def test_run_install_with_optional_group_not_selected(
installer, locker, repo, package, installed
):
_configure_run_install_dev(
locker, repo, package, installed, with_optional_group=True
)
installer.dev_mode(False)
installer.dev_only(True)
installer.run()
assert 0 == installer.executor.installations_count
......@@ -371,6 +379,21 @@ def test_run_install_no_dev_and_dev_only(installer, locker, repo, package, insta
assert 1 == installer.executor.removals_count
def test_run_install_with_optional_group_selected(
installer, locker, repo, package, installed
):
_configure_run_install_dev(
locker, repo, package, installed, with_optional_group=True
)
installer.with_groups(["dev"])
installer.run()
assert 0 == installer.executor.installations_count
assert 0 == installer.executor.updates_count
assert 0 == installer.executor.removals_count
@pytest.mark.parametrize(
"managed_reserved_package_names",
[
......@@ -439,7 +462,7 @@ def test_run_install_remove_untracked(
}
)
installer.dev_mode(True).remove_untracked(True)
installer.remove_untracked(True)
installer.run()
assert 0 == installer.executor.installations_count
......@@ -863,7 +886,7 @@ def test_installer_with_pypi_repository(package, locker, installed, config):
NullIO(), NullEnv(), package, locker, pool, config, installed=installed
)
package.add_dependency(Factory.create_dependency("pytest", "^3.5", category="dev"))
package.add_dependency(Factory.create_dependency("pytest", "^3.5", groups=["dev"]))
installer.run()
expected = fixture("with-pypi-repository")
......@@ -1069,7 +1092,7 @@ def test_run_changes_category_if_needed(installer, locker, repo, package):
package.add_dependency(
Factory.create_dependency(
"A", {"version": "^1.0", "optional": True}, category="dev"
"A", {"version": "^1.0", "optional": True}, groups=["dev"]
)
)
package.add_dependency(Factory.create_dependency("B", "^1.1"))
......@@ -1169,8 +1192,8 @@ def test_run_update_with_locked_extras(installer, locker, repo, package):
b_dependency.in_extras.append("foo")
c_dependency = get_dependency("C", "^1.0")
c_dependency.python_versions = "~2.7"
package_a.requires.append(b_dependency)
package_a.requires.append(c_dependency)
package_a.add_dependency(b_dependency)
package_a.add_dependency(c_dependency)
repo.add_package(package_a)
repo.add_package(get_package("B", "1.0"))
......@@ -1869,7 +1892,7 @@ def test_installer_can_handle_old_lock_files(
pool = Pool()
pool.add_repository(MockRepository())
package.add_dependency(Factory.create_dependency("pytest", "^3.5", category="dev"))
package.add_dependency(Factory.create_dependency("pytest", "^3.5", groups=["dev"]))
locker.locked()
locker.mock_lock_data(fixture("old-lock"))
......
......@@ -227,7 +227,7 @@ def test_run_update_after_removing_dependencies(
assert len(removals) == 1
def test_run_install_no_dev(installer, locker, repo, package, installed):
def test_run_install_no_group(installer, locker, repo, package, installed):
locker.locked(True)
locker.mock_lock_data(
{
......@@ -281,9 +281,9 @@ def test_run_install_no_dev(installer, locker, repo, package, installed):
package.add_dependency(Factory.create_dependency("A", "~1.0"))
package.add_dependency(Factory.create_dependency("B", "~1.1"))
package.add_dependency(Factory.create_dependency("C", "~1.2", category="dev"))
package.add_dependency(Factory.create_dependency("C", "~1.2", groups=["dev"]))
installer.dev_mode(False)
installer.without_groups(["dev"])
installer.run()
installs = installer.installer.installs
......@@ -364,7 +364,7 @@ def test_run_install_remove_untracked(
}
)
installer.dev_mode(True).remove_untracked(True)
installer.remove_untracked(True)
installer.run()
installs = installer.installer.installs
......@@ -767,7 +767,7 @@ def test_installer_with_pypi_repository(package, locker, installed, config):
NullIO(), NullEnv(), package, locker, pool, config, installed=installed
)
package.add_dependency(Factory.create_dependency("pytest", "^3.5", category="dev"))
package.add_dependency(Factory.create_dependency("pytest", "^3.5", groups=["dev"]))
installer.run()
expected = fixture("with-pypi-repository")
......@@ -974,7 +974,7 @@ def test_run_changes_category_if_needed(installer, locker, repo, package):
package.add_dependency(
Factory.create_dependency(
"A", {"version": "^1.0", "optional": True}, category="dev"
"A", {"version": "^1.0", "optional": True}, groups=["dev"]
)
)
package.add_dependency(Factory.create_dependency("B", "^1.1"))
......@@ -1074,8 +1074,8 @@ def test_run_update_with_locked_extras(installer, locker, repo, package):
b_dependency.in_extras.append("foo")
c_dependency = get_dependency("C", "^1.0")
c_dependency.python_versions = "~2.7"
package_a.requires.append(b_dependency)
package_a.requires.append(c_dependency)
package_a.add_dependency(b_dependency)
package_a.add_dependency(c_dependency)
repo.add_package(package_a)
repo.add_package(get_package("B", "1.0"))
......@@ -1739,7 +1739,7 @@ def test_installer_can_handle_old_lock_files(
pool = Pool()
pool.add_repository(MockRepository())
package.add_dependency(Factory.create_dependency("pytest", "^3.5", category="dev"))
package.add_dependency(Factory.create_dependency("pytest", "^3.5", groups=["dev"]))
locker.locked()
locker.mock_lock_data(fixture("old-lock"))
......
......@@ -86,7 +86,7 @@ resolved_reference = "123456"
[metadata]
lock-version = "1.1"
python-versions = "*"
content-hash = "115cf985d932e9bf5f540555bbdd75decbb62cac81e399375fc19f6277f8c1d8"
content-hash = "178f2cd01dc40e96be23a4a0ae1094816626346346618335e5ff4f0b2c0c5831"
[metadata.files]
A = [
......@@ -295,7 +295,7 @@ python-versions = "*"
[metadata]
lock-version = "1.1"
python-versions = "*"
content-hash = "115cf985d932e9bf5f540555bbdd75decbb62cac81e399375fc19f6277f8c1d8"
content-hash = "178f2cd01dc40e96be23a4a0ae1094816626346346618335e5ff4f0b2c0c5831"
[metadata.files]
A = []
......@@ -335,7 +335,7 @@ foo = ["B (>=1.0.0)"]
[metadata]
lock-version = "1.1"
python-versions = "*"
content-hash = "115cf985d932e9bf5f540555bbdd75decbb62cac81e399375fc19f6277f8c1d8"
content-hash = "178f2cd01dc40e96be23a4a0ae1094816626346346618335e5ff4f0b2c0c5831"
[metadata.files]
A = []
......@@ -365,7 +365,7 @@ foo = ["bar"]
[metadata]
lock-version = "1.1"
python-versions = "*"
content-hash = "115cf985d932e9bf5f540555bbdd75decbb62cac81e399375fc19f6277f8c1d8"
content-hash = "178f2cd01dc40e96be23a4a0ae1094816626346346618335e5ff4f0b2c0c5831"
[metadata.files]
A = []
......@@ -410,7 +410,7 @@ reference = "legacy"
[metadata]
lock-version = "1.1"
python-versions = "*"
content-hash = "115cf985d932e9bf5f540555bbdd75decbb62cac81e399375fc19f6277f8c1d8"
content-hash = "178f2cd01dc40e96be23a4a0ae1094816626346346618335e5ff4f0b2c0c5831"
[metadata.files]
A = []
......@@ -497,7 +497,7 @@ B = {version = "^1.0.0", extras = ["a", "b", "c"], optional = true}
[metadata]
lock-version = "1.1"
python-versions = "*"
content-hash = "115cf985d932e9bf5f540555bbdd75decbb62cac81e399375fc19f6277f8c1d8"
content-hash = "178f2cd01dc40e96be23a4a0ae1094816626346346618335e5ff4f0b2c0c5831"
[metadata.files]
A = []
......@@ -591,7 +591,7 @@ F = {git = "https://github.com/python-poetry/poetry.git", branch = "foo"}
[metadata]
lock-version = "1.1"
python-versions = "*"
content-hash = "115cf985d932e9bf5f540555bbdd75decbb62cac81e399375fc19f6277f8c1d8"
content-hash = "178f2cd01dc40e96be23a4a0ae1094816626346346618335e5ff4f0b2c0c5831"
[metadata.files]
A = []
......
......@@ -148,7 +148,7 @@ def test_solver_with_deps(solver, repo, package):
repo.add_package(package_b)
repo.add_package(new_package_b)
package_a.requires.append(get_dependency("B", "<1.1"))
package_a.add_dependency(get_dependency("B", "<1.1"))
ops = solver.solve()
......@@ -176,7 +176,7 @@ def test_install_honours_not_equal(solver, repo, package):
repo.add_package(new_package_b12)
repo.add_package(new_package_b13)
package_a.requires.append(get_dependency("B", "<=1.3,!=1.3,!=1.2"))
package_a.add_dependency(get_dependency("B", "<=1.3,!=1.3,!=1.2"))
ops = solver.solve()
......@@ -201,10 +201,10 @@ def test_install_with_deps_in_order(solver, repo, package):
repo.add_package(package_b)
repo.add_package(package_c)
package_b.requires.append(get_dependency("A", ">=1.0"))
package_b.requires.append(get_dependency("C", ">=1.0"))
package_b.add_dependency(get_dependency("A", ">=1.0"))
package_b.add_dependency(get_dependency("C", ">=1.0"))
package_c.requires.append(get_dependency("A", ">=1.0"))
package_c.add_dependency(get_dependency("A", ">=1.0"))
ops = solver.solve()
......@@ -278,9 +278,9 @@ def test_update_with_use_latest(solver, repo, installed, package, locked):
)
def test_solver_sets_categories(solver, repo, package):
def test_solver_sets_groups(solver, repo, package):
package.add_dependency(Factory.create_dependency("A", "*"))
package.add_dependency(Factory.create_dependency("B", "*", category="dev"))
package.add_dependency(Factory.create_dependency("B", "*", groups=["dev"]))
package_a = get_package("A", "1.0")
package_b = get_package("B", "1.0")
......@@ -429,7 +429,7 @@ def test_solver_returns_extras_if_requested(solver, repo, package):
dep = get_dependency("C", "^1.0", optional=True)
dep.marker = parse_marker("extra == 'foo'")
package_b.extras = {"foo": [dep]}
package_b.requires.append(dep)
package_b.add_dependency(dep)
repo.add_package(package_a)
repo.add_package(package_b)
......@@ -474,8 +474,8 @@ def test_solver_returns_extras_only_requested(solver, repo, package, enabled_ext
package_b.extras = {"one": [dep10], "two": [dep20]}
package_b.requires.append(dep10)
package_b.requires.append(dep20)
package_b.add_dependency(dep10)
package_b.add_dependency(dep20)
repo.add_package(package_a)
repo.add_package(package_b)
......@@ -523,7 +523,7 @@ def test_solver_returns_extras_when_multiple_extras_use_same_dependency(
package_b.extras = {"one": [dep], "two": [dep]}
package_b.requires.append(dep)
package_b.add_dependency(dep)
extras = [enabled_extra] if enabled_extra is not None else []
package_a.add_dependency(
......@@ -574,8 +574,8 @@ def test_solver_returns_extras_only_requested_nested(
package_b.extras = {"one": [dep10], "two": [dep20]}
package_b.requires.append(dep10)
package_b.requires.append(dep20)
package_b.add_dependency(dep10)
package_b.add_dependency(dep20)
extras = [enabled_extra] if enabled_extra is not None else []
package_a.add_dependency(
......@@ -824,14 +824,14 @@ def test_solver_sub_dependencies_with_not_supported_python_version_transitive(
)
def test_solver_with_dependency_in_both_main_and_dev_dependencies(
def test_solver_with_dependency_in_both_default_and_dev_dependencies(
solver, repo, package
):
solver.provider.set_package_python_versions("^3.5")
package.add_dependency(Factory.create_dependency("A", "*"))
package.add_dependency(
Factory.create_dependency(
"A", {"version": "*", "extras": ["foo"]}, category="dev"
"A", {"version": "*", "extras": ["foo"]}, groups=["dev"]
)
)
......@@ -884,7 +884,7 @@ def test_solver_with_dependency_in_both_main_and_dev_dependencies_with_one_more_
package.add_dependency(Factory.create_dependency("E", "*"))
package.add_dependency(
Factory.create_dependency(
"A", {"version": "*", "extras": ["foo"]}, category="dev"
"A", {"version": "*", "extras": ["foo"]}, groups=["dev"]
)
)
......@@ -1417,8 +1417,8 @@ def test_solver_does_not_trigger_new_resolution_on_duplicate_dependencies_if_onl
package_a = get_package("A", "1.0.0")
package_a.extras = {"foo": [dep1], "bar": [dep2]}
package_a.requires.append(dep1)
package_a.requires.append(dep2)
package_a.add_dependency(dep1)
package_a.add_dependency(dep2)
package_b2 = get_package("B", "2.0.0")
package_b1 = get_package("B", "1.0.0")
......@@ -1539,7 +1539,7 @@ def test_solver_ignores_dependencies_with_incompatible_python_full_version_marke
package.add_dependency(Factory.create_dependency("B", "^2.0"))
package_a = get_package("A", "1.0.0")
package_a.requires.append(
package_a.add_dependency(
Dependency.create_from_pep_508(
'B (<2.0); platform_python_implementation == "PyPy" and python_full_version < "2.7.9"'
)
......@@ -2286,12 +2286,12 @@ def test_solver_does_not_raise_conflict_for_conditional_dev_dependencies(
solver.provider.set_package_python_versions("~2.7 || ^3.5")
package.add_dependency(
Factory.create_dependency(
"A", {"version": "^1.0", "python": "~2.7"}, category="dev"
"A", {"version": "^1.0", "python": "~2.7"}, groups=["dev"]
)
)
package.add_dependency(
Factory.create_dependency(
"A", {"version": "^2.0", "python": "^3.5"}, category="dev"
"A", {"version": "^2.0", "python": "^3.5"}, groups=["dev"]
)
)
......
......@@ -62,16 +62,12 @@ def poetry(fixture_dir, locker):
def set_package_requires(poetry, skip=None):
skip = skip or set()
packages = poetry.locker.locked_repository(with_dev_reqs=True).packages
poetry.package.requires = [
pkg.to_dependency()
for pkg in packages
if pkg.category == "main" and pkg.name not in skip
]
poetry.package.dev_requires = [
pkg.to_dependency()
for pkg in packages
if pkg.category == "dev" and pkg.name not in skip
]
package = poetry.package.with_dependency_groups([], only=True)
for pkg in packages:
if pkg.name not in skip:
package.add_dependency(pkg.to_dependency())
poetry._package = package
def test_exporter_can_export_requirements_txt_with_standard_packages(
......@@ -483,16 +479,18 @@ def test_exporter_can_export_requirements_txt_with_nested_packages_and_markers_a
}
)
poetry.package.requires = [
root = poetry.package.with_dependency_groups([], only=True)
root.add_dependency(
Factory.create_dependency(
name="a", constraint=dict(version="^1.2.3", python="<3.8")
),
]
poetry.package.dev_requires = [
)
)
root.add_dependency(
Factory.create_dependency(
name="b", constraint=dict(version="^4.5.6"), category="dev"
),
]
name="b", constraint=dict(version="^4.5.6"), groups=["dev"]
)
)
poetry._package = root
exporter = Exporter(poetry)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment