Thanks to visit codestin.com
Credit goes to github.com

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
20 changes: 20 additions & 0 deletions .ruff.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,48 @@ exclude = ["tools/to_schemastore.py", "tests/invalid-examples"]
# --- Linting config ---
[lint]
extend-select = [
"ARG", # flake8-unused-arguments
"B", # flake8-bugbear
"BLE", # flake8-blind-except
"C4", # flake8-comprehensions
"C90", # McCabe cyclomatic complexity
"DTZ", # flake8-datetimez
"EM", # flake8-errmsg
"EXE", # flake8-executable
"FA", # flake8-future-annotations
"FBT", # flake8-boolean-trap
"FLY", # flynt
"FURB", # refurb
"I", # isort
"ICN", # flake8-import-conventions
"INT", # flake8-gettext
"ISC", # flake8-implicit-str-concat
"LOG", # flake8-logging
"PERF", # Perflint
"PGH", # pygrep-hooks
"PIE", # flake8-pie
"PL", # Pylint
"PT", # flake8-pytest-style
"PYI", # flake8-pyi
"Q", # flake8-quotes
"RET", # flake8-return
"RSE", # flake8-raise
"RUF", # Ruff-specific rules
"S", # flake8-bandit
"SIM", # flake8-simplify
"SLOT", # flake8-slots
"T10", # flake8-debugger
"TC", # flake8-type-checking
"TCH", # flake8-type-checking
"TRY", # tryceratops
"UP", # pyupgrade
"YTT", # flake8-2020
]
ignore = [
"PLC0415", # import at top of file
"RSE102", # parens on exception raise
"S101", # assert is used by mypy and pytest
"TRY401", # redundant logging message, TODO check
]

[lint.per-file-ignores]
Expand Down
2 changes: 1 addition & 1 deletion docs/_gendocs.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
__location__ = Path(__file__).parent


def gen_stubs(module_dir: str, output_dir: str):
def gen_stubs(module_dir: str, output_dir: str): # noqa: ARG001
shutil.rmtree(output_dir, ignore_errors=True) # Always start fresh
out = Path(output_dir)
out.mkdir(parents=True, exist_ok=True)
Expand Down
3 changes: 2 additions & 1 deletion src/validate_pyproject/_tomllib.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
)
from toml import loads # type: ignore[no-redef]
except ImportError as ex:
raise ImportError("Please install `tomli` (TOML parser)") from ex
msg = "Please install `tomli` (TOML parser)"
raise ImportError(msg) from ex


