Skip to content

Commit

Permalink
Add PEP 621 as a source for modules
Browse files Browse the repository at this point in the history
  • Loading branch information
Tom Halstead authored Jun 30, 2022
1 parent c946232 commit c43dc96
Show file tree
Hide file tree
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
Expand Up @@ -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.

Expand Down
21 changes: 20 additions & 1 deletion src/flake8_requirements/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down Expand Up @@ -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."""
Expand Down Expand Up @@ -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:
Expand All @@ -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.
Expand Down
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.