CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-prospector

Prospector is a tool to analyse Python code by aggregating the result of other tools.

Pending
Overview
Eval results
Files

tools.mddocs/

Tools

Plugin system for integrating static analysis tools with base classes and tool registry. The tools framework allows Prospector to run multiple analysis tools with a unified interface.

Capabilities

ToolBase Abstract Class

Base class that all analysis tools must implement.

class ToolBase(ABC):
    pass

Abstract base class for all Prospector tools. Tools must implement the configure and run methods.

@abstractmethod
def configure(self, prospector_config: "ProspectorConfig", found_files: FileFinder) -> Optional[tuple[Optional[Union[str, Path]], Optional[Iterable[Message]]]]

Configures the tool based on the provided configuration and discovered files.

Parameters:

  • prospector_config: ProspectorConfig - Prospector configuration object
  • found_files: FileFinder - File discovery object

Returns:

  • Optional[tuple] - Two-element tuple or None:
    • First element: Optional[Union[str, Path]] - Configuration source (file path or description), None for defaults
    • Second element: Optional[Iterable[Message]] - Configuration messages/warnings, None if no issues

This method allows tools to:

  • Discover tool-specific configuration files (e.g., .pylintrc, pyproject.toml)
  • Validate configuration settings
  • Report configuration issues as messages
  • Use Prospector defaults if no tool-specific config is found
@abstractmethod
def run(self, found_files: FileFinder) -> list[Message]

Executes the analysis tool and returns found issues.

Parameters:

  • found_files: FileFinder - File discovery object containing files to analyze

Returns:

  • list[Message] - List of analysis messages found by the tool
def get_ignored_codes(self, line: str) -> list[tuple[str, int]]

