Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(pyproject): add pyproject entrypoints (#11) #12

Merged
merged 1 commit into from
May 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
Plugin for py.test for doing vulture tests
"""
import os

from pathlib import Path

from setuptools import (
Expand Down Expand Up @@ -44,7 +45,7 @@

setup(
name='pytest-vulture',
version='2.0.2',
version='2.1.0',
include_package_data=True,
author='Abadie Moran',
author_email='moran.abadie@gatewatcher.com',
Expand Down
7 changes: 7 additions & 0 deletions src/pytest_vulture/conf/package.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,18 @@ class PackageConfiguration(Configuration):
True
"""
_setup_path: Path = Path("setup.py")
_source_path: Path = Path("")
_check_entry_points: bool = True
_NAME = "package"

@property
def setup_path(self) -> Path:
return self._setup_path

@property
def source_path(self) -> Path:
return self._source_path

@property
def check_entry_points(self) -> bool:
return self._check_entry_points
Expand All @@ -38,6 +43,8 @@ def read_ini(self, config: ConfigParser):
"""Read the ini file"""
with suppress(KeyError):
self._setup_path = Path(config[self._NAME]['setup_path'])
with suppress(KeyError):
self._source_path = Path(config[self._NAME]['source_path'])
with suppress(KeyError):
self._check_entry_points = self._to_bool(
config[self._NAME]['check_entry_points']
Expand Down
32 changes: 24 additions & 8 deletions src/pytest_vulture/setup_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,16 @@ class SetupManager:
_entry_points: List[str]
_config: IniReader
_UNUSED_FUNCTION_MESSAGE = "unused function"
_PY_PROJECT = "[project.scripts]"

def __init__(self, config: IniReader):
self._entry_points = []
self._config = config
try:
content = self._config.package_configuration.setup_path.read_text("utf-8").replace("\n", "")
except (FileNotFoundError, UnicodeDecodeError):
except (OSError, ValueError):
return
self.__generate_entry_points(content)
self._generate_entry_points(content)

def is_entry_point(self, vulture: Item) -> bool:
"""Check if the vulture output is an entry point
Expand All @@ -61,23 +62,33 @@ def is_entry_point(self, vulture: Item) -> bool:
return True
return False

@classmethod
def _python_path(cls, vulture: Item):
def _python_path(self, vulture: Item):
try:
relative_path = vulture.filename.relative_to(Path("").absolute())
relative_path = vulture.filename.relative_to(self._get_dir_path().absolute())
except ValueError:
relative_path = vulture.filename

python_path = relative_path.as_posix().replace("/", ".").replace(".py", "")
dots_message = f"{cls._UNUSED_FUNCTION_MESSAGE} '"
dots_message = f"{self._UNUSED_FUNCTION_MESSAGE} '"
find = re.findall(f"(?={dots_message}).*(?<=')", vulture.message)
if find:
function_name = find[0].replace(dots_message, "")
python_path += ":" + function_name[:-1]
return python_path

def __generate_entry_points(self, content: str):
def _find_py_project_toml(self, content: str):
"""We do not want to add a toml dependency for now."""
entry_points = content.split(self._PY_PROJECT, 1)[1].split("[", 1)[0].split("\n")
entry_points = [entry_point for entry_point in entry_points if entry_point]
for entry_point in entry_points:
self.__parse_entry_point_line(entry_point.replace(" ", "").replace('"', ""), [])
return ""

def _generate_entry_points(self, content: str):
"""Parse the setup.pu file to get the entry points"""
if self._PY_PROJECT in content:
self._find_py_project_toml(content)
return
root_paths = self.__generate_root_paths(content)
entry_points = {}
find = re.findall("(?=entry_points={).*(?<=})", content.replace("\n", ""))
Expand Down Expand Up @@ -109,6 +120,11 @@ def __parse_entry_point_line(self, equality: str, root_paths: List[Tuple[str, st
self._entry_points.append(value)
self.__check_entry_points()

def _get_dir_path(self) -> Path:
source_path = self._config.package_configuration.source_path
dir_path = self._config.package_configuration.setup_path.absolute().parent
return dir_path / source_path

def __check_entry_points(self):
"""Checks if the entry points exists"""
if not self._config.package_configuration.check_entry_points:
Expand All @@ -120,7 +136,7 @@ def __check_entry_points(self):
path_dots = split_points[0]
except IndexError:
continue
dir_path = self._config.package_configuration.setup_path.absolute().parent
dir_path = self._get_dir_path()
new_path = Path(path_dots.replace(".", "/"))
if (dir_path / new_path).is_dir(): # pragma: no cover
path = (dir_path / new_path / "__init__.py").absolute()
Expand Down
2 changes: 2 additions & 0 deletions test/tests/functional/test_setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ def test_is_entry_point(check_entry_points, examples_path, setup, message, file,
conf = Mock()
conf.package_configuration.check_entry_points = check_entry_points
conf.package_configuration.setup_path = examples_path / "setups" / setup
conf.package_configuration.source_path = Path("")
setup_entry_point = SetupManager(conf)
vulture = Item("test", "function", Path(file), first_lineno, first_lineno, message, 50)

Expand All @@ -49,6 +50,7 @@ def test_errors(examples_path, path, error):
conf = Mock()
conf.package_configuration.check_entry_points = True
not_found_path = examples_path / "setups" / path
conf.package_configuration.source_path = Path("")
conf.package_configuration.setup_path = not_found_path

with pytest.raises(error):
Expand Down
9 changes: 6 additions & 3 deletions test/tests/unit/test_line_parsing.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""tests the vulture line parsing"""
# pylint: disable=protected-access
from pathlib import Path
from unittest.mock import Mock

import pytest

Expand All @@ -16,6 +17,8 @@
(Item("test", "function", Path("src/tutu.py"), 15, 15, "unused function 'tata'", 60), "src.tutu:tata"),
]
)
def test_python_path(vulture_message, path):

assert SetupManager._python_path(vulture_message) == path
def test_python_path(vulture_message, path, tmp_path):
mock = Mock()
mock.package_configuration.source_path = Path("")
mock.package_configuration.setup_path = tmp_path
assert SetupManager(mock)._python_path(vulture_message) == path
48 changes: 48 additions & 0 deletions test/tests/unit/test_setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
"""Tests the setup parser."""
# pylint: disable=protected-access
from pathlib import Path
from unittest.mock import Mock

from vulture.core import Item

from pytest_vulture.setup_manager import SetupManager


_DATA = """[build-system]
build-backend = "setuptools.build_meta"
requires = [
"setuptools",
]

[project]
name = "test-tools"
version = "2.0.0a0"
requires-python = ">=3.9"
dynamic = [
"dependencies",
]
[project.scripts]
test_api = "test_tools.cli.test:main"

[tool.setuptools.dynamic]
dependencies = {file = ["requirements/main.txt"]}
"""

def test_py_project(tmp_path):
setup = tmp_path / "pyproject.toml"
test_tools = tmp_path / "test_tools/cli"
test_tools.mkdir(parents=True)
(test_tools / "test.py").write_text("def main()", encoding="utf-8")
setup.write_text(_DATA, encoding="utf-8")
mock = Mock()
mock.package_configuration.setup_path = setup
mock.package_configuration.source_path = tmp_path
setup = SetupManager(mock)

assert setup._entry_points == ['test_tools.cli.test:main']
assert setup.is_entry_point(
Item("test", "function", Path("toto.py"), 1, 1, "unused function 'main'", 50)
) is False
assert setup.is_entry_point(
Item("test", "function", Path("test_tools/cli/test.py"), 1, 1, "unused function 'main'", 50)
) is True