Skip to content

Commit

Permalink
Add PEP 621 as a source for modules
Browse files Browse the repository at this point in the history
Tom Halstead authored Jun 30, 2022

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
1 parent c946232 commit c43dc96
Showing 3 changed files with 84 additions and 1 deletion.
1 change: 1 addition & 0 deletions README.rst
Original file line number Diff line number Diff line change
@@ -16,6 +16,7 @@ In order to collect project's dependencies, this checker evaluates Python code f
``setup.py`` file stored in the project's root directory. Code evaluation is done with the `eval()
<https://docs.python.org/3/library/functions.html#eval>`_ function. As a fall-back method, this
checker also tries to load dependencies, in order, from the ``setup.cfg``, the ``pyproject.toml``
file from the `PEP 621 <https://peps.python.org/pep-0621/>`_ project section, the ``pyproject.toml``
file from the `poetry <https://python-poetry.org/>`_ tool section, or from the
``requirements.txt`` text file in the project's root directory.

21 changes: 20 additions & 1 deletion src/flake8_requirements/checker.py
Original file line number Diff line number Diff line change
@@ -18,7 +18,7 @@
from .modules import STDLIB_PY3

# NOTE: Changing this number will alter package version as well.
__version__ = "1.5.5"
__version__ = "1.6.0"
__license__ = "MIT"

LOG = getLogger('flake8.plugin.requirements')
@@ -536,6 +536,22 @@ def get_pyproject_toml(cls):
LOG.debug("Couldn't load project setup: %s", e)
return {}

@classmethod
def get_pyproject_toml_pep621(cls):
"""Try to get PEP 621 metadata."""
cfg_pep518 = cls.get_pyproject_toml()
return cfg_pep518.get('project', {})

def get_pyproject_toml_pep621_requirements(self):
"""Try to get PEP 621 metadata requirements."""
pep621 = self.get_pyproject_toml_pep621()
requirements = []
requirements.extend(parse_requirements(
pep621.get("dependencies", ())))
for r in pep621.get("optional-dependencies", {}).values():
requirements.extend(parse_requirements(r))
return requirements

@classmethod
def get_pyproject_toml_poetry(cls):
"""Try to get poetry configuration."""
@@ -627,6 +643,7 @@ def get_mods_1st_party(cls):
modules = [project2module(
cls.get_setup_py().keywords.get('name') or
cls.get_setup_cfg().get('metadata', 'name') or
cls.get_pyproject_toml_pep621().get('name') or
cls.get_pyproject_toml_poetry().get('name') or
"")]
if modules[0] in cls.known_modules:
@@ -645,6 +662,8 @@ def get_mods_3rd_party_requirements(self):
self.get_setup_py_requirements() or
# Check setup configuration file for requirements.
self.get_setup_cfg_requirements() or
# Check PEP 621 metadata for requirements.
self.get_pyproject_toml_pep621_requirements() or
# Check project configuration for requirements.
self.get_pyproject_toml_poetry_requirements() or
# Fall-back to requirements.txt in our root directory.
63 changes: 63 additions & 0 deletions test/test_pep621.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import unittest

from flake8_requirements.checker import Flake8Checker
from flake8_requirements.checker import ModuleSet
from flake8_requirements.checker import memoize

try:
from unittest import mock
builtins_open = 'builtins.open'
except ImportError:
import mock
builtins_open = '__builtin__.open'


class Pep621TestCase(unittest.TestCase):

content = """
[project]
name="test"
dependencies=["tools==1.0"]
[project.optional-dependencies]
dev = ["dev-tools==1.0"]
"""

def setUp(self):
memoize.mem = {}

def test_get_pyproject_toml_pep621(self):
with mock.patch(builtins_open, mock.mock_open(read_data=self.content)):
pep621 = Flake8Checker.get_pyproject_toml_pep621()
expected = {
"name": "test",
"dependencies": ["tools==1.0"],
"optional-dependencies": {
"dev": ["dev-tools==1.0"]
},
}
self.assertDictEqual(pep621, expected)

def test_1st_party(self):
with mock.patch(builtins_open, mock.mock_open()) as m:
m.side_effect = (
IOError("No such file or directory: 'setup.py'"),
IOError("No such file or directory: 'setup.cfg'"),
mock.mock_open(read_data=self.content).return_value,
)

checker = Flake8Checker(None, None)
mods = checker.get_mods_1st_party()
self.assertEqual(mods, ModuleSet({"test": {}}))

def test_3rd_party(self):
with mock.patch(builtins_open, mock.mock_open()) as m:
m.side_effect = (
IOError("No such file or directory: 'setup.py'"),
IOError("No such file or directory: 'setup.cfg'"),
mock.mock_open(read_data=self.content).return_value,
)

checker = Flake8Checker(None, None)
mods = checker.get_mods_3rd_party()
self.assertEqual(mods, ModuleSet({"tools": {}, "dev_tools": {}}))

0 comments on commit c43dc96

Please sign in to comment.