Skip to content

Commit 102ef9b

Browse files
authored
Merge pull request #120 from bennati/pr-116-2
Mock the actual setup provider defined in setup.py
2 parents c22ccf3 + 636bb5a commit 102ef9b

File tree

6 files changed

+292
-12
lines changed

6 files changed

+292
-12
lines changed

src/python_inspector/setup_py_live_eval.py

Lines changed: 62 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,17 @@
99
#
1010
"""Generate requirements from `setup.py` and `requirements-devel.txt`."""
1111

12+
import ast
1213
import os
13-
import re
1414
import sys
1515

1616
try:
1717
import configparser
1818
except ImportError: # pragma: no cover
1919
import ConfigParser as configparser
2020

21+
import distutils.core
22+
2123
import mock
2224
import setuptools
2325
from commoncode.command import pushd
@@ -54,11 +56,65 @@ def iter_requirements(level, extras, setup_file):
5456
setup_requires = {}
5557
# change directory to setup.py path
5658
with pushd(os.path.dirname(setup_file)):
57-
with mock.patch.object(setuptools, "setup") as mock_setup:
58-
sys.path.append(os.path.dirname(setup_file))
59-
g = {"__file__": setup_file, "__name__": "__main__"}
60-
with open(setup_file) as sf:
61-
exec(sf.read(), g)
59+
with open(setup_file) as sf:
60+
file_contents = sf.read()
61+
node = ast.parse(file_contents)
62+
asnames = {}
63+
imports = []
64+
for elem in ast.walk(node):
65+
# save the asnames to parse aliases later
66+
if isinstance(elem, ast.Import):
67+
for n in elem.names:
68+
asnames[(n.asname if n.asname is not None else n.name)] = n.name
69+
for elem in ast.walk(node):
70+
# for function imports, e.g. from setuptools import setup; setup()
71+
if isinstance(elem, ast.ImportFrom) and "setup" in [e.name for e in elem.names]:
72+
imports.append(elem.module)
73+
# for module imports, e.g. import setuptools; setuptools.setup(...)
74+
elif (
75+
isinstance(elem, ast.Expr)
76+
and isinstance(elem.value, ast.Call)
77+
and isinstance(elem.value.func, ast.Attribute)
78+
and isinstance(elem.value.func.value, ast.Name)
79+
and elem.value.func.attr == "setup"
80+
):
81+
name = elem.value.func.value.id
82+
if name in asnames.keys():
83+
name = asnames[name]
84+
imports.append(name)
85+
# for module imports, e.g. import disttools.core; disttools.core.setup(...)
86+
elif (
87+
isinstance(elem, ast.Expr)
88+
and isinstance(elem.value, ast.Call)
89+
and isinstance(elem.value.func, ast.Attribute)
90+
and isinstance(elem.value.func.value, ast.Attribute)
91+
and elem.value.func.attr == "setup"
92+
):
93+
name = (
94+
str(elem.value.func.value.value.id) + "." + str(elem.value.func.value.attr)
95+
)
96+
if name in asnames.keys():
97+
name = asnames[name]
98+
imports.append(name)
99+
setup_providers = [i for i in imports if i in ["distutils.core", "setuptools"]]
100+
if len(setup_providers) == 0:
101+
print(
102+
f"Warning: unable to recognize setup provider in {setup_file}: "
103+
"defaulting to 'distutils.core'."
104+
)
105+
setup_provider = "distutils.core"
106+
elif len(setup_providers) == 1:
107+
setup_provider = setup_providers[0]
108+
else:
109+
print(
110+
f"Warning: ambiguous setup provider in {setup_file}: candidates are {setup_providers}"
111+
"defaulting to 'distutils.core'."
112+
)
113+
setup_provider = "distutils.core"
114+
with mock.patch.object(eval(setup_provider), "setup") as mock_setup:
115+
sys.path.append(os.path.dirname(setup_file))
116+
g = {"__file__": setup_file, "__name__": "__main__"}
117+
exec(file_contents, g)
62118
sys.path.pop()
63119
# removing the assertion `assert g["setup"]`` since this is not true for all cases
64120
# for example when setuptools.setup() is called instead of setup()
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
"""
2+
Copyright 2018 Matthew Aynalem
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
"""
16+
import distutils.core as dts, os
17+
from setuptools import find_packages
18+
19+
dts.setup(
20+
name='packer.py',
21+
version='0.3.0',
22+
author='Matthew Aynalem',
23+
author_email='[email protected]',
24+
packages=['packerpy'],
25+
url='https://github.com/mayn/packer.py',
26+
license='Apache License 2.0',
27+
description='packer.py - python library to run hashicorp packer CLI commands',
28+
keywords="hashicorp packer",
29+
install_requires=[
30+
],
31+
classifiers=[
32+
'License :: OSI Approved :: Apache Software License',
33+
'Programming Language :: Python :: 2',
34+
'Programming Language :: Python :: 2.7',
35+
'Programming Language :: Python :: 3',
36+
'Programming Language :: Python :: 3.4',
37+
'Programming Language :: Python :: 3.5',
38+
'Programming Language :: Python :: 3.6',
39+
],
40+
)
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
"""
2+
Copyright 2018 Matthew Aynalem
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
"""
16+
import distutils.core
17+
from setuptools import find_packages
18+
19+
distutils.core.setup(
20+
name='packer.py',
21+
version='0.3.0',
22+
author='Matthew Aynalem',
23+
author_email='[email protected]',
24+
packages=['packerpy'],
25+
url='https://github.com/mayn/packer.py',
26+
license='Apache License 2.0',
27+
description='packer.py - python library to run hashicorp packer CLI commands',
28+
keywords="hashicorp packer",
29+
install_requires=[
30+
],
31+
classifiers=[
32+
'License :: OSI Approved :: Apache Software License',
33+
'Programming Language :: Python :: 2',
34+
'Programming Language :: Python :: 2.7',
35+
'Programming Language :: Python :: 3',
36+
'Programming Language :: Python :: 3.4',
37+
'Programming Language :: Python :: 3.5',
38+
'Programming Language :: Python :: 3.6',
39+
],
40+
)

