From a11e589b6670005c80611232867db27ce54f2c70 Mon Sep 17 00:00:00 2001 From: Brandon LeBlanc Date: Fri, 6 Jan 2017 09:59:49 -0600 Subject: [PATCH] Implement the ability to parse setup.py requirements Should satisfy part of #137. --- pyup/requirements.py | 31 +++++++++++++++++++++++++++- tests/test_requirements.py | 42 +++++++++++++++++++++++++++++++++++++- 2 files changed, 71 insertions(+), 2 deletions(-) diff --git a/pyup/requirements.py b/pyup/requirements.py index 8e32f40..06e39b5 100644 --- a/pyup/requirements.py +++ b/pyup/requirements.py @@ -107,7 +107,8 @@ def __init__(self, path, content, sha=None): self._is_valid = None def __str__(self): - return "RequirementFile(path='{path}', sha='{sha}', content='{content}')".format( + return "{klass}(path='{path}', sha='{sha}', content='{content}')".format( + klass=self.__class__.__name__, path=self.path, content=self.content[:30] + "[truncated]" if len(self.content) > 30 else self.content, sha=self.sha @@ -229,6 +230,34 @@ def get_requirement_class(self): # pragma: no cover return Requirement +class SetupPyFile(RequirementFile): + def _parse(self): + self._requirements, self._other_files = [], [] + + Klass = self.get_requirement_class() + + def setup(*args, **kwargs): + reqs = kwargs.get('install_requires', []) + kwargs.get('tests_require', []) + + for num, line in enumerate(reqs): + req = Klass.parse(line, num) + + if req.package: + self._requirements.append(req) + + try: + module = __import__('setuptools') + except ImportError: + module = __import__('distutils.core') + + old_setup = module.setup + module.setup = setup + + exec(self.content, globals()) + + module.setup = old_setup + + class Requirement(object): def __init__(self, name, specs, hashCmp, line, lineno, extras): self.name = name diff --git a/tests/test_requirements.py b/tests/test_requirements.py index 4059938..e974d50 100644 --- a/tests/test_requirements.py +++ b/tests/test_requirements.py @@ -3,7 +3,7 @@ from unittest import TestCase from pyup.requirements import Requirement from mock import patch, PropertyMock, Mock -from pyup.requirements import RequirementFile, RequirementsBundle +from pyup.requirements import RequirementFile, RequirementsBundle, SetupPyFile from .test_package import package_factory from .test_pullrequest import pullrequest_factory import requests_mock @@ -891,3 +891,43 @@ def test_requirements(self): [r for r in reqs.requirements] ) + +class SetupPyFileTestCase(TestCase): + def setUp(self): + with open(os.path.join(os.path.dirname(__file__), '..', 'setup.py')) as f: + self.content = f.read() + + def test_parse(self): + assertion = AssertionError('setup function should not have been called') + requirements = [ + "requests", + "pygithub", + "click", + "tqdm", + "pyyaml", + "hashin", + ] + [ + "requests-mock", + "mock", + "flake8" + ] + + req = SetupPyFile('setup.py', self.content) + + def req_requirements(): + return sorted(r.name for r in req.requirements) + + try: + with patch('setuptools.setup', side_effect=assertion): + self.assertListEqual(req_requirements(), sorted(requirements)) + except ImportError: + pass + else: + return + + with patch('distutils.core.setup', side_effect=assertion): + self.assertListEqual(req_requirements(), sorted(requirements)) + + def test_str(self): + req = SetupPyFile('setup.py', self.content) + self.assertEqual(str(req), "SetupPyFile(path='setup.py', sha='None', content='#!/usr/bin/env python\n# -*- co[truncated]')")