setup_reader.py (poetry-1.1.15) | : | setup_reader.py (poetry-1.2.0) | ||
---|---|---|---|---|
from __future__ import annotations | ||||
import ast | import ast | |||
from configparser import ConfigParser | ||||
from pathlib import Path | ||||
from typing import Any | from typing import Any | |||
from typing import Dict | ||||
from typing import Iterable | ||||
from typing import List | ||||
from typing import Optional | ||||
from typing import Tuple | ||||
from typing import Union | ||||
from poetry.core.semver import Version | ||||
from ._compat import PY35 | ||||
from ._compat import Path | ||||
from ._compat import basestring | ||||
try: | ||||
from configparser import ConfigParser | ||||
except ImportError: | ||||
from ConfigParser import ConfigParser | ||||
class SetupReader(object): | from poetry.core.semver.version import Version | |||
class SetupReader: | ||||
""" | """ | |||
Class that reads a setup.py file without executing it. | Class that reads a setup.py file without executing it. | |||
""" | """ | |||
DEFAULT = { | DEFAULT: dict[str, Any] = { | |||
"name": None, | "name": None, | |||
"version": None, | "version": None, | |||
"install_requires": [], | "install_requires": [], | |||
"extras_require": {}, | "extras_require": {}, | |||
"python_requires": None, | "python_requires": None, | |||
} | } | |||
FILES = ["setup.py", "setup.cfg"] | FILES = ["setup.py", "setup.cfg"] | |||
@classmethod | @classmethod | |||
def read_from_directory( | def read_from_directory(cls, directory: str | Path) -> dict[str, Any]: | |||
cls, directory | if isinstance(directory, str): | |||
): # type: (Union[basestring, Path]) -> Dict[str, Union[List, Dict]] | ||||
if isinstance(directory, basestring): | ||||
directory = Path(directory) | directory = Path(directory) | |||
result = cls.DEFAULT.copy() | result = cls.DEFAULT.copy() | |||
for filename in cls.FILES: | for filename in cls.FILES: | |||
filepath = directory / filename | filepath = directory / filename | |||
if not filepath.exists(): | if not filepath.exists(): | |||
continue | continue | |||
new_result = getattr(cls(), "read_{}".format(filename.replace(".", " | read_file_func = getattr(cls(), "read_" + filename.replace(".", "_") | |||
_")))( | ) | |||
filepath | new_result = read_file_func(filepath) | |||
) | ||||
for key in result.keys(): | for key in result.keys(): | |||
if new_result[key]: | if new_result[key]: | |||
result[key] = new_result[key] | result[key] = new_result[key] | |||
return result | return result | |||
@classmethod | def read_setup_py(self, filepath: str | Path) -> dict[str, Any]: | |||
def _is_empty_result(cls, result): # type: (Dict[str, Any]) -> bool | if isinstance(filepath, str): | |||
return ( | ||||
not result["install_requires"] | ||||
and not result["extras_require"] | ||||
and not result["python_requires"] | ||||
) | ||||
def read_setup_py( | ||||
self, filepath | ||||
): # type: (Union[basestring, Path]) -> Dict[str, Union[List, Dict]] | ||||
if not PY35: | ||||
return self.DEFAULT | ||||
if isinstance(filepath, basestring): | ||||
filepath = Path(filepath) | filepath = Path(filepath) | |||
with filepath.open(encoding="utf-8") as f: | with filepath.open(encoding="utf-8") as f: | |||
content = f.read() | content = f.read() | |||
result = {} | result: dict[str, Any] = {} | |||
body = ast.parse(content).body | body = ast.parse(content).body | |||
setup_call, body = self._find_setup_call(body) | setup_call = self._find_setup_call(body) | |||
if not setup_call: | if setup_call is None: | |||
return self.DEFAULT | return self.DEFAULT | |||
# Inspecting keyword arguments | # Inspecting keyword arguments | |||
result["name"] = self._find_single_string(setup_call, body, "name") | call, body = setup_call | |||
result["version"] = self._find_single_string(setup_call, body, "version" | result["name"] = self._find_single_string(call, body, "name") | |||
) | result["version"] = self._find_single_string(call, body, "version") | |||
result["install_requires"] = self._find_install_requires(setup_call, bod | result["install_requires"] = self._find_install_requires(call, body) | |||
y) | result["extras_require"] = self._find_extras_require(call, body) | |||
result["extras_require"] = self._find_extras_require(setup_call, body) | ||||
result["python_requires"] = self._find_single_string( | result["python_requires"] = self._find_single_string( | |||
setup_call, body, "python_requires" | call, body, "python_requires" | |||
) | ) | |||
return result | return result | |||
def read_setup_cfg( | def read_setup_cfg(self, filepath: str | Path) -> dict[str, Any]: | |||
self, filepath | ||||
): # type: (Union[basestring, Path]) -> Dict[str, Union[List, Dict]] | ||||
parser = ConfigParser() | parser = ConfigParser() | |||
parser.read(str(filepath)) | parser.read(str(filepath)) | |||
name = None | name = None | |||
version = None | version = None | |||
if parser.has_option("metadata", "name"): | if parser.has_option("metadata", "name"): | |||
name = parser.get("metadata", "name") | name = parser.get("metadata", "name") | |||
if parser.has_option("metadata", "version"): | if parser.has_option("metadata", "version"): | |||
version = Version.parse(parser.get("metadata", "version")).text | version = Version.parse(parser.get("metadata", "version")).text | |||
install_requires = [] | install_requires = [] | |||
extras_require = {} | extras_require: dict[str, list[str]] = {} | |||
python_requires = None | python_requires = None | |||
if parser.has_section("options"): | if parser.has_section("options"): | |||
if parser.has_option("options", "install_requires"): | if parser.has_option("options", "install_requires"): | |||
for dep in parser.get("options", "install_requires").split("\n") : | for dep in parser.get("options", "install_requires").split("\n") : | |||
dep = dep.strip() | dep = dep.strip() | |||
if not dep: | if not dep: | |||
continue | continue | |||
install_requires.append(dep) | install_requires.append(dep) | |||
skipping to change at line 149 | skipping to change at line 121 | |||
return { | return { | |||
"name": name, | "name": name, | |||
"version": version, | "version": version, | |||
"install_requires": install_requires, | "install_requires": install_requires, | |||
"extras_require": extras_require, | "extras_require": extras_require, | |||
"python_requires": python_requires, | "python_requires": python_requires, | |||
} | } | |||
def _find_setup_call( | def _find_setup_call( | |||
self, elements | self, elements: list[ast.stmt] | |||
): # type: (List[Any]) -> Tuple[Optional[ast.Call], Optional[List[Any]]] | ) -> tuple[ast.Call, list[ast.stmt]] | None: | |||
funcdefs = [] | funcdefs: list[ast.stmt] = [] | |||
for i, element in enumerate(elements): | for i, element in enumerate(elements): | |||
if isinstance(element, ast.If) and i == len(elements) - 1: | if isinstance(element, ast.If) and i == len(elements) - 1: | |||
# Checking if the last element is an if statement | # Checking if the last element is an if statement | |||
# and if it is 'if __name__ == "__main__"' which | # and if it is 'if __name__ == "__main__"' which | |||
# could contain the call to setup() | # could contain the call to setup() | |||
test = element.test | test = element.test | |||
if not isinstance(test, ast.Compare): | if not isinstance(test, ast.Compare): | |||
continue | continue | |||
left = test.left | left = test.left | |||
if not isinstance(left, ast.Name): | if not isinstance(left, ast.Name): | |||
continue | continue | |||
if left.id != "__name__": | if left.id != "__name__": | |||
continue | continue | |||
setup_call, body = self._find_sub_setup_call([element]) | setup_call = self._find_sub_setup_call([element]) | |||
if not setup_call: | if setup_call is None: | |||
continue | continue | |||
return setup_call, body + elements | call, body = setup_call | |||
return call, body + elements | ||||
if not isinstance(element, ast.Expr): | if not isinstance(element, ast.Expr): | |||
if isinstance(element, ast.FunctionDef): | if isinstance(element, ast.FunctionDef): | |||
funcdefs.append(element) | funcdefs.append(element) | |||
continue | continue | |||
value = element.value | value = element.value | |||
if not isinstance(value, ast.Call): | if not isinstance(value, ast.Call): | |||
continue | continue | |||
func = value.func | func = value.func | |||
if not (isinstance(func, ast.Name) and func.id == "setup") and not ( | if not (isinstance(func, ast.Name) and func.id == "setup") and not ( | |||
isinstance(func, ast.Attribute) | isinstance(func, ast.Attribute) | |||
and hasattr(func.value, "id") | and getattr(func.value, "id", None) == "setuptools" | |||
and func.value.id == "setuptools" | ||||
and func.attr == "setup" | and func.attr == "setup" | |||
): | ): | |||
continue | continue | |||
return value, elements | return value, elements | |||
# Nothing, we inspect the function definitions | # Nothing, we inspect the function definitions | |||
return self._find_sub_setup_call(funcdefs) | return self._find_sub_setup_call(funcdefs) | |||
def _find_sub_setup_call( | def _find_sub_setup_call( | |||
self, elements | self, elements: list[ast.stmt] | |||
): # type: (List[Any]) -> Tuple[Optional[ast.Call], Optional[List[Any]]] | ) -> tuple[ast.Call, list[ast.stmt]] | None: | |||
for element in elements: | for element in elements: | |||
if not isinstance(element, (ast.FunctionDef, ast.If)): | if not isinstance(element, (ast.FunctionDef, ast.If)): | |||
continue | continue | |||
setup_call = self._find_setup_call(element.body) | setup_call = self._find_setup_call(element.body) | |||
if setup_call != (None, None): | if setup_call is not None: | |||
setup_call, body = setup_call | sub_call, body = setup_call | |||
body = elements + body | body = elements + body | |||
return setup_call, body | return sub_call, body | |||
return None, None | return None | |||
def _find_install_requires( | def _find_install_requires(self, call: ast.Call, body: list[ast.stmt]) -> li | |||
self, call, body | st[str]: | |||
): # type: (ast.Call, Iterable[Any]) -> List[str] | install_requires: list[str] = [] | |||
install_requires = [] | ||||
value = self._find_in_call(call, "install_requires") | value = self._find_in_call(call, "install_requires") | |||
if value is None: | if value is None: | |||
# Trying to find in kwargs | # Trying to find in kwargs | |||
kwargs = self._find_call_kwargs(call) | kwargs = self._find_call_kwargs(call) | |||
if kwargs is None or not isinstance(kwargs, ast.Name): | if kwargs is None or not isinstance(kwargs, ast.Name): | |||
return install_requires | return install_requires | |||
variable = self._find_variable_in_body(body, kwargs.id) | variable = self._find_variable_in_body(body, kwargs.id) | |||
if not isinstance(variable, (ast.Dict, ast.Call)): | if not isinstance(variable, (ast.Dict, ast.Call)): | |||
skipping to change at line 246 | skipping to change at line 217 | |||
value = self._find_in_call(variable, "install_requires") | value = self._find_in_call(variable, "install_requires") | |||
else: | else: | |||
value = self._find_in_dict(variable, "install_requires") | value = self._find_in_dict(variable, "install_requires") | |||
if value is None: | if value is None: | |||
return install_requires | return install_requires | |||
if isinstance(value, ast.List): | if isinstance(value, ast.List): | |||
for el in value.elts: | for el in value.elts: | |||
install_requires.append(el.s) | if isinstance(el, ast.Str): | |||
install_requires.append(el.s) | ||||
elif isinstance(value, ast.Name): | elif isinstance(value, ast.Name): | |||
variable = self._find_variable_in_body(body, value.id) | variable = self._find_variable_in_body(body, value.id) | |||
if variable is not None and isinstance(variable, ast.List): | if variable is not None and isinstance(variable, ast.List): | |||
for el in variable.elts: | for el in variable.elts: | |||
install_requires.append(el.s) | if isinstance(el, ast.Str): | |||
install_requires.append(el.s) | ||||
return install_requires | return install_requires | |||
def _find_extras_require( | def _find_extras_require( | |||
self, call, body | self, call: ast.Call, body: list[ast.stmt] | |||
): # type: (ast.Call, Iterable[Any]) -> Dict[str, List] | ) -> dict[str, list[str]]: | |||
extras_require = {} | extras_require: dict[str, list[str]] = {} | |||
value = self._find_in_call(call, "extras_require") | value = self._find_in_call(call, "extras_require") | |||
if value is None: | if value is None: | |||
# Trying to find in kwargs | # Trying to find in kwargs | |||
kwargs = self._find_call_kwargs(call) | kwargs = self._find_call_kwargs(call) | |||
if kwargs is None or not isinstance(kwargs, ast.Name): | if kwargs is None or not isinstance(kwargs, ast.Name): | |||
return extras_require | return extras_require | |||
variable = self._find_variable_in_body(body, kwargs.id) | variable = self._find_variable_in_body(body, kwargs.id) | |||
if not isinstance(variable, (ast.Dict, ast.Call)): | if not isinstance(variable, (ast.Dict, ast.Call)): | |||
skipping to change at line 287 | skipping to change at line 260 | |||
return extras_require | return extras_require | |||
value = self._find_in_call(variable, "extras_require") | value = self._find_in_call(variable, "extras_require") | |||
else: | else: | |||
value = self._find_in_dict(variable, "extras_require") | value = self._find_in_dict(variable, "extras_require") | |||
if value is None: | if value is None: | |||
return extras_require | return extras_require | |||
if isinstance(value, ast.Dict): | if isinstance(value, ast.Dict): | |||
val: ast.expr | None | ||||
for key, val in zip(value.keys, value.values): | for key, val in zip(value.keys, value.values): | |||
if not isinstance(key, ast.Str): | ||||
continue | ||||
if isinstance(val, ast.Name): | if isinstance(val, ast.Name): | |||
val = self._find_variable_in_body(body, val.id) | val = self._find_variable_in_body(body, val.id) | |||
if isinstance(val, ast.List): | if isinstance(val, ast.List): | |||
extras_require[key.s] = [e.s for e in val.elts] | extras_require[key.s] = [ | |||
e.s for e in val.elts if isinstance(e, ast.Str) | ||||
] | ||||
elif isinstance(value, ast.Name): | elif isinstance(value, ast.Name): | |||
variable = self._find_variable_in_body(body, value.id) | variable = self._find_variable_in_body(body, value.id) | |||
if variable is None or not isinstance(variable, ast.Dict): | if variable is None or not isinstance(variable, ast.Dict): | |||
return extras_require | return extras_require | |||
for key, val in zip(variable.keys, variable.values): | for key, val in zip(variable.keys, variable.values): | |||
if not isinstance(key, ast.Str): | ||||
continue | ||||
if isinstance(val, ast.Name): | if isinstance(val, ast.Name): | |||
val = self._find_variable_in_body(body, val.id) | val = self._find_variable_in_body(body, val.id) | |||
if isinstance(val, ast.List): | if isinstance(val, ast.List): | |||
extras_require[key.s] = [e.s for e in val.elts] | extras_require[key.s] = [ | |||
e.s for e in val.elts if isinstance(e, ast.Str) | ||||
] | ||||
return extras_require | return extras_require | |||
def _find_single_string( | def _find_single_string( | |||
self, call, body, name | self, call: ast.Call, body: list[ast.stmt], name: str | |||
): # type: (ast.Call, List[Any], str) -> Optional[str] | ) -> str | None: | |||
value = self._find_in_call(call, name) | value = self._find_in_call(call, name) | |||
if value is None: | if value is None: | |||
# Trying to find in kwargs | # Trying to find in kwargs | |||
kwargs = self._find_call_kwargs(call) | kwargs = self._find_call_kwargs(call) | |||
if kwargs is None or not isinstance(kwargs, ast.Name): | if kwargs is None or not isinstance(kwargs, ast.Name): | |||
return | return None | |||
variable = self._find_variable_in_body(body, kwargs.id) | variable = self._find_variable_in_body(body, kwargs.id) | |||
if not isinstance(variable, (ast.Dict, ast.Call)): | if not isinstance(variable, (ast.Dict, ast.Call)): | |||
return | return None | |||
if isinstance(variable, ast.Call): | if isinstance(variable, ast.Call): | |||
if not isinstance(variable.func, ast.Name): | if not isinstance(variable.func, ast.Name): | |||
return | return None | |||
if variable.func.id != "dict": | if variable.func.id != "dict": | |||
return | return None | |||
value = self._find_in_call(variable, name) | value = self._find_in_call(variable, name) | |||
else: | else: | |||
value = self._find_in_dict(variable, name) | value = self._find_in_dict(variable, name) | |||
if value is None: | if value is None: | |||
return | return None | |||
if isinstance(value, ast.Str): | if isinstance(value, ast.Str): | |||
return value.s | return value.s | |||
elif isinstance(value, ast.Name): | elif isinstance(value, ast.Name): | |||
variable = self._find_variable_in_body(body, value.id) | variable = self._find_variable_in_body(body, value.id) | |||
if variable is not None and isinstance(variable, ast.Str): | if variable is not None and isinstance(variable, ast.Str): | |||
return variable.s | return variable.s | |||
def _find_in_call(self, call, name): # type: (ast.Call, str) -> Optional[An | return None | |||
y] | ||||
def _find_in_call(self, call: ast.Call, name: str) -> Any | None: | ||||
for keyword in call.keywords: | for keyword in call.keywords: | |||
if keyword.arg == name: | if keyword.arg == name: | |||
return keyword.value | return keyword.value | |||
return None | ||||
def _find_call_kwargs(self, call): # type: (ast.Call) -> Optional[Any] | def _find_call_kwargs(self, call: ast.Call) -> Any | None: | |||
kwargs = None | kwargs = None | |||
for keyword in call.keywords: | for keyword in call.keywords: | |||
if keyword.arg is None: | if keyword.arg is None: | |||
kwargs = keyword.value | kwargs = keyword.value | |||
return kwargs | return kwargs | |||
def _find_variable_in_body( | def _find_variable_in_body( | |||
self, body, name | self, body: list[ast.stmt], name: str | |||
): # type: (Iterable[Any], str) -> Optional[Any] | ) -> ast.expr | None: | |||
found = None | ||||
for elem in body: | for elem in body: | |||
if found: | ||||
break | ||||
if not isinstance(elem, ast.Assign): | if not isinstance(elem, ast.Assign): | |||
continue | continue | |||
for target in elem.targets: | for target in elem.targets: | |||
if not isinstance(target, ast.Name): | if not isinstance(target, ast.Name): | |||
continue | continue | |||
if target.id == name: | if target.id == name: | |||
return elem.value | return elem.value | |||
def _find_in_dict(self, dict_, name): # type: (ast.Call, str) -> Optional[A | return None | |||
ny] | ||||
def _find_in_dict(self, dict_: ast.Dict, name: str) -> ast.expr | None: | ||||
for key, val in zip(dict_.keys, dict_.values): | for key, val in zip(dict_.keys, dict_.values): | |||
if isinstance(key, ast.Str) and key.s == name: | if isinstance(key, ast.Str) and key.s == name: | |||
return val | return val | |||
return None | ||||
End of changes. 44 change blocks. | ||||
99 lines changed or deleted | 81 lines changed or added |