tests/data/setup-distutils.txt

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
"""
2+
Copyright 2018 Matthew Aynalem
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
"""
16+
from distutils.core import setup
17+
from setuptools import find_packages
18+
19+
setup(
20+
name='packer.py',
21+
version='0.3.0',
22+
author='Matthew Aynalem',
23+
author_email='[email protected]',
24+
packages=['packerpy'],
25+
url='https://github.com/mayn/packer.py',
26+
license='Apache License 2.0',
27+
description='packer.py - python library to run hashicorp packer CLI commands',
28+
keywords="hashicorp packer",
29+
install_requires=[
30+
],
31+
classifiers=[
32+
'License :: OSI Approved :: Apache Software License',
33+
'Programming Language :: Python :: 2',
34+
'Programming Language :: Python :: 2.7',
35+
'Programming Language :: Python :: 3',
36+
'Programming Language :: Python :: 3.4',
37+
'Programming Language :: Python :: 3.5',
38+
'Programming Language :: Python :: 3.6',
39+
],
40+
)

tests/data/setup-qualifiedfct.txt

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# -*- coding: utf-8 -*-
2+
#
3+
# This file is part of Requirements-Builder
4+
# Copyright (C) 2015, 2016, 2017, 2018, 2019, 2020 CERN.
5+
#
6+
# Requirements-Builder is free software; you can redistribute it and/or
7+
# modify it under the terms of the Revised BSD License; see LICENSE
8+
# file for more details.
9+
#
10+
"""Build requirements files from setup.py requirements."""
11+
12+
import os
13+
14+
import setuptools
15+
16+
# Get the version string. Cannot be done with import!
17+
g = {}
18+
19+
install_requires = [
20+
'click>=6.1.0',
21+
'mock>=1.3.0',
22+
]
23+
24+
tests_require = [
25+
'check-manifest>=0.25',
26+
'coverage>=4.0',
27+
'isort>=4.0.0',
28+
'pydocstyle>=1.0.0',
29+
'pytest-cache>=1.0',
30+
'pytest-cov>=2.0.0',
31+
'pytest-pep8>=1.0.6',
32+
'pytest>=2.8.0',
33+
]
34+
35+
extras_require = {
36+
'docs': [
37+
'Sphinx>=2.4',
38+
],
39+
'tests': tests_require,
40+
}
41+
42+
extras_require['all'] = extras_require['tests'] + extras_require['docs']
43+
44+
setup_requires = ['pytest-runner>=2.6.2', ]
45+
46+
setuptools.setup(
47+
name='requirements-builder',
48+
version="0.1.0",
49+
description=__doc__,
50+
long_description='\n\n',
51+
author="Invenio Collaboration",
52+
author_email='[email protected]',
53+
url='https://github.com/inveniosoftware/requirements-builder',
54+
entry_points={
55+
'console_scripts':
56+
["requirements-builder = requirements_builder.cli:cli"]
57+
},
58+
packages=['requirements_builder', ],
59+
include_package_data=True,
60+
extras_require=extras_require,
61+
install_requires=install_requires,
62+
setup_requires=setup_requires,
63+
tests_require=tests_require,
64+
license='BSD',
65+
zip_safe=False,
66+
keywords='requirements-builder',
67+
classifiers=[
68+
'Intended Audience :: Developers',
69+
'License :: OSI Approved :: BSD License',
70+
'Natural Language :: English',
71+
'Programming Language :: Python :: 3',
72+
'Programming Language :: Python :: 3.5',
73+
'Programming Language :: Python :: 3.6',
74+
'Programming Language :: Python :: 3.7',
75+
'Programming Language :: Python :: 3.8',
76+
],
77+
)

