Skip to content

Commit

Permalink
Add support for file: directive in install_requires, `extras_requ…
Browse files Browse the repository at this point in the history
…ire` (#77)
  • Loading branch information
abravalheri committed Mar 27, 2023
2 parents 94781cb + 17fa13f commit 9120a9f
Show file tree
Hide file tree
Showing 8 changed files with 135 additions and 6 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@
Changelog
=========

Version 0.13 (dev)
==================

* ``setuptools`` plugin:
* Add support for ``file:`` directive in ``install_requires`` and ``extras_require``, :pr:`77`.
Note that ``setuptools`` (as per v67.6) may require that all ``optional-dependencies``
to be specified via the ``file:`` directive if at least one of them requires so.

Version 0.12
============

Expand Down
21 changes: 17 additions & 4 deletions src/ini2toml/plugins/setuptools_pep621.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ def processing_rules(self) -> ProcessingRules:
# ---- Options ----
("options", "zip-safe"): split_bool,
("options", "setup-requires"): split_deps,
("options", "install-requires"): split_deps,
("options", "install-requires"): directive("file", orelse=split_deps),
("options", "tests-require"): split_deps,
("options", "scripts"): split_list_comma,
("options", "eager-resources"): split_list_comma,
Expand Down Expand Up @@ -197,7 +197,7 @@ def dependent_processing_rules(self, doc: IR) -> ProcessingRules:
on the existing document.
"""
groups: Mapping[str, Transformation] = {
"options.extras-require": split_deps,
"options.extras-require": directive("file", orelse=split_deps),
"options.package-data": split_list_comma,
"options.exclude-package-data": split_list_comma,
"options.data-files": split_list_comma,
Expand Down Expand Up @@ -459,7 +459,7 @@ def handle_dynamic(self, doc: R) -> R:
when not explicitly set (in that case plugins such as ``setuptools_scm`` are
expected to provide a value at runtime).
"""
potential = ["version", "classifiers", "description"]
potential = ["version", "classifiers", "description", "dependencies"]
# directives = {k[-1]: v for k, v in self.setupcfg_directives().items()}
metadata, options = doc["metadata"], doc["options"]

Expand All @@ -484,8 +484,21 @@ def handle_dynamic(self, doc: R) -> R:
extras = ["scripts", "gui-scripts"]
if not fields:
return doc
metadata.setdefault("dynamic", []).extend(fields + extras)

dyn_deps = [
isinstance(v, Directive)
for v in doc.get("project:optional-dependencies", {}).values()
]
if any(dyn_deps):
dynamic["optional-dependencies"] = doc.pop("project:optional-dependencies")
fields.append("optional-dependencies")
if not all(dyn_deps):
_logger.warning(
"setuptools may require all optional-dependencies to be dynamic "
"when using `pyproject.toml`."
)

metadata.setdefault("dynamic", []).extend(fields + extras)
if dynamic:
doc.setdefault("options.dynamic", IR()).update(dynamic)
# ^ later `options.dynamic` is converted to `tool.setuptools.dynamic`
Expand Down
17 changes: 17 additions & 0 deletions tests/examples/dynamic_extras/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[build-system]
requires = ["setuptools>=61.2"]
build-backend = "setuptools.build_meta"

[project]
name = "hello"
version = "42"
dynamic = ["dependencies", "optional-dependencies"]

[tool.setuptools]
include-package-data = false

[tool.setuptools.dynamic]
dependencies = {file = ["requirements.txt"]}

[tool.setuptools.dynamic.optional-dependencies]
dev = {file = ["dev-requirements.txt"]}
9 changes: 9 additions & 0 deletions tests/examples/dynamic_extras/setup.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[metadata]
name = hello
version = 42

[options]
install_requires = file: requirements.txt

[options.extras_require]
dev = file: dev-requirements.txt
21 changes: 21 additions & 0 deletions tests/examples/dynamic_extras_mixed/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[build-system]
requires = ["setuptools>=61.2"]
build-backend = "setuptools.build_meta"

[project]
name = "hello"
version = "42"
dynamic = ["dependencies", "optional-dependencies"]

[tool.setuptools]
include-package-data = false

[tool.setuptools.dynamic]
dependencies = {file = ["requirements.txt"]}

[tool.setuptools.dynamic.optional-dependencies]
dev = {file = ["dev-requirements.txt"]}
other = [
"platformdirs",
"rich",
]
12 changes: 12 additions & 0 deletions tests/examples/dynamic_extras_mixed/setup.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[metadata]
name = hello
version = 42

[options]
install_requires = file: requirements.txt

[options.extras_require]
dev = file: dev-requirements.txt
other =
platformdirs
rich
36 changes: 36 additions & 0 deletions tests/plugins/test_setuptools_pep621.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import pytest
import tomli

from ini2toml.plugins.profile_independent_tasks import remove_empty_table_headers
from ini2toml.plugins.setuptools_pep621 import Directive, SetuptoolsPEP621, activate
Expand Down Expand Up @@ -593,6 +594,41 @@ def test_handle_dynamic(plugin, parse, convert):
assert convert(doc).strip() == expected_dynamic.strip()


# ----

example_dynamic_dependencies = """\
[options]
install_requires = file: requirements.txt
[options.extras_require]
dev = file: dev-requirements.txt
"""

expected_dynamic_dependencies = """\
[project]
dynamic = ["version", "dependencies", "optional-dependencies"]
[tool.setuptools.dynamic]
dependencies = {file = ["requirements.txt"]}
optional-dependencies.dev = {file = ["dev-requirements.txt"]}
"""


def test_dynamic_dependecies(plugin, parse, convert):
doc = parse(example_dynamic_dependencies)
print(doc)
print("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@")
doc = plugin.normalise_keys(doc)
doc = plugin.pep621_transform(doc)
print("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@")
print(doc)
converted = tomli.loads(convert(doc).strip())
converted.pop("build-system", None)
converted["tool"]["setuptools"].pop("include-package-data", None)
print("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@")
print(converted)
assert converted == tomli.loads(expected_dynamic_dependencies)


# ----

expected_empty = """\
Expand Down
17 changes: 15 additions & 2 deletions tests/test_examples.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import pytest
import tomli
from validate_pyproject.api import Validator
from validate_pyproject.errors import ValidationError

from ini2toml import cli
from ini2toml.drivers import configparser, full_toml, lite_toml
Expand Down Expand Up @@ -53,7 +54,13 @@ def test_examples_api(original, expected, validate):
# Make sure they can be parsed
dict_equivalent = tomli.loads(out)
assert dict_equivalent == tomli.loads(expected_text)
assert validate(remove_deprecated(dict_equivalent)) is not None
try:
assert validate(remove_deprecated(dict_equivalent)) is not None
except ValidationError as ex:
if "optional-dependencies" not in str(ex):
# For the time being both `setuptools` and `validate-pyproject`
# are not prepared to deal with mixed dynamic dependencies
raise


COMMENT_LINE = re.compile(r"^\s*#[^\n]*\n", re.M)
Expand Down Expand Up @@ -81,7 +88,13 @@ def test_examples_api_lite(original, expected, validate):
# At least the Python-equivalents should be the same when parsing
dict_equivalent = tomli.loads(out)
assert dict_equivalent == tomli.loads(expected_text)
assert validate(remove_deprecated(dict_equivalent)) is not None
try:
assert validate(remove_deprecated(dict_equivalent)) is not None
except ValidationError as ex:
if "optional-dependencies" not in str(ex):
# For the time being both `setuptools` and `validate-pyproject`
# are not prepared to deal with mixed dynamic dependencies
raise

without_comments = COMMENT_LINE.sub("", expected_text)
without_comments = INLINE_COMMENT.sub("", without_comments)
Expand Down

0 comments on commit 9120a9f

Please sign in to comment.