Skip to content

Commit

Permalink
Merge pull request #12 from Gatewatcher/11-add-pyprojecttoml-entrypoints
Browse files Browse the repository at this point in the history
feat(pyproject): add pyproject entrypoints (#11)
  • Loading branch information
MoranAbadieGatewatcher committed May 19, 2023
2 parents a1075eb + 93fa7b4 commit fb4838d
Show file tree
Hide file tree
Showing 6 changed files with 89 additions and 12 deletions.
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

0 comments on commit fb4838d

Please sign in to comment.