tests/test_setup_py_live_eval.py

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,24 +8,51 @@
88
# file for more details.
99
#
1010
"""Tests for `requirements-builder` module."""
11-
1211
from os.path import abspath
1312
from os.path import dirname
1413
from os.path import join
1514

15+
import pytest
16+
1617
from python_inspector.setup_py_live_eval import iter_requirements
1718

1819
REQ = abspath(join(dirname(__file__), "./data/requirements.devel.txt"))
19-
SETUP = abspath(join(dirname(__file__), "./data/setup.txt"))
2020

2121

22-
def test_iter_requirements_with_setup_py():
22+
@pytest.mark.parametrize(
23+
"setup_py",
24+
[
25+
abspath(join(dirname(__file__), "./data/setup.txt")),
26+
abspath(join(dirname(__file__), "./data/setup-qualifiedfct.txt")),
27+
],
28+
)
29+
def test_iter_requirements_with_setup_py(setup_py):
2330
"""Test requirements-builder."""
2431
# Min
25-
assert list(iter_requirements("min", [], SETUP)) == ["click==6.1.0", "mock==1.3.0"]
32+
assert list(iter_requirements("min", [], setup_py)) == ["click==6.1.0", "mock==1.3.0"]
33+
34+
# PyPI
35+
assert list(iter_requirements("pypi", [], setup_py)) == ["click>=6.1.0", "mock>=1.3.0"]
36+
37+
# Dev
38+
assert list(iter_requirements("dev", [], setup_py)) == ["click>=6.1.0", "mock>=1.3.0"]
39+
40+
41+
@pytest.mark.parametrize(
42+
"setup_py",
43+
[
44+
abspath(join(dirname(__file__), "./data/setup-distutils.txt")),
45+
abspath(join(dirname(__file__), "./data/setup-distutils-qualifiedfct.txt")),
46+
abspath(join(dirname(__file__), "./data/setup-distutils-asnames.txt")),
47+
],
48+
)
49+
def test_iter_requirements_with_setup_py_noreqs(setup_py):
50+
"""Test against setup.py files which import setup in different ways"""
51+
# Min
52+
assert list(iter_requirements("min", [], setup_py)) == []
2653

2754
# PyPI
28-
assert list(iter_requirements("pypi", [], SETUP)) == ["click>=6.1.0", "mock>=1.3.0"]
55+
assert list(iter_requirements("pypi", [], setup_py)) == []
2956

3057
# Dev
31-
assert list(iter_requirements("dev", [], SETUP)) == ["click>=6.1.0", "mock>=1.3.0"]
58+
assert list(iter_requirements("dev", [], setup_py)) == []

0 commit comments

Comments
 (0)