__all__ = [
Expand Down
28 changes: 13 additions & 15 deletions src/validate_pyproject/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
Retrieve JSON schemas for validating dicts representing a ``pyproject.toml`` file.
"""

from __future__ import annotations

import json
import logging
import sys
Expand All @@ -11,14 +13,10 @@
from types import MappingProxyType, ModuleType
from typing import (
Callable,
Dict,
Iterator,
Mapping,
Optional,
Sequence,
Tuple,
TypeVar,
Union,
)

import fastjsonschema as FJS
Expand All @@ -37,7 +35,7 @@
if sys.version_info >= (3, 9): # pragma: no cover
from importlib.resources import files

def read_text(package: Union[str, ModuleType], resource: str) -> str:
def read_text(package: str | ModuleType, resource: str) -> str:
""":meta private:"""
return files(package).joinpath(resource).read_text(encoding="utf-8")

Expand Down Expand Up @@ -94,8 +92,8 @@ class SchemaRegistry(Mapping[str, Schema]):
:meta private: (low level detail)
"""

def __init__(self, plugins: Sequence["PluginProtocol"] = ()):
self._schemas: Dict[str, Tuple[str, str, Schema]] = {}
def __init__(self, plugins: Sequence[PluginProtocol] = ()):
self._schemas: dict[str, tuple[str, str, Schema]] = {}
# (which part of the TOML, who defines, schema)

top_level = typing.cast("dict", load(TOP_LEVEL_SCHEMA)) # Make it mutable
Expand All @@ -114,7 +112,7 @@ def __init__(self, plugins: Sequence["PluginProtocol"] = ()):
# Add tools using Plugins
for plugin in plugins:
if plugin.tool:
allow_overwrite: Optional[str] = None
allow_overwrite: str | None = None
if plugin.tool in tool_properties:
_logger.warning(f"{plugin} overwrites `tool.{plugin.tool}` schema")
allow_overwrite = plugin.schema.get("$id")
Expand Down Expand Up @@ -150,7 +148,7 @@ def _ensure_compatibility(
self,
reference: str,
schema: Schema,
allow_overwrite: Optional[str] = None,
allow_overwrite: str | None = None,
) -> Schema:
if "$id" not in schema or not schema["$id"]:
raise errors.SchemaMissingId(reference or "<extra>")
Expand Down Expand Up @@ -208,19 +206,19 @@ def __getitem__(self, key: str) -> Callable[[str], Schema]:


class Validator:
_plugins: Sequence["PluginProtocol"]
_plugins: Sequence[PluginProtocol]

def __init__(
self,
plugins: Union[Sequence["PluginProtocol"], AllPlugins] = ALL_PLUGINS,
plugins: Sequence[PluginProtocol] | AllPlugins = ALL_PLUGINS,
format_validators: Mapping[str, FormatValidationFn] = FORMAT_FUNCTIONS,
extra_validations: Sequence[ValidationFn] = EXTRA_VALIDATIONS,
*,
extra_plugins: Sequence["PluginProtocol"] = (),
extra_plugins: Sequence[PluginProtocol] = (),
):
self._code_cache: Optional[str] = None
self._cache: Optional[ValidationFn] = None
self._schema: Optional[Schema] = None
self._code_cache: str | None = None
self._cache: ValidationFn | None = None
self._schema: Schema | None = None

# Let's make the following options readonly
self._format_validators = MappingProxyType(format_validators)
Expand Down
14 changes: 9 additions & 5 deletions src/validate_pyproject/caching.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
# This module is intentionally kept minimal,
# so that it can be imported without triggering imports outside stdlib.
from __future__ import annotations

import hashlib
import io
import logging
import os
from pathlib import Path
from typing import Callable, Optional, Union
from typing import TYPE_CHECKING, Callable, Union

if TYPE_CHECKING:
import io

PathLike = Union[str, "os.PathLike[str]"]
_logger = logging.getLogger(__name__)
Expand All @@ -14,8 +18,8 @@
def as_file(
fn: Callable[[str], io.StringIO],
arg: str,
cache_dir: Optional[PathLike] = None,
) -> Union[io.StringIO, io.BufferedReader]:
cache_dir: PathLike | None = None,
) -> io.StringIO | io.BufferedReader:
"""
Cache the result of calling ``fn(arg)`` into a file inside ``cache_dir``.
The file name is derived from ``arg``.
Expand All @@ -36,7 +40,7 @@ def as_file(
return open(cache_path, "rb")


def path_for(arbitrary_id: str, cache: Optional[PathLike] = None) -> Optional[Path]:
def path_for(arbitrary_id: str, cache: PathLike | None = None) -> Path | None:
cache_dir = cache or os.getenv("VALIDATE_PYPROJECT_CACHE_REMOTE")
if not cache_dir:
return None
Expand Down
40 changes: 20 additions & 20 deletions src/validate_pyproject/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,22 @@

# ruff: noqa: C408
# Unnecessary `dict` call (rewrite as a literal)
from __future__ import annotations

import argparse
import io
import json
import logging
import sys
from contextlib import contextmanager
from itertools import chain
from textwrap import dedent, wrap
from typing import (
TYPE_CHECKING,
Callable,
Dict,
Generator,
Iterator,
List,
NamedTuple,
Sequence,
Tuple,
Type,
TypeVar,
)

Expand All @@ -34,6 +31,9 @@
from .plugins import list_from_entry_points as list_plugins_from_entry_points
from .remote import RemotePlugin, load_store

if TYPE_CHECKING:
import io

_logger = logging.getLogger(__package__)
T = TypeVar("T", bound=NamedTuple)

Expand All @@ -53,7 +53,7 @@ def critical_logging() -> Generator[None, None, None]:

_STDIN = argparse.FileType("r")("-")

META: Dict[str, dict] = {
META: dict[str, dict] = {
"version": dict(
flags=("-V", "--version"),
action="version",
Expand Down Expand Up @@ -116,15 +116,15 @@ def critical_logging() -> Generator[None, None, None]:


class CliParams(NamedTuple):
input_file: List[io.TextIOBase]
plugins: List[PluginWrapper]
tool: List[str]
input_file: list[io.TextIOBase]
plugins: list[PluginWrapper]
tool: list[str]
store: str
loglevel: int = logging.WARNING
dump_json: bool = False


def __meta__(plugins: Sequence[PluginProtocol]) -> Dict[str, dict]:
def __meta__(plugins: Sequence[PluginProtocol]) -> dict[str, dict]:
"""'Hyper parameters' to instruct :mod:`argparse` how to create the CLI"""
meta = {k: v.copy() for k, v in META.items()}
meta["enable"]["choices"] = {p.tool for p in plugins}
Expand All @@ -137,8 +137,8 @@ def parse_args(
args: Sequence[str],
plugins: Sequence[PluginProtocol],
description: str = "Validate a given TOML file",
get_parser_spec: Callable[[Sequence[PluginProtocol]], Dict[str, dict]] = __meta__,
params_class: Type[T] = CliParams, # type: ignore[assignment]
get_parser_spec: Callable[[Sequence[PluginProtocol]], dict[str, dict]] = __meta__,
params_class: type[T] = CliParams, # type: ignore[assignment]
) -> T:
"""Parse command line parameters

