Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

This package is a plugin that allows the export of locked packages to various formats.

**Note**: For now, only the `requirements.txt` format is available.
**Note**: For now, only the `constraints.txt` and `requirements.txt` formats are available.

This plugin provides the same features as the existing `export` command of Poetry which it will eventually replace.

Expand Down Expand Up @@ -36,11 +36,11 @@ The plugin provides an `export` command to export to the desired format.
poetry export -f requirements.txt --output requirements.txt
```

**Note**: Only the `requirements.txt` format is currently supported.
**Note**: Only the `constraints.txt` and `requirements.txt` formats are currently supported.

### Available options

* `--format (-f)`: The format to export to (default: `requirements.txt`). Currently, only `requirements.txt` is supported.
* `--format (-f)`: The format to export to (default: `requirements.txt`). Currently, only `constraints.txt` and `requirements.txt` are supported.
* `--output (-o)`: The name of the output file. If omitted, print to standard output.
* `--without`: The dependency groups to ignore when exporting.
* `--with`: The optional dependency groups to include when exporting.
Expand Down
4 changes: 2 additions & 2 deletions docs/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ menu:
The export plugin allows the export of locked packages to various formats.

{{% note %}}
Only the `requirements.txt` format is currently supported.
Only the `constraints.txt` and `requirements.txt` formats are currently supported.
{{% /note %}}

## Exporting packages
Expand Down Expand Up @@ -65,7 +65,7 @@ poetry export --only test,docs

### Available options

* `--format (-f)`: The format to export to (default: `requirements.txt`). Currently, only `requirements.txt` is supported.
* `--format (-f)`: The format to export to (default: `requirements.txt`). Currently, only `constraints.txt` and `requirements.txt` are supported.
* `--output (-o)`: The name of the output file. If omitted, print to standard output.
* `--without`: The dependency groups to ignore when exporting.
* `--with`: The optional dependency groups to include when exporting.
Expand Down
3 changes: 2 additions & 1 deletion src/poetry_plugin_export/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ class ExportCommand(GroupCommand):
option(
"format",
"f",
"Format to export to. Currently, only requirements.txt is supported.",
"Format to export to. Currently, only constraints.txt and requirements.txt"
" are supported.",
flag=False,
default=Exporter.FORMAT_REQUIREMENTS_TXT,
),
Expand Down
22 changes: 21 additions & 1 deletion src/poetry_plugin_export/exporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,14 @@ class Exporter:
Exporter class to export a lock file to alternative formats.
"""

FORMAT_CONSTRAINTS_TXT = "constraints.txt"
FORMAT_REQUIREMENTS_TXT = "requirements.txt"
ALLOWED_HASH_ALGORITHMS = ("sha256", "sha384", "sha512")

EXPORT_METHODS = {FORMAT_REQUIREMENTS_TXT: "_export_requirements_txt"}
EXPORT_METHODS = {
FORMAT_CONSTRAINTS_TXT: "_export_constraints_txt",
FORMAT_REQUIREMENTS_TXT: "_export_requirements_txt",
}

def __init__(self, poetry: Poetry) -> None:
self._poetry = poetry
Expand Down Expand Up @@ -71,7 +75,15 @@ def export(self, fmt: str, cwd: Path, output: IO | str) -> None:

getattr(self, self.EXPORT_METHODS[fmt])(cwd, output)

def _export_constraints_txt(self, cwd: Path, output: IO | str) -> None:
return self._export_generic_txt(cwd, output, export_as_constraints=True)

def _export_requirements_txt(self, cwd: Path, output: IO | str) -> None:
return self._export_generic_txt(cwd, output, export_as_constraints=False)

def _export_generic_txt(
self, cwd: Path, output: IO | str, export_as_constraints: bool
) -> None:
from poetry.core.packages.utils.utils import path_to_url

indexes = set()
Expand All @@ -90,10 +102,18 @@ def _export_requirements_txt(self, cwd: Path, output: IO | str) -> None:
):
line = ""

if export_as_constraints:
dependency_package = dependency_package.without_features()

dependency = dependency_package.dependency
package = dependency_package.package

if package.develop:
if export_as_constraints:
raise RuntimeError(
f"{package.pretty_name} is configured for develop mode"
" which is incompatible with the constraints format."
)
line += "-e "

requirement = dependency.to_pep_508(with_extras=False)
Expand Down
133 changes: 133 additions & 0 deletions tests/test_exporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,139 @@ def set_package_requires(poetry: Poetry, skip: set[str] | None = None) -> None:
poetry._package = package


def test_exporter_keeps_extras_for_requirements_txt(
tmp_dir: str, poetry: Poetry
) -> None:
poetry.locker.mock_lock_data( # type: ignore[attr-defined]
{
"package": [
{
"name": "foo",
"version": "1.2.3",
"category": "main",
"optional": False,
"python-versions": "*",
"dependencies": {
"bar": {
"extras": ["baz"],
"version": ">=0.1.0",
}
},
},
{
"name": "bar",
"version": "4.5.6",
"category": "main",
"optional": False,
"python-versions": "*",
"dependencies": {
"baz": {
"version": ">=0.1.0",
"optional": True,
"markers": "extra == 'baz'",
}
},
"extras": {"baz": ["baz (>=0.1.0)"]},
},
{
"name": "baz",
"version": "7.8.9",
"category": "main",
"optional": False,
"python-versions": "*",
},
],
"metadata": {
"python-versions": "*",
"content-hash": "123456789",
"files": {"foo": [], "bar": [], "baz": []},
},
}
)
set_package_requires(poetry)

exporter = Exporter(poetry)
exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt")

with (Path(tmp_dir) / "requirements.txt").open(encoding="utf-8") as f:
content = f.read()

expected = f"""\
bar==4.5.6 ; {MARKER_PY}
bar[baz]==4.5.6 ; {MARKER_PY}
baz==7.8.9 ; {MARKER_PY}
foo==1.2.3 ; {MARKER_PY}
"""

assert content == expected


def test_exporter_removes_extras_for_constraints_txt(
tmp_dir: str, poetry: Poetry
) -> None:
poetry.locker.mock_lock_data( # type: ignore[attr-defined]
{
"package": [
{
"name": "foo",
"version": "1.2.3",
"category": "main",
"optional": False,
"python-versions": "*",
"dependencies": {
"bar": {
"extras": ["baz"],
"version": ">=0.1.0",
}
},
},
{
"name": "bar",
"version": "4.5.6",
"category": "main",
"optional": False,
"python-versions": "*",
"dependencies": {
"baz": {
"version": ">=0.1.0",
"optional": True,
"markers": "extra == 'baz'",
}
},
"extras": {"baz": ["baz (>=0.1.0)"]},
},
{
"name": "baz",
"version": "7.8.9",
"category": "main",
"optional": False,
"python-versions": "*",
},
],
"metadata": {
"python-versions": "*",
"content-hash": "123456789",
"files": {"foo": [], "bar": [], "baz": []},
},
}
)
set_package_requires(poetry)

exporter = Exporter(poetry)
exporter.export("constraints.txt", Path(tmp_dir), "constraints.txt")

with (Path(tmp_dir) / "constraints.txt").open(encoding="utf-8") as f:
content = f.read()

expected = f"""\
bar==4.5.6 ; {MARKER_PY}
baz==7.8.9 ; {MARKER_PY}
foo==1.2.3 ; {MARKER_PY}
"""

assert content == expected


def test_exporter_can_export_requirements_txt_with_standard_packages(
tmp_dir: str, poetry: Poetry
) -> None:
Expand Down