Skip to content

Commit

Permalink
Fix AST parsing when repeated assignments occur
Browse files Browse the repository at this point in the history
- Additionally fix handling of `os.environ` based version numbers and
  other assignments
- Fixes #246

Signed-off-by: Dan Ryan <dan.ryan@canonical.com>
  • Loading branch information
techalchemy committed May 29, 2020
1 parent c8df5d7 commit 6477669
Show file tree
Hide file tree
Showing 5 changed files with 56 additions and 9 deletions.
1 change: 1 addition & 0 deletions news/246.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed an issue in the AST parser which caused failures when parsing ``setup.py`` files with assignments (e.g. ``variable = some_value``) to the same name more than once, followed by operations on those variables (e.g. ``new_value = variable + other_variable``).
20 changes: 11 additions & 9 deletions src/requirementslib/models/setup_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -729,14 +729,16 @@ def unmap_binops(self):
self.binOps_map[binop] = ast_unparse(binop, analyzer=self)

def match_assignment_str(self, match):
return next(
iter(k for k in self.assignments if getattr(k, "id", "") == match), None
)
matches = [k for k in self.assignments if getattr(k, "id", "") == match]
if matches:
return matches[-1]
return None

def match_assignment_name(self, match):
return next(
iter(k for k in self.assignments if getattr(k, "id", "") == match.id), None
)
matches = [k for k in self.assignments if getattr(k, "id", "") == match.id]
if matches:
return matches[-1]
return None

def generic_unparse(self, item):
if any(isinstance(item, k) for k in AST_BINOP_MAP.keys()):
Expand Down Expand Up @@ -771,7 +773,7 @@ def unparse_Subscript(self, item):
if isinstance(item.slice, ast.Index):
try:
unparsed = unparsed[self.unparse(item.slice.value)]
except KeyError:
except (KeyError, TypeError):
# not everything can be looked up before runtime
unparsed = item
return unparsed
Expand Down Expand Up @@ -838,7 +840,7 @@ def unparse_Compare(self, item):
if isinstance(item.left, ast.Attribute) or isinstance(item.left, ast.Str):
import importlib

left = unparse(item.left)
left = self.unparse(item.left)
if "." in left:
name, _, val = left.rpartition(".")
left = getattr(importlib.import_module(name), val, left)
Expand Down Expand Up @@ -1002,7 +1004,7 @@ def ast_unparse(item, initial_mapping=False, analyzer=None, recurse=True): # no
if isinstance(item.slice, ast.Index):
try:
unparsed = unparsed[unparse(item.slice.value)]
except KeyError:
except (KeyError, TypeError):
# not everything can be looked up before runtime
unparsed = item
elif any(isinstance(item, k) for k in AST_BINOP_MAP.keys()):
Expand Down
26 changes: 26 additions & 0 deletions tests/fixtures/setup_py/package_with_repeated_assignments/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import os

from setuptools import find_packages, setup

thisdir = os.path.abspath(os.path.dirname(__file__))
version = os.environ["PACKAGE_VERSION"]


def my_function(other_list):
entry = {"key": [{"matches": ["string 1", "string 2", "some_string"]}]}
matches = entry["key"][0]["matches"]
matches = [x for x in matches if "some_string" not in x]
entry["key"][0]["matches"] = matches + other_list


setup(
name="test_package_with_repeated_assignments",
version=version,
description="Test package with repeated assignments and version from environment",
long_description="This is a package",
install_requires=["six",],
package_dir={"": "src"},
packages=find_packages("src"),
include_package_data=True,
zip_safe=False,
)
18 changes: 18 additions & 0 deletions tests/unit/test_setup_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,24 @@ def test_ast_parser_handles_binops(setup_py_dir):
assert analyzer.parse_setup_function() == parsed


def test_ast_parser_handles_repeated_assignments(setup_py_dir):
target = setup_py_dir.joinpath(
"package_with_repeated_assignments/setup.py"
).as_posix()
parsed = ast_parse_setup_py(target)
analyzer = ast_parse_file(target)
assert parsed["name"] == "test_package_with_repeated_assignments"
assert isinstance(parsed["version"], str) is False
assert parsed["install_requires"] == ["six"]
analyzer_parsed = analyzer.parse_setup_function()
# the versions in this instance are AST objects as they come from
# os.environ and will need to be parsed downstream from here, so
# equality comparisons will fail
analyzer_parsed.pop("version")
parsed.pop("version")
assert analyzer_parsed == parsed


def test_setup_cfg_parser(setup_cfg_dir):
setup_path = setup_cfg_dir / "package_with_multiple_extras/setup.cfg"
if six.PY2:
Expand Down

0 comments on commit 6477669

Please sign in to comment.