diff --git a/src/poetry/inspection/info.py b/src/poetry/inspection/info.py index 557ebcf3a11..9c02895312b 100644 --- a/src/poetry/inspection/info.py +++ b/src/poetry/inspection/info.py @@ -27,7 +27,6 @@ from poetry.utils.helpers import extractall from poetry.utils.isolated_build import isolated_builder -from poetry.utils.setup_reader import SetupReader if TYPE_CHECKING: @@ -341,58 +340,6 @@ def _from_sdist_file(cls, path: Path) -> PackageInfo: return info.update(new_info) - @staticmethod - def has_setup_files(path: Path) -> bool: - return any((path / f).exists() for f in SetupReader.FILES) - - @classmethod - def from_setup_files(cls, path: Path) -> PackageInfo: - """ - Mechanism to parse package information from a `setup.[py|cfg]` file. This uses - the implementation at `poetry.utils.setup_reader.SetupReader` in order to parse - the file. This is not reliable for complex setup files and should only attempted - as a fallback. - - :param path: Path to `setup.py` file - """ - if not cls.has_setup_files(path): - raise PackageInfoError( - path, "No setup files (setup.py, setup.cfg) was found." - ) - - try: - result = SetupReader.read_from_directory(path) - except Exception as e: - raise PackageInfoError(path, e) - - python_requires = result["python_requires"] - if python_requires is None: - python_requires = "*" - - requires = "".join(dep + "\n" for dep in result["install_requires"]) - if result["extras_require"]: - requires += "\n" - - for extra_name, deps in result["extras_require"].items(): - requires += f"[{extra_name}]\n" - - for dep in deps: - requires += dep + "\n" - - requires += "\n" - - requirements = parse_requires(requires) - - info = cls( - name=result.get("name"), - version=result.get("version"), - summary=result.get("description", ""), - requires_dist=requirements, - requires_python=python_requires, - ) - - return info - @staticmethod def _find_dist_info(path: Path) -> Iterator[Path]: """ @@ -491,16 +438,13 @@ def _get_poetry_package(path: Path) -> ProjectPackage | None: return None @classmethod - def from_directory(cls, path: Path, disable_build: bool = False) -> PackageInfo: + def from_directory(cls, path: Path) -> PackageInfo: """ - Generate package information from a package source directory. If `disable_build` - is not `True` and introspection of all available metadata fails, the package is - attempted to be built in an isolated environment so as to generate required - metadata. + Generate package information from a package source directory. If introspection + of all available metadata fails, the package is attempted to be built in an + isolated environment so as to generate required metadata. :param path: Path to generate package information from. - :param disable_build: If not `True` and setup reader fails, PEP 517 isolated - build is attempted in order to gather metadata. """ project_package = cls._get_poetry_package(path) info: PackageInfo | None @@ -511,10 +455,7 @@ def from_directory(cls, path: Path, disable_build: bool = False) -> PackageInfo: if not info or info.requires_dist is None: try: - if disable_build: - info = cls.from_setup_files(path) - else: - info = get_pep517_metadata(path) + info = get_pep517_metadata(path) except PackageInfoError: if not info: raise @@ -591,11 +532,6 @@ def get_pep517_metadata(path: Path) -> PackageInfo: """ info = None - with contextlib.suppress(PackageInfoError): - info = PackageInfo.from_setup_files(path) - if all(x is not None for x in (info.version, info.name, info.requires_dist)): - return info - with tempfile.TemporaryDirectory() as dist: try: dest = Path(dist) diff --git a/src/poetry/utils/setup_reader.py b/src/poetry/utils/setup_reader.py deleted file mode 100644 index 3c233cd92c7..00000000000 --- a/src/poetry/utils/setup_reader.py +++ /dev/null @@ -1,383 +0,0 @@ -from __future__ import annotations - -import ast - -from configparser import ConfigParser -from typing import TYPE_CHECKING -from typing import Any -from typing import ClassVar - -from poetry.core.constraints.version import Version - - -if TYPE_CHECKING: - from pathlib import Path - - -class SetupReaderError(Exception): - pass - - -class SetupReader: - """ - Class that reads a setup.py file without executing it. - """ - - DEFAULT: ClassVar[dict[str, Any]] = { - "name": None, - "version": None, - "description": None, - "install_requires": [], - "extras_require": {}, - "python_requires": None, - } - - FILES: ClassVar[list[str]] = ["setup.py", "setup.cfg"] - - @classmethod - def read_from_directory(cls, directory: Path) -> dict[str, Any]: - result = cls.DEFAULT.copy() - for filename in cls.FILES: - filepath = directory / filename - if not filepath.exists(): - continue - - read_file_func = getattr(cls(), "read_" + filename.replace(".", "_")) - new_result = read_file_func(filepath) - - for key in result: - if new_result[key]: - result[key] = new_result[key] - - return result - - def read_setup_py(self, filepath: Path) -> dict[str, Any]: - with filepath.open(encoding="utf-8") as f: - content = f.read() - - result: dict[str, Any] = {} - - body = ast.parse(content).body - - setup_call = self._find_setup_call(body) - if setup_call is None: - return self.DEFAULT - - # Inspecting keyword arguments - call, body = setup_call - result["name"] = self._find_single_string(call, body, "name") - result["version"] = self._find_single_string(call, body, "version") - result["description"] = self._find_single_string(call, body, "description") - result["install_requires"] = self._find_install_requires(call, body) - result["extras_require"] = self._find_extras_require(call, body) - result["python_requires"] = self._find_single_string( - call, body, "python_requires" - ) - - return result - - def read_setup_cfg(self, filepath: Path) -> dict[str, Any]: - parser = ConfigParser() - - parser.read(str(filepath)) - - name = None - version = None - description = None - if parser.has_option("metadata", "name"): - name = parser.get("metadata", "name") - - if parser.has_option("metadata", "version"): - version = Version.parse(parser.get("metadata", "version")).text - - if parser.has_option("metadata", "description"): - description = parser.get("metadata", "description") - - install_requires = [] - extras_require: dict[str, list[str]] = {} - python_requires = None - if parser.has_section("options"): - if parser.has_option("options", "install_requires"): - for dep in parser.get("options", "install_requires").split("\n"): - dep = dep.strip() - if not dep: - continue - - install_requires.append(dep) - - if parser.has_option("options", "python_requires"): - python_requires = parser.get("options", "python_requires") - - if parser.has_section("options.extras_require"): - for group in parser.options("options.extras_require"): - extras_require[group] = [] - deps = parser.get("options.extras_require", group) - for dep in deps.split("\n"): - dep = dep.strip() - if not dep: - continue - - extras_require[group].append(dep) - - return { - "name": name, - "version": version, - "description": description, - "install_requires": install_requires, - "extras_require": extras_require, - "python_requires": python_requires, - } - - def _find_setup_call( - self, elements: list[ast.stmt] - ) -> tuple[ast.Call, list[ast.stmt]] | None: - funcdefs: list[ast.stmt] = [] - for i, element in enumerate(elements): - if isinstance(element, ast.If) and i == len(elements) - 1: - # Checking if the last element is an if statement - # and if it is 'if __name__ == "__main__"' which - # could contain the call to setup() - test = element.test - if not isinstance(test, ast.Compare): - continue - - left = test.left - if not isinstance(left, ast.Name): - continue - - if left.id != "__name__": - continue - - setup_call = self._find_sub_setup_call([element]) - if setup_call is None: - continue - - call, body = setup_call - return call, body + elements - - if not isinstance(element, ast.Expr): - if isinstance(element, ast.FunctionDef): - funcdefs.append(element) - - continue - - value = element.value - if not isinstance(value, ast.Call): - continue - - func = value.func - if not (isinstance(func, ast.Name) and func.id == "setup") and not ( - isinstance(func, ast.Attribute) - and getattr(func.value, "id", None) == "setuptools" - and func.attr == "setup" - ): - continue - - return value, elements - - # Nothing, we inspect the function definitions - return self._find_sub_setup_call(funcdefs) - - def _find_sub_setup_call( - self, elements: list[ast.stmt] - ) -> tuple[ast.Call, list[ast.stmt]] | None: - for element in elements: - if not isinstance(element, (ast.FunctionDef, ast.If)): - continue - - setup_call = self._find_setup_call(element.body) - if setup_call is not None: - sub_call, body = setup_call - - body = elements + body - - return sub_call, body - - return None - - def _find_install_requires(self, call: ast.Call, body: list[ast.stmt]) -> list[str]: - value = self._find_in_call(call, "install_requires") - if value is None: - # Trying to find in kwargs - kwargs = self._find_call_kwargs(call) - - if kwargs is None or not isinstance(kwargs, ast.Name): - return [] - - variable = self._find_variable_in_body(body, kwargs.id) - - if isinstance(variable, ast.Dict): - value = self._find_in_dict(variable, "install_requires") - - elif ( - isinstance(variable, ast.Call) - and isinstance(variable.func, ast.Name) - and variable.func.id == "dict" - ): - value = self._find_in_call(variable, "install_requires") - - else: - raise SetupReaderError(f"Cannot handle variable {variable}") - - if value is None: - return [] - - if isinstance(value, ast.Name): - value = self._find_variable_in_body(body, value.id) - - if isinstance(value, ast.Constant) and value.value is None: - return [] - - if isinstance(value, ast.List): - return string_list_values(value) - - raise SetupReaderError(f"Cannot handle value of type {type(value)}") - - def _find_extras_require( - self, call: ast.Call, body: list[ast.stmt] - ) -> dict[str, list[str]]: - value = self._find_in_call(call, "extras_require") - if value is None: - # Trying to find in kwargs - kwargs = self._find_call_kwargs(call) - - if kwargs is None or not isinstance(kwargs, ast.Name): - return {} - - variable = self._find_variable_in_body(body, kwargs.id) - if isinstance(variable, ast.Dict): - value = self._find_in_dict(variable, "extras_require") - - elif ( - isinstance(variable, ast.Call) - and isinstance(variable.func, ast.Name) - and variable.func.id == "dict" - ): - value = self._find_in_call(variable, "extras_require") - - else: - raise SetupReaderError(f"Cannot handle variable {variable}") - - if value is None: - return {} - - if isinstance(value, ast.Name): - value = self._find_variable_in_body(body, value.id) - - if isinstance(value, ast.Constant) and value.value is None: - return {} - - if isinstance(value, ast.Dict): - extras_require: dict[str, list[str]] = {} - val: ast.expr | None - for key, val in zip(value.keys, value.values): - if not isinstance(key, ast.Constant) or not isinstance(key.value, str): - raise SetupReaderError(f"Cannot handle key {key}") - - if isinstance(val, ast.Name): - val = self._find_variable_in_body(body, val.id) - - if not isinstance(val, ast.List): - raise SetupReaderError(f"Cannot handle value of type {type(val)}") - - extras_require[key.value] = string_list_values(val) - - return extras_require - - raise SetupReaderError(f"Cannot handle value of type {type(value)}") - - def _find_single_string( - self, call: ast.Call, body: list[ast.stmt], name: str - ) -> str | None: - value = self._find_in_call(call, name) - if value is None: - # Trying to find in kwargs - kwargs = self._find_call_kwargs(call) - - if kwargs is None or not isinstance(kwargs, ast.Name): - return None - - variable = self._find_variable_in_body(body, kwargs.id) - if not isinstance(variable, (ast.Dict, ast.Call)): - return None - - if isinstance(variable, ast.Call): - if not isinstance(variable.func, ast.Name): - return None - - if variable.func.id != "dict": - return None - - value = self._find_in_call(variable, name) - else: - value = self._find_in_dict(variable, name) - - if value is None: - return None - - if isinstance(value, ast.Constant) and isinstance(value.value, str): - return value.value - elif isinstance(value, ast.Name): - variable = self._find_variable_in_body(body, value.id) - - if ( - variable is not None - and isinstance(variable, ast.Constant) - and isinstance(variable.value, str) - ): - return variable.value - - return None - - def _find_in_call(self, call: ast.Call, name: str) -> Any | None: - for keyword in call.keywords: - if keyword.arg == name: - return keyword.value - return None - - def _find_call_kwargs(self, call: ast.Call) -> Any | None: - kwargs = None - for keyword in call.keywords: - if keyword.arg is None: - kwargs = keyword.value - - return kwargs - - def _find_variable_in_body( - self, body: list[ast.stmt], name: str - ) -> ast.expr | None: - for elem in body: - if not isinstance(elem, ast.Assign): - continue - - for target in elem.targets: - if not isinstance(target, ast.Name): - continue - - if target.id == name: - return elem.value - - return None - - def _find_in_dict(self, dict_: ast.Dict, name: str) -> ast.expr | None: - for key, val in zip(dict_.keys, dict_.values): - if ( - isinstance(key, ast.Constant) - and isinstance(key.value, str) - and key.value == name - ): - return val - - return None - - -def string_list_values(value: ast.List) -> list[str]: - strings = [] - for element in value.elts: - if isinstance(element, ast.Constant) and isinstance(element.value, str): - strings.append(element.value) - - else: - raise SetupReaderError("Found non-string element in list") - - return strings diff --git a/tests/conftest.py b/tests/conftest.py index bc0f3440332..05e1c6cbfec 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -7,7 +7,6 @@ import shutil import sys -from contextlib import suppress from pathlib import Path from typing import TYPE_CHECKING from typing import Any @@ -26,9 +25,8 @@ from poetry.config.config import Config as BaseConfig from poetry.config.dict_config_source import DictConfigSource from poetry.factory import Factory -from poetry.inspection.info import PackageInfo -from poetry.inspection.info import PackageInfoError from poetry.layouts import layout +from poetry.packages.direct_origin import _get_package_from_git from poetry.repositories import Repository from poetry.repositories import RepositoryPool from poetry.utils.cache import ArtifactCache @@ -279,19 +277,6 @@ def mock_user_config_dir(mocker: MockerFixture, config_dir: Path) -> None: mocker.patch("poetry.config.config.CONFIG_DIR", new=config_dir) -@pytest.fixture(autouse=True) -def pep517_metadata_mock(mocker: MockerFixture) -> None: - def get_pep517_metadata(path: Path) -> PackageInfo: - with suppress(PackageInfoError): - return PackageInfo.from_setup_files(path) - return PackageInfo(name="demo", version="0.1.2") - - mocker.patch( - "poetry.inspection.info.get_pep517_metadata", - get_pep517_metadata, - ) - - @pytest.fixture def environ() -> Iterator[None]: with isolated_environment(): @@ -316,6 +301,8 @@ def git_mock(mocker: MockerFixture) -> None: p = mocker.patch("poetry.vcs.git.Git.get_revision") p.return_value = MOCK_DEFAULT_GIT_REVISION + _get_package_from_git.cache_clear() + @pytest.fixture def http() -> Iterator[type[httpretty.httpretty]]: diff --git a/tests/helpers.py b/tests/helpers.py index c25179bb74a..bf15904e979 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -76,10 +76,10 @@ def get_dependency( def copy_path(source: Path, dest: Path) -> None: - if dest.is_symlink() or dest.is_file(): - dest.unlink() # missing_ok is only available in Python >= 3.8 - elif dest.is_dir(): + if dest.is_dir(): shutil.rmtree(dest) + else: + dest.unlink(missing_ok=True) if source.is_dir(): shutil.copytree(source, dest) @@ -108,12 +108,13 @@ def mock_clone( assert parsed.resource is not None folder = FIXTURE_PATH / "git" / parsed.resource / path + assert folder.is_dir() if not source_root: source_root = Path(Config.create().get("cache-dir")) / "src" dest = source_root / path - dest.parent.mkdir(parents=True, exist_ok=True) + dest.mkdir(parents=True, exist_ok=True) copy_path(folder, dest) return MockDulwichRepo(dest) diff --git a/tests/inspection/test_info.py b/tests/inspection/test_info.py index 99fd04d66c0..377aba2e902 100644 --- a/tests/inspection/test_info.py +++ b/tests/inspection/test_info.py @@ -28,11 +28,6 @@ from tests.types import SetProjectContext -@pytest.fixture(autouse=True) -def pep517_metadata_mock() -> None: - pass - - @pytest.fixture def demo_sdist(fixture_dir: FixtureDirGetter) -> Path: return fixture_dir("distributions") / "demo-0.1.0.tar.gz" @@ -150,19 +145,29 @@ def demo_check_info(info: PackageInfo, requires_dist: set[str] | None = None) -> if requires_dist: assert set(info.requires_dist) == requires_dist else: + # Exact formatting various according to the exact mechanism used to extract the + # metadata. assert set(info.requires_dist) in ( - # before https://github.com/python-poetry/poetry-core/pull/510 { 'cleo; extra == "foo"', "pendulum (>=1.4.4)", 'tomlkit; extra == "bar"', }, - # after https://github.com/python-poetry/poetry-core/pull/510 { 'cleo ; extra == "foo"', "pendulum (>=1.4.4)", 'tomlkit ; extra == "bar"', }, + { + 'cleo ; extra == "foo"', + "pendulum>=1.4.4", + 'tomlkit ; extra == "bar"', + }, + { + "cleo ; extra == 'foo'", + "pendulum (>=1.4.4)", + "tomlkit ; extra == 'bar'", + }, ) @@ -243,9 +248,7 @@ def test_info_from_bdist(demo_wheel: Path) -> None: def test_info_from_poetry_directory(fixture_dir: FixtureDirGetter) -> None: - info = PackageInfo.from_directory( - fixture_dir("inspection") / "demo", disable_build=True - ) + info = PackageInfo.from_directory(fixture_dir("inspection") / "demo") demo_check_info(info) @@ -275,16 +278,6 @@ def test_info_from_requires_txt(fixture_dir: FixtureDirGetter) -> None: demo_check_info(info) -def test_info_from_setup_py(demo_setup: Path) -> None: - info = PackageInfo.from_setup_files(demo_setup) - demo_check_info(info, requires_dist={"package"}) - - -def test_info_from_setup_cfg(demo_setup_cfg: Path) -> None: - info = PackageInfo.from_setup_files(demo_setup_cfg) - demo_check_info(info, requires_dist={"package"}) - - def test_info_no_setup_pkg_info_no_deps(fixture_dir: FixtureDirGetter) -> None: info = PackageInfo.from_metadata_directory( fixture_dir("inspection") / "demo_no_setup_pkg_info_no_deps" @@ -318,14 +311,7 @@ def test_info_no_setup_pkg_info_no_deps_dynamic(fixture_dir: FixtureDirGetter) - def test_info_setup_simple(mocker: MockerFixture, demo_setup: Path) -> None: spy = mocker.spy(VirtualEnv, "run") info = PackageInfo.from_directory(demo_setup) - assert spy.call_count == 0 - demo_check_info(info, requires_dist={"package"}) - - -def test_info_setup_cfg(mocker: MockerFixture, demo_setup_cfg: Path) -> None: - spy = mocker.spy(VirtualEnv, "run") - info = PackageInfo.from_directory(demo_setup_cfg) - assert spy.call_count == 0 + assert spy.call_count == 5 demo_check_info(info, requires_dist={"package"}) @@ -356,14 +342,6 @@ def test_info_setup_complex_pep517_legacy( demo_check_info(info, requires_dist={"package"}) -def test_info_setup_complex_disable_build( - mocker: MockerFixture, demo_setup_complex: Path -) -> None: - # Cannot extract install_requires from list comprehension. - with pytest.raises(PackageInfoError): - PackageInfo.from_directory(demo_setup_complex, disable_build=True) - - @pytest.mark.network def test_info_setup_complex_calls_script(demo_setup_complex_calls_script: Path) -> None: """Building the project requires calling a script from its build_requires.""" @@ -391,24 +369,6 @@ def test_info_setup_missing_mandatory_should_trigger_pep517( assert spy.call_count == 1 -@pytest.mark.network -def test_info_setup_missing_install_requires_is_fine( - mocker: MockerFixture, source_dir: Path -) -> None: - setup = "from setuptools import setup; " - setup += "setup(" - setup += 'name="demo", ' - setup += 'version="0.1.0", ' - setup += ")" - - setup_py = source_dir / "setup.py" - setup_py.write_text(setup) - - spy = mocker.spy(VirtualEnv, "run") - _ = PackageInfo.from_directory(source_dir) - assert spy.call_count == 0 - - def test_info_prefer_poetry_config_over_egg_info(fixture_dir: FixtureDirGetter) -> None: info = PackageInfo.from_directory( fixture_dir("inspection") / "demo_with_obsolete_egg_info" diff --git a/tests/repositories/test_pypi_repository.py b/tests/repositories/test_pypi_repository.py index dec6a6559be..91c8a03eadc 100644 --- a/tests/repositories/test_pypi_repository.py +++ b/tests/repositories/test_pypi_repository.py @@ -239,37 +239,11 @@ def test_fallback_pep_658_metadata( assert dep.python_versions == "~2.7" -def test_fallback_can_read_setup_to_get_dependencies( - pypi_repository: PyPiRepository, -) -> None: - repo = pypi_repository - repo._fallback = True - - package = repo.package("sqlalchemy", Version.parse("1.2.12")) - - assert package.name == "sqlalchemy" - assert len(package.requires) == 9 - assert len([r for r in package.requires if r.is_optional()]) == 9 - - assert package.extras == { - "mssql-pymssql": [Dependency("pymssql", "*")], - "mssql-pyodbc": [Dependency("pyodbc", "*")], - "mysql": [Dependency("mysqlclient", "*")], - "oracle": [Dependency("cx_oracle", "*")], - "postgresql": [Dependency("psycopg2", "*")], - "postgresql-pg8000": [Dependency("pg8000", "*")], - "postgresql-psycopg2binary": [Dependency("psycopg2-binary", "*")], - "postgresql-psycopg2cffi": [Dependency("psycopg2cffi", "*")], - "pymysql": [Dependency("pymysql", "*")], - } - - def test_pypi_repository_supports_reading_bz2_files( pypi_repository: PyPiRepository, ) -> None: repo = pypi_repository repo._fallback = True - package = repo.package("twisted", Version.parse("18.9.0")) assert package.name == "twisted" diff --git a/tests/utils/test_dependency_specification.py b/tests/utils/test_dependency_specification.py index a729f884384..24c9c710bd3 100644 --- a/tests/utils/test_dependency_specification.py +++ b/tests/utils/test_dependency_specification.py @@ -7,6 +7,7 @@ from deepdiff import DeepDiff +from poetry.inspection.info import PackageInfo from poetry.utils.dependency_specification import RequirementsParser @@ -167,6 +168,8 @@ def test_parse_dependency_specification( ) -> None: original = Path.exists + # Parsing file and path dependencies reads metadata from the file or path in + # question: for these tests we mock that out. def _mock(self: Path) -> bool: if "/" in requirement and self == Path.cwd().joinpath(requirement): return True @@ -174,6 +177,11 @@ def _mock(self: Path) -> bool: mocker.patch("pathlib.Path.exists", _mock) + mocker.patch( + "poetry.inspection.info.get_pep517_metadata", + return_value=PackageInfo(name="demo", version="0.1.2"), + ) + assert any( not DeepDiff( RequirementsParser(artifact_cache=artifact_cache).parse(requirement), diff --git a/tests/utils/test_isolated_build.py b/tests/utils/test_isolated_build.py index 164d8c79fca..044dac1404e 100644 --- a/tests/utils/test_isolated_build.py +++ b/tests/utils/test_isolated_build.py @@ -1,5 +1,6 @@ from __future__ import annotations +import shutil import sys from pathlib import Path @@ -85,7 +86,8 @@ def test_isolated_env_install_failure( def test_isolated_builder_outside_poetry_project_context( tmp_working_directory: Path, fixture_dir: FixtureDirGetter ) -> None: - source = fixture_dir("project_with_setup") + source = tmp_working_directory / "source" + shutil.copytree(fixture_dir("project_with_setup"), source) destination = tmp_working_directory / "dist" try: diff --git a/tests/utils/test_setup_reader.py b/tests/utils/test_setup_reader.py deleted file mode 100644 index 444debfbf16..00000000000 --- a/tests/utils/test_setup_reader.py +++ /dev/null @@ -1,245 +0,0 @@ -from __future__ import annotations - -from pathlib import Path -from typing import TYPE_CHECKING - -import pytest - -from poetry.core.version.exceptions import InvalidVersion - -from poetry.utils.setup_reader import SetupReader - - -if TYPE_CHECKING: - from collections.abc import Callable - - -@pytest.fixture() -def setup() -> Callable[[str], Path]: - def _setup(name: str) -> Path: - return Path(__file__).parent / "fixtures" / "setups" / name - - return _setup - - -def test_setup_reader_read_minimal_setup_py(setup: Callable[[str], Path]) -> None: - result = SetupReader.read_from_directory(setup("minimal")) - - expected_name = None - expected_version = None - expected_description = None - expected_install_requires: list[str] = [] - expected_extras_require: dict[str, list[str]] = {} - expected_python_requires = None - - assert result["name"] == expected_name - assert result["version"] == expected_version - assert result["description"] == expected_description - assert result["install_requires"] == expected_install_requires - assert result["extras_require"] == expected_extras_require - assert result["python_requires"] == expected_python_requires - - -def test_setup_reader_read_first_level_setup_call_with_direct_types( - setup: Callable[[str], Path], -) -> None: - result = SetupReader.read_from_directory(setup("flask")) - - expected_name = "Flask" - expected_version = None - expected_description = "A simple framework for building complex web applications." - expected_install_requires = [ - "Werkzeug>=0.14", - "Jinja2>=2.10", - "itsdangerous>=0.24", - "click>=5.1", - ] - expected_extras_require = { - "dotenv": ["python-dotenv"], - "dev": [ - "pytest>=3", - "coverage", - "tox", - "sphinx", - "pallets-sphinx-themes", - "sphinxcontrib-log-cabinet", - ], - "docs": ["sphinx", "pallets-sphinx-themes", "sphinxcontrib-log-cabinet"], - } - expected_python_requires = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" - - assert result["name"] == expected_name - assert result["version"] == expected_version - assert result["description"] == expected_description - assert result["install_requires"] == expected_install_requires - assert result["extras_require"] == expected_extras_require - assert result["python_requires"] == expected_python_requires - - -def test_setup_reader_read_first_level_setup_call_with_variables( - setup: Callable[[str], Path], -) -> None: - result = SetupReader.read_from_directory(setup("requests")) - - expected_name = None - expected_version = None - expected_description = None - expected_install_requires = [ - "chardet>=3.0.2,<3.1.0", - "idna>=2.5,<2.8", - "urllib3>=1.21.1,<1.25", - "certifi>=2017.4.17", - ] - expected_extras_require = { - "security": ["pyOpenSSL >= 0.14", "cryptography>=1.3.4", "idna>=2.0.0"], - "socks": ["PySocks>=1.5.6, !=1.5.7"], - 'socks:sys_platform == "win32" and python_version == "2.7"': ["win_inet_pton"], - } - expected_python_requires = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - - assert result["name"] == expected_name - assert result["version"] == expected_version - assert result["description"] == expected_description - assert result["install_requires"] == expected_install_requires - assert result["extras_require"] == expected_extras_require - assert result["python_requires"] == expected_python_requires - - -def test_setup_reader_read_sub_level_setup_call_with_direct_types( - setup: Callable[[str], Path], -) -> None: - result = SetupReader.read_from_directory(setup("sqlalchemy")) - - expected_name = "SQLAlchemy" - expected_version = None - expected_description = "Database Abstraction Library" - expected_install_requires: list[str] = [] - expected_extras_require = { - "mysql": ["mysqlclient"], - "pymysql": ["pymysql"], - "postgresql": ["psycopg2"], - "postgresql_pg8000": ["pg8000"], - "postgresql_psycopg2cffi": ["psycopg2cffi"], - "oracle": ["cx_oracle"], - "mssql_pyodbc": ["pyodbc"], - "mssql_pymssql": ["pymssql"], - } - - assert result["name"] == expected_name - assert result["version"] == expected_version - assert result["description"] == expected_description - assert result["install_requires"] == expected_install_requires - assert result["extras_require"] == expected_extras_require - assert result["python_requires"] is None - - -def test_setup_reader_read_setup_cfg(setup: Callable[[str], Path]) -> None: - result = SetupReader.read_from_directory(setup("with-setup-cfg")) - - expected_name = "with-setup-cfg" - expected_version = "1.2.3" - expected_description = "Package with setup.cfg" - expected_install_requires = ["six", "tomlkit"] - expected_extras_require = { - "validation": ["cerberus"], - "tests": ["pytest", "pytest-xdist", "pytest-cov"], - } - expected_python_requires = ">=2.6,!=3.0,!=3.1,!=3.2,!=3.3" - - assert result["name"] == expected_name - assert result["version"] == expected_version - assert result["description"] == expected_description - assert result["install_requires"] == expected_install_requires - assert result["extras_require"] == expected_extras_require - assert result["python_requires"] == expected_python_requires - - -def test_setup_reader_read_minimal_setup_cfg(setup: Callable[[str], Path]) -> None: - result = SetupReader.read_from_directory(setup("with-setup-cfg-minimal")) - - expected_name = None - expected_version = None - expected_description = None - expected_install_requires: list[str] = [] - expected_extras_require: dict[str, list[str]] = {} - expected_python_requires = None - - assert result["name"] == expected_name - assert result["version"] == expected_version - assert result["description"] == expected_description - assert result["install_requires"] == expected_install_requires - assert result["extras_require"] == expected_extras_require - assert result["python_requires"] == expected_python_requires - - -def test_setup_reader_read_setup_cfg_with_attr(setup: Callable[[str], Path]) -> None: - with pytest.raises(InvalidVersion): - SetupReader.read_from_directory(setup("with-setup-cfg-attr")) - - -def test_setup_reader_read_setup_kwargs(setup: Callable[[str], Path]) -> None: - result = SetupReader.read_from_directory(setup("pendulum")) - - expected_name = "pendulum" - expected_version = "2.0.4" - expected_description = "Python datetimes made easy" - expected_install_requires = ["python-dateutil>=2.6,<3.0", "pytzdata>=2018.3"] - expected_extras_require = {':python_version < "3.5"': ["typing>=3.6,<4.0"]} - expected_python_requires = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - - assert result["name"] == expected_name - assert result["version"] == expected_version - assert result["description"] == expected_description - assert result["install_requires"] == expected_install_requires - assert result["extras_require"] == expected_extras_require - assert result["python_requires"] == expected_python_requires - - -def test_setup_reader_read_setup_call_in_main(setup: Callable[[str], Path]) -> None: - result = SetupReader.read_from_directory(setup("pyyaml")) - - expected_name = "PyYAML" - expected_version = "3.13" - expected_description = "YAML parser and emitter for Python" - expected_install_requires: list[str] = [] - expected_extras_require: dict[str, list[str]] = {} - expected_python_requires = None - - assert result["name"] == expected_name - assert result["version"] == expected_version - assert result["description"] == expected_description - assert result["install_requires"] == expected_install_requires - assert result["extras_require"] == expected_extras_require - assert result["python_requires"] == expected_python_requires - - -def test_setup_reader_read_extras_require_with_variables( - setup: Callable[[str], Path], -) -> None: - result = SetupReader.read_from_directory(setup("extras_require_with_vars")) - - expected_name = "extras_require_with_vars" - expected_version = "0.0.1" - expected_description = "test setup_reader.py" - expected_install_requires: list[str] = [] - expected_extras_require = {"test": ["pytest"]} - expected_python_requires = None - - assert result["name"] == expected_name - assert result["version"] == expected_version - assert result["description"] == expected_description - assert result["install_requires"] == expected_install_requires - assert result["extras_require"] == expected_extras_require - assert result["python_requires"] == expected_python_requires - - -def test_setup_reader_setuptools(setup: Callable[[str], Path]) -> None: - result = SetupReader.read_from_directory(setup("setuptools_setup")) - - expected_name = "my_package" - expected_version = "0.1.2" - expected_description = "Just a description" - - assert result["name"] == expected_name - assert result["version"] == expected_version - assert result["description"] == expected_description