Skip to content
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
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ repos:
- id: debug-statements
- id: check-added-large-files
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.15.22
rev: v0.16.0
hooks:
- id: ruff-format
- id: ruff
- id: ruff-check
args:
- --fix
- --ignore=E501
Expand Down
7 changes: 4 additions & 3 deletions prospector/autodetect.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from __future__ import annotations

import os
import re
import warnings
from pathlib import Path
from typing import Union

from requirements_detector import find_requirements
from requirements_detector.detect import RequirementsNotFound
Expand Down Expand Up @@ -69,7 +70,7 @@ def find_from_path(path: Path) -> set[str]:
return names


def find_from_requirements(path: Union[str, Path]) -> set[str]:
def find_from_requirements(path: str | Path) -> set[str]:
reqs = find_requirements(path)
names: set[str] = set()
for requirement in reqs:
Expand All @@ -78,7 +79,7 @@ def find_from_requirements(path: Union[str, Path]) -> set[str]:
return names


def autodetect_libraries(path: Union[str, Path]) -> set[str]:
def autodetect_libraries(path: str | Path) -> set[str]:
if os.path.isfile(path):
path = os.path.dirname(path)
if path == "":
Expand Down
11 changes: 6 additions & 5 deletions prospector/blender.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,23 @@
# the same line. For example, both pyflakes and pylint will generate an
# "Unused Import" warning on the same line. This is obviously redundant, so we
# remove duplicates.
from __future__ import annotations

import pkgutil
from collections import defaultdict
from pathlib import Path
from typing import Optional

import yaml

from prospector.message import Message

__all__ = (
"blend",
"BLEND_COMBOS",
"blend",
)


