Commit 72c96a81 by Tom Milligan Committed by Sébastien Eustace

export: add extras flag and exclude optional packages by default (#1285)

parent b637e9a1
......@@ -442,6 +442,7 @@ poetry export -f requirements.txt > requirements.txt
* `--output (-o)`: The name of the output file. If omitted, print to standard
output.
* `--dev`: Include development dependencies.
* `--extras (-E)`: Extra sets of dependencies to include.
* `--without-hashes`: Exclude hashes from the exported file.
* `--with-credentials`: Include credentials for extra indices.
......
from cleo import option
from poetry.utils.exporter import Exporter
from cleo import option
from .command import Command
......@@ -14,6 +15,13 @@ class ExportCommand(Command):
option("output", "o", "The name of the output file.", flag=False),
option("without-hashes", None, "Exclude hashes from the exported file."),
option("dev", None, "Include development dependencies."),
option(
"extras",
"E",
"Extra sets of dependencies to include.",
flag=False,
multiple=True,
),
option("with-credentials", None, "Include credentials for extra indices."),
]
......@@ -55,5 +63,6 @@ class ExportCommand(Command):
output or self.io,
with_hashes=not self.option("without-hashes"),
dev=self.option("dev"),
extras=self.option("extras"),
with_credentials=self.option("with-credentials"),
)
......@@ -23,7 +23,14 @@ class Exporter(object):
self._poetry = poetry
def export(
self, fmt, cwd, output, with_hashes=True, dev=False, with_credentials=False
self,
fmt,
cwd,
output,
with_hashes=True,
dev=False,
extras=None,
with_credentials=False,
): # type: (str, Path, Union[IO, str], bool, bool, bool) -> None
if fmt not in self.ACCEPTED_FORMATS:
raise ValueError("Invalid export format: {}".format(fmt))
......@@ -33,18 +40,39 @@ class Exporter(object):
output,
with_hashes=with_hashes,
dev=dev,
extras=extras,
with_credentials=with_credentials,
)
def _export_requirements_txt(
self, cwd, output, with_hashes=True, dev=False, with_credentials=False
self,
cwd,
output,
with_hashes=True,
dev=False,
extras=None,
with_credentials=False,
): # type: (Path, Union[IO, str], bool, bool, bool) -> None
indexes = []
content = ""
# Generate a list of package names we have opted into via `extras`
extras_set = frozenset(extras or ())
extra_package_names = set()
if extras:
for extra_name, extra_packages in self._poetry.locker.lock_data.get(
"extras", {}
).items():
if extra_name in extras_set:
extra_package_names.update(extra_packages)
for package in sorted(
self._poetry.locker.locked_repository(dev).packages, key=lambda p: p.name
):
# If a package is optional and we haven't opted in to it, continue
if package.optional and package.name not in extra_package_names:
continue
if package.source_type == "git":
dependency = VCSDependency(
package.name,
......
......@@ -39,6 +39,10 @@ classifiers = [
[tool.poetry.dependencies]
python = "~2.7 || ^3.4"
foo = "^1.0"
bar = { version = "^1.1", optional = true }
[tool.poetry.extras]
feature_bar = ["bar"]
"""
......@@ -68,6 +72,7 @@ def test_export_exports_requirements_txt_file_locks_if_no_lock_file(app, repo):
assert not app.poetry.locker.lock.exists()
repo.add_package(get_package("foo", "1.0.0"))
repo.add_package(get_package("bar", "1.1.0"))
tester.execute("--format requirements.txt --output requirements.txt")
......@@ -89,6 +94,7 @@ foo==1.0.0
def test_export_exports_requirements_txt_uses_lock_file(app, repo):
repo.add_package(get_package("foo", "1.0.0"))
repo.add_package(get_package("bar", "1.1.0"))
command = app.find("lock")
tester = CommandTester(command)
......@@ -119,6 +125,7 @@ foo==1.0.0
def test_export_fails_on_invalid_format(app, repo):
repo.add_package(get_package("foo", "1.0.0"))
repo.add_package(get_package("bar", "1.1.0"))
command = app.find("lock")
tester = CommandTester(command)
......@@ -135,6 +142,7 @@ def test_export_fails_on_invalid_format(app, repo):
def test_export_prints_to_stdout_by_default(app, repo):
repo.add_package(get_package("foo", "1.0.0"))
repo.add_package(get_package("bar", "1.1.0"))
command = app.find("lock")
tester = CommandTester(command)
......@@ -152,3 +160,26 @@ foo==1.0.0
"""
assert expected == tester.io.fetch_output()
def test_export_includes_extras_by_flag(app, repo):
repo.add_package(get_package("foo", "1.0.0"))
repo.add_package(get_package("bar", "1.1.0"))
command = app.find("lock")
tester = CommandTester(command)
tester.execute()
assert app.poetry.locker.lock.exists()
command = app.find("export")
tester = CommandTester(command)
tester.execute("--format requirements.txt --extras feature_bar")
expected = """\
bar==1.1.0
foo==1.0.0
"""
assert expected == tester.io.fetch_output()
......@@ -310,6 +310,99 @@ foo==1.2.3 \\
assert expected == content
def test_exporter_exports_requirements_txt_without_optional_packages(tmp_dir, poetry):
poetry.locker.mock_lock_data(
{
"package": [
{
"name": "foo",
"version": "1.2.3",
"category": "main",
"optional": False,
"python-versions": "*",
},
{
"name": "bar",
"version": "4.5.6",
"category": "dev",
"optional": True,
"python-versions": "*",
},
],
"metadata": {
"python-versions": "*",
"content-hash": "123456789",
"hashes": {"foo": ["12345"], "bar": ["67890"]},
},
}
)
exporter = Exporter(poetry)
exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt", dev=True)
with (Path(tmp_dir) / "requirements.txt").open(encoding="utf-8") as f:
content = f.read()
expected = """\
foo==1.2.3 \\
--hash=sha256:12345
"""
assert expected == content
def test_exporter_exports_requirements_txt_with_optional_packages_if_opted_in(
tmp_dir, poetry
):
poetry.locker.mock_lock_data(
{
"package": [
{
"name": "foo",
"version": "1.2.3",
"category": "main",
"optional": False,
"python-versions": "*",
},
{
"name": "bar",
"version": "4.5.6",
"category": "dev",
"optional": True,
"python-versions": "*",
},
],
"metadata": {
"python-versions": "*",
"content-hash": "123456789",
"hashes": {"foo": ["12345"], "bar": ["67890"]},
},
"extras": {"feature_bar": ["bar"]},
}
)
exporter = Exporter(poetry)
exporter.export(
"requirements.txt",
Path(tmp_dir),
"requirements.txt",
dev=True,
extras=["feature_bar"],
)
with (Path(tmp_dir) / "requirements.txt").open(encoding="utf-8") as f:
content = f.read()
expected = """\
bar==4.5.6 \\
--hash=sha256:67890
foo==1.2.3 \\
--hash=sha256:12345
"""
assert expected == content
def test_exporter_can_export_requirements_txt_with_git_packages(tmp_dir, poetry):
poetry.locker.mock_lock_data(
{
......
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