Parses a line of code for inline ignore directives (e.g., # noqa).

Parameters:

  • line: str - Line of source code to parse

Returns:

  • list[tuple[str, int]] - List of (error_code, line_offset) tuples for codes to ignore

Tool Registry

TOOLS: dict[str, type[ToolBase]]

Registry of all available analysis tools.

Available Tools:

  • "dodgy": DodgyTool - Detects potentially dodgy code (hardcoded passwords, etc.)
  • "mccabe": McCabeTool - Cyclomatic complexity analysis
  • "pyflakes": PyFlakesTool - Fast static analysis for Python
  • "pycodestyle": PycodestyleTool - PEP 8 style checker
  • "pylint": PylintTool - Comprehensive Python linter
  • "pydocstyle": PydocstyleTool - Docstring style checker
  • "profile-validator": ProfileValidationTool - Validates Prospector profiles
  • "vulture": VultureTool - Dead code finder (optional dependency)
  • "pyroma": PyromaTool - Python package quality checker (optional dependency)
  • "pyright": PyrightTool - Microsoft's Python type checker (optional dependency)
  • "mypy": MypyTool - Python type checker (optional dependency)
  • "bandit": BanditTool - Security linter (optional dependency)
  • "ruff": RuffTool - Fast Python linter and formatter (optional dependency)
DEFAULT_TOOLS: tuple[str, ...]

Tuple of tool names that run by default.

Default Tools:

  • "dodgy" - Always runs
  • "mccabe" - Always runs
  • "pyflakes" - Always runs
  • "pycodestyle" - Always runs
  • "pylint" - Always runs
  • "pydocstyle" - Always runs
  • "profile-validator" - Always runs
DEPRECATED_TOOL_NAMES: dict[str, str]

Mapping of deprecated tool names to their current names.

Deprecated Names:

  • "pep8""pycodestyle"
  • "pep257""pydocstyle"

Individual Tool Classes

Built-in Tools

class DodgyTool(ToolBase):
    pass

Detects potentially dodgy code patterns like hardcoded passwords, TODO comments, and other code smells.

class McCabeTool(ToolBase):
    pass

Measures cyclomatic complexity of functions and methods. Identifies overly complex code that may be hard to maintain.

class PyFlakesTool(ToolBase):
    pass

Fast static analysis tool that catches common Python errors like undefined variables, unused imports, and syntax errors.

class PycodestyleTool(ToolBase):
    pass

Checks Python code against PEP 8 style guidelines, including line length, indentation, whitespace, and naming conventions.

class PylintTool(ToolBase):
    pass

Comprehensive Python linter that checks for errors, enforces coding standards, looks for code smells, and suggests refactoring.

class PydocstyleTool(ToolBase):
    pass

Checks docstring style against PEP 257 conventions and other docstring standards.

class ProfileValidationTool(ToolBase):
    pass

Validates Prospector profile configuration files for syntax and semantic correctness.

Optional Tools

These tools require additional dependencies and are only available when installed:

class VultureTool(ToolBase):
    pass

Finds unused code (dead code) in Python programs. Install with pip install prospector[with_vulture].

class PyromaTool(ToolBase):
    pass

Rates the quality of Python packages based on packaging best practices. Install with pip install prospector[with_pyroma].

class PyrightTool(ToolBase):
    pass

Microsoft's fast static type checker for Python. Install with pip install prospector[with_pyright].

class MypyTool(ToolBase):
    pass

Static type checker for Python that uses type hints. Install with pip install prospector[with_mypy].

class BanditTool(ToolBase):
    pass

Security-focused linter that identifies common security issues in Python code. Install with pip install prospector[with_bandit].

class RuffTool(ToolBase):
    pass

Fast Python linter and code formatter written in Rust. Install with pip install prospector[with_ruff].

Usage Examples

Using Tool Registry

from prospector import tools

# Get all available tools
print("Available tools:")
for name, tool_class in tools.TOOLS.items():
    print(f"  {name}: {tool_class.__name__}")

# Check default tools
print(f"\nDefault tools: {tools.DEFAULT_TOOLS}")

# Check for deprecated names
if "pep8" in tools.DEPRECATED_TOOL_NAMES:
    new_name = tools.DEPRECATED_TOOL_NAMES["pep8"]
    print(f"pep8 is deprecated, use {new_name} instead")

Creating and Configuring Tools

from prospector import tools
from prospector.config import ProspectorConfig
from prospector.finder import FileFinder
from pathlib import Path

# Create configuration and file finder
config = ProspectorConfig()
finder = FileFinder(Path("."))

# Create a specific tool
pylint_tool = tools.TOOLS["pylint"]()

# Configure the tool
config_result = pylint_tool.configure(config, finder)
if config_result:
    config_source, config_messages = config_result
    if config_source:
        print(f"Pylint configured from: {config_source}")
    if config_messages:
        for msg in config_messages:
            print(f"Config warning: {msg.message}")

# Run the tool
messages = pylint_tool.run(finder)
print(f"Pylint found {len(messages)} issues")

Implementing a Custom Tool

from prospector.tools.base import ToolBase
from prospector.finder import FileFinder
from prospector.message import Message, Location
from typing import TYPE_CHECKING, Optional, Iterable, Union
from pathlib import Path

if TYPE_CHECKING:
    from prospector.config import ProspectorConfig

class CustomTool(ToolBase):
    """Example custom analysis tool"""
    
    def configure(self, prospector_config: "ProspectorConfig", found_files: FileFinder) -> Optional[tuple[Optional[Union[str, Path]], Optional[Iterable[Message]]]]:
        # No external configuration needed
        return None
    
    def run(self, found_files: FileFinder) -> list[Message]:
        messages = []
        
        # Analyze each Python module
        for module_path in found_files.iter_all_modules():
            try:
                with open(module_path, 'r', encoding='utf-8') as f:
                    lines = f.readlines()
                
                # Check for TODO comments
                for line_num, line in enumerate(lines, 1):
                    if 'TODO' in line:
                        location = Location(
                            path=module_path,
                            module=None,
                            function=None,
                            line=line_num,
                            character=line.find('TODO')
                        )
                        
                        message = Message(
                            source="custom-tool",
                            code="TODO",
                            location=location,
                            message="TODO comment found",
                            is_fixable=False
                        )
                        messages.append(message)
                        
            except (OSError, UnicodeDecodeError):
                # Skip files that can't be read
                continue
                
        return messages

# Register custom tool (in practice, this would be done in __init__.py)
# tools.TOOLS["custom"] = CustomTool

Working with Optional Tools

from prospector import tools

def check_optional_tool(tool_name: str):
    """Check if an optional tool is available"""
    try:
        tool_class = tools.TOOLS[tool_name]
        tool = tool_class()
        print(f"{tool_name} is available")
        return True
    except Exception as e:
        print(f"{tool_name} is not available: {e}")
        return False

# Check availability of optional tools
optional_tools = ["mypy", "bandit", "vulture", "pyroma", "pyright", "ruff"]
for tool_name in optional_tools:
    check_optional_tool(tool_name)

Tool Configuration Discovery

from prospector import tools
from prospector.config import ProspectorConfig
from prospector.finder import FileFinder
from pathlib import Path

config = ProspectorConfig()
finder = FileFinder(Path("."))

# Check how each tool gets configured
for tool_name in tools.DEFAULT_TOOLS:
    tool_class = tools.TOOLS[tool_name]
    tool = tool_class()
    
    try:
        result = tool.configure(config, finder)
        if result:
            config_source, config_messages = result
            if config_source:
                print(f"{tool_name}: configured from {config_source}")
            else:
                print(f"{tool_name}: using defaults")
                
            if config_messages:
                print(f"  {len(list(config_messages))} config messages")
        else:
            print(f"{tool_name}: no configuration")
            
    except Exception as e:
        print(f"{tool_name}: configuration error - {e}")

Analyzing Inline Ignore Directives

from prospector.tools.base import ToolBase

# Create a tool instance to test ignore parsing
tool = ToolBase()  # This would normally be a concrete tool

# Test lines with ignore directives
test_lines = [
    "print('hello')  # noqa",
    "import os  # noqa: F401",
    "x = 1  # noqa: E501,W292",
    "def func():  # pylint: disable=invalid-name",
    "regular_line_no_ignore()"
]

for line in test_lines:
    ignored_codes = tool.get_ignored_codes(line)
    if ignored_codes:
        print(f"Line: {line}")
        print(f"  Ignores: {ignored_codes}")
    else:
        print(f"Line: {line} (no ignores)")

Install with Tessl CLI

npx tessl i tessl/pypi-prospector

docs

analysis-runner.md

configuration.md

exceptions.md

file-finding.md

formatters.md

index.md

messages.md

profiles.md

tools.md

tile.json