def blend_line(messages: list[Message], blend_combos: Optional[list[list[tuple[str, str]]]] = None) -> list[Message]:
def blend_line(messages: list[Message], blend_combos: list[list[tuple[str, str]]] | None = None) -> list[Message]:
"""
Given a list of messages on the same line, blend them together so that we
end up with one message per actual problem. Note that we can still return
Expand Down Expand Up @@ -81,11 +82,11 @@ def blend_line(messages: list[Message], blend_combos: Optional[list[list[tuple[s
return [m for m in blended if not getattr(m, "used", False)]


def blend(messages: list[Message], blend_combos: Optional[list[list[tuple[str, str]]]] = None) -> list[Message]:
def blend(messages: list[Message], blend_combos: list[list[tuple[str, str]]] | None = None) -> list[Message]:
blend_combos = blend_combos or BLEND_COMBOS

# group messages by file and then line number
msgs_grouped: dict[Optional[Path], dict[Optional[int], list[Message]]] = defaultdict(lambda: defaultdict(list))
msgs_grouped: dict[Path | None, dict[int | None, list[Message]]] = defaultdict(lambda: defaultdict(list))

for message in messages:
msgs_grouped[message.location.path][message.location.line].append(
Expand Down
23 changes: 12 additions & 11 deletions prospector/config/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
from __future__ import annotations

import os
import re
import sys
from pathlib import Path
from typing import Any, Callable, Optional, Union
from typing import Any, Callable

import setoptconf.config

Expand Down Expand Up @@ -32,7 +34,7 @@ class ProspectorConfig:
# Also the 'too many instance attributes' warning is ignored, as this
# is a config object and its sole purpose is to hold many properties!

def __init__(self, workdir: Optional[Path] = None):
def __init__(self, workdir: Path | None = None):
self.config, self.arguments = self._configure_prospector()
self.paths = self._get_work_path(self.config, self.arguments)
self.explicit_file_mode = all(p.is_file for p in self.paths)
Expand All @@ -42,7 +44,7 @@ def __init__(self, workdir: Optional[Path] = None):
self.libraries = self._find_used_libraries(self.config, self.profile)
self.tools_to_run = self._determine_tool_runners(self.config, self.profile)
self.ignores = self._determine_ignores(self.config, self.profile, self.libraries)
self.configured_by: dict[str, Optional[Union[str, Path]]] = {}
self.configured_by: dict[str, str | Path | None] = {}
self.messages: list[Message] = []

def make_exclusion_filter(self) -> Callable[[Path], bool]:
Expand Down Expand Up @@ -130,7 +132,7 @@ def _get_work_path(self, config: setoptconf.config.Configuration, arguments: dic

def _get_profile(
self, workdir: Path, config: setoptconf.config.Configuration
) -> tuple[ProspectorProfile, Optional[str]]:
) -> tuple[ProspectorProfile, str | None]:
# Use the specified profiles
profile_provided = False
if len(config.profiles) > 0:
Expand All @@ -139,7 +141,7 @@ def _get_profile(

# if there is a '.prospector.ya?ml' or a '.prospector/prospector.ya?ml' or equivalent landscape config
# file then we'll include that
profile_name: Union[None, str, Path] = None
profile_name: None | str | Path = None
if not profile_provided:
for possible_profile in AUTO_LOADED_PROFILES:
prospector_yaml = os.path.join(workdir, possible_profile)
Expand Down Expand Up @@ -222,12 +224,11 @@ def _get_profile(
return profile, strictness

def _find_used_libraries(self, config: setoptconf.config.Configuration, profile: ProspectorProfile) -> list[str]:
libraries = []
libraries: list[str] = []

# Bring in adaptors that we automatically detect are needed
if config.autodetect and profile.autodetect is True:
for found_dep in autodetect_libraries(self.workdir):
libraries.append(found_dep)
libraries.extend(autodetect_libraries(self.workdir))

# Bring in adaptors for the specified libraries
for name in set(config.uses + profile.uses):
Expand Down Expand Up @@ -267,7 +268,7 @@ def _determine_tool_runners(self, config: setoptconf.config.Configuration, profi
# remove it from the list to run
to_run.remove(tool)

return sorted(list(to_run))
return sorted(to_run)

def _determine_ignores(
self, config: setoptconf.config.Configuration, profile: ProspectorProfile, libraries: list[str]
Expand All @@ -288,7 +289,7 @@ def _determine_ignores(
boundary = r"(^|/|\\)%s(/|\\|$)"
for ignore_path in config.ignore_paths + profile.ignore_paths:
ignore_path = str(ignore_path)
if ignore_path.endswith("/") or ignore_path.endswith("\\"):
if ignore_path.endswith(("/", "\\")):
ignore_path = ignore_path[:-1]
ignores.append(re.compile(boundary % re.escape(ignore_path)))

Expand Down Expand Up @@ -326,7 +327,7 @@ def tool_options(self, tool_name: str) -> dict[str, Any]:
return {}
return tool.get("options", {})

def external_config_location(self, tool_name: str) -> Optional[Path]:
def external_config_location(self, tool_name: str) -> Path | None:
return getattr(self.config, f"{tool_name}_config_file", None)

@property
Expand Down
5 changes: 3 additions & 2 deletions prospector/config/configuration.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

import importlib.metadata
from typing import Optional

import setoptconf as soc

Expand Down Expand Up @@ -100,7 +101,7 @@ def build_default_sources() -> list[soc.Source]:


def build_command_line_source(
prog: Optional[str] = None, description: Optional[str] = "Performs static analysis of Python code"
prog: str | None = None, description: str | None = "Performs static analysis of Python code"
) -> soc.CommandLineSource:
parser_options = {}
if prog is not None:
Expand Down
6 changes: 4 additions & 2 deletions prospector/finder.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from __future__ import annotations

from collections.abc import Iterable, Iterator
from pathlib import Path
from typing import Callable, Optional
from typing import Callable

from prospector.exceptions import PermissionMissing
from prospector.pathutils import is_python_module, is_python_package, is_virtualenv
Expand All @@ -18,7 +20,7 @@ class FileFinder:
is basically to know which files to pass to which tools to be inspected.
"""