Expand Down Expand Up @@ -174,7 +174,7 @@ def select_plugins(
plugins: Sequence[Plugins],
enabled: Sequence[str] = (),
disabled: Sequence[str] = (),
) -> List[Plugins]:
) -> list[Plugins]:
available = list(plugins)
if enabled:
available = [p for p in available if p.tool in enabled]
Expand All @@ -200,13 +200,13 @@ def exceptions2exit() -> Generator[None, None, None]:
except _ExceptionGroup as group:
for prefix, ex in group:
print(prefix)
_logger.error(str(ex) + "\n")
_logger.exception(str(ex) + "\n")
raise SystemExit(1) from None
except _REGULAR_EXCEPTIONS as ex:
_logger.error(str(ex))
_logger.exception(str(ex))
raise SystemExit(1) from None
except Exception as ex: # pragma: no cover
_logger.error(f"{ex.__class__.__name__}: {ex}\n")
_logger.exception(f"{ex.__class__.__name__}: {ex}\n")
_logger.debug("Please check the following information:", exc_info=True)
raise SystemExit(1) from None

Expand Down Expand Up @@ -234,7 +234,7 @@ def run(args: Sequence[str] = ()) -> int:
for file in params.input_file:
try:
_run_on_file(validator, params, file)
except _REGULAR_EXCEPTIONS as ex:
except _REGULAR_EXCEPTIONS as ex: # noqa: PERF203
exceptions.add(f"Invalid {_format_file(file)}", ex)

exceptions.raise_if_any()
Expand Down Expand Up @@ -262,7 +262,7 @@ class Formatter(argparse.RawTextHelpFormatter):
# order to create our own formatter, we are left no choice other then overwrite a
# "private" method considered to be an implementation detail.

def _split_lines(self, text: str, width: int) -> List[str]:
def _split_lines(self, text: str, width: int) -> list[str]:
return list(chain.from_iterable(wrap(x, width) for x in text.splitlines()))


Expand All @@ -289,7 +289,7 @@ def _format_file(file: io.TextIOBase) -> str:


class _ExceptionGroup(Exception):
_members: List[Tuple[str, Exception]]
_members: list[tuple[str, Exception]]

def __init__(self) -> None:
self._members = []
Expand All @@ -298,7 +298,7 @@ def __init__(self) -> None:
def add(self, prefix: str, ex: Exception) -> None:
self._members.append((prefix, ex))

def __iter__(self) -> Iterator[Tuple[str, Exception]]:
def __iter__(self) -> Iterator[tuple[str, Exception]]:
return iter(self._members)

def raise_if_any(self) -> None:
Expand Down
Loading