def __init__(self, *provided_paths: Path, exclusion_filters: Optional[Iterable[Callable[[Path], bool]]] = None):
def __init__(self, *provided_paths: Path, exclusion_filters: Iterable[Callable[[Path], bool]] | None = None):
"""
:param provided_paths:
A list of Path objects to search for files and modules - can be either directories or files
Expand Down
6 changes: 4 additions & 2 deletions prospector/formatters/base.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
from __future__ import annotations

from abc import ABC, abstractmethod

from prospector.profiles.profile import ProspectorProfile

__all__ = ("Formatter",)

from pathlib import Path
from typing import Any, Optional
from typing import Any

from prospector.message import Location, Message

Expand All @@ -16,7 +18,7 @@ def __init__(
summary: dict[str, Any],
messages: list[Message],
profile: ProspectorProfile,
paths_relative_to: Optional[Path] = None,
paths_relative_to: Path | None = None,
) -> None:
self.summary = summary
self.messages = messages
Expand Down
7 changes: 4 additions & 3 deletions prospector/formatters/base_summary.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

import os
from pathlib import Path
from typing import Optional

from prospector.formatters.base import Formatter
from prospector.message import Location, Message
Expand Down Expand Up @@ -47,7 +48,7 @@ def render_profile(self) -> str:

return "\n".join(output)

def get_ci_annotation(self, message: Message) -> Optional[str]:
def get_ci_annotation(self, message: Message) -> str | None:
intro = (
f"({message.source})"
if message.code is None
Expand All @@ -63,7 +64,7 @@ def get_ci_annotation(self, message: Message) -> Optional[str]:
return github_message
return None

def _get_ci_prefix(self, location: Location, title: str) -> Optional[str]:
def _get_ci_prefix(self, location: Location, title: str) -> str | None:
if location.path is None:
return None
if os.environ.get("GITHUB_ACTIONS") == "true":
Expand Down
5 changes: 3 additions & 2 deletions prospector/formatters/grouped.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

from collections import defaultdict
from pathlib import Path
from typing import Optional

from prospector.formatters.text import TextFormatter
from prospector.message import Message
Expand All @@ -16,7 +17,7 @@ def render_messages(self) -> str:
"",
]

groups: dict[Path, dict[Optional[int], list[Message]]] = defaultdict(lambda: defaultdict(list))
groups: dict[Path, dict[int | None, list[Message]]] = defaultdict(lambda: defaultdict(list))

for message in self.messages:
groups[self._make_path(message.location)][message.location.line].append(message)
Expand Down
43 changes: 22 additions & 21 deletions prospector/message.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
from __future__ import annotations

from pathlib import Path
from typing import Optional, Union


class Location:
_path: Optional[Path]
_path: Path | None

def __init__(
self,
path: Optional[Union[Path, str]],
module: Optional[str],
function: Optional[str],
line: Optional[int],
character: Optional[int],
line_end: Optional[int] = None,
character_end: Optional[int] = None,
path: Path | str | None,
module: str | None,
function: str | None,
line: int | None,
character: int | None,
line_end: int | None = None,
character_end: int | None = None,
):
if isinstance(path, Path):
self._path = path.absolute()
Expand All @@ -31,13 +32,13 @@ def __init__(
self.character_end = character_end

@property
def path(self) -> Optional[Path]:
def path(self) -> Path | None:
return self._path

def absolute_path(self) -> Optional[Path]:
def absolute_path(self) -> Path | None:
return self._path

def relative_path(self, root: Optional[Path]) -> Optional[Path]:
def relative_path(self, root: Path | None) -> Path | None:
if self._path is None:
return None
if root is None:
Expand All @@ -55,9 +56,9 @@ def __eq__(self, other: object) -> bool:
return False
return self._path == other._path and self.line == other.line and self.character == other.character

def __lt__(self, other: "Location") -> bool:
def __lt__(self, other: Location) -> bool:
if not isinstance(other, Location):
raise ValueError
raise TypeError

if self._path is None and other._path is None:
return False
Expand All @@ -79,7 +80,7 @@ def __init__(
code: str,
location: Location,
message: str,
doc_url: Optional[str] = None,
doc_url: str | None = None,
is_fixable: bool = False,
):
self.source = source
Expand All @@ -99,21 +100,21 @@ def __eq__(self, other: object) -> bool:
return self.code == other.code
return False

def __lt__(self, other: "Message") -> bool:
def __lt__(self, other: Message) -> bool:
if self.location == other.location:
return self.code < other.code
return self.location < other.location


def make_tool_error_message(
filepath: Union[Path, str],
filepath: Path | str,
source: str,
code: str,
message: str,
line: Optional[int] = None,
character: Optional[int] = None,
module: Optional[str] = None,
function: Optional[str] = None,
line: int | None = None,
character: int | None = None,
module: str | None = None,
function: str | None = None,
) -> Message:
location = Location(path=filepath, module=module, function=function, line=line, character=character)
return Message(source=source, code=code, location=location, message=message)
7 changes: 4 additions & 3 deletions prospector/postfilter.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

from pathlib import Path
from typing import Optional

from prospector.message import Message
from prospector.suppression import get_suppressions
Expand All @@ -9,9 +10,9 @@
def filter_messages(
filepaths: list[Path],
messages: list[Message],
tools: Optional[dict[str, ToolBase]] = None,
tools: dict[str, ToolBase] | None = None,
blending: bool = False,
blend_combos: Optional[list[list[tuple[str, str]]]] = None,
blend_combos: list[list[tuple[str, str]]] | None = None,
) -> list[Message]:
"""
This method post-processes all messages output by all tools, in order to filter
Expand Down
Loading
Loading