Skip to content
Open
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 @@ -16,7 +16,7 @@ repos:
- id: detect-private-key

- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.15.22
rev: v0.16.1
hooks:
- id: ruff-check
args: [--fix]
Expand All @@ -28,7 +28,7 @@ repos:
- id: actionlint

- repo: https://github.com/zizmorcore/zizmor-pre-commit
rev: v1.28.0
rev: v1.29.0
hooks:
- id: zizmor
args: [--no-progress]
2 changes: 1 addition & 1 deletion src/env_auditor/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from __future__ import annotations

from importlib.metadata import version, PackageNotFoundError
from importlib.metadata import PackageNotFoundError, version

try:
__version__: str = version("env-auditor")
Expand Down
11 changes: 4 additions & 7 deletions src/env_auditor/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import re
import sys
from pathlib import Path
from typing import NoReturn, Optional
from typing import NoReturn

from env_auditor import __version__
from env_auditor.colors import supports_color
Expand All @@ -19,7 +19,6 @@
from env_auditor.reporter import render_json, render_text
from env_auditor.scanner import scan_directory


# ──────────────────────────────────────────────────────────────────────────────
# Argument parser
# ──────────────────────────────────────────────────────────────────────────────
Expand Down Expand Up @@ -130,7 +129,7 @@ def _resolve_scan_root(raw_path: str) -> Path:
Raises:
SystemExit(2): If the path does not exist or is not a directory.
"""
resolved: Optional[Path] = None
resolved: Path | None = None
try:
resolved = Path(raw_path).resolve()
except (OSError, ValueError) as exc:
Expand Down Expand Up @@ -319,9 +318,7 @@ def _run_audit(
# "Result: PASS" while the process still exited 1.
effective_undoc = diff.undocumented - ignore_keys
effective_stale = diff.stale - ignore_keys
if effective_undoc or diff.required_missing:
exit_code = 1
elif cfg.strict and effective_stale:
if effective_undoc or diff.required_missing or cfg.strict and effective_stale:
exit_code = 1
else:
exit_code = 0
Expand Down Expand Up @@ -357,7 +354,7 @@ def _run_audit(
# ──────────────────────────────────────────────────────────────────────────────


def main(argv: Optional[list[str]] = None) -> None:
def main(argv: list[str] | None = None) -> None:
"""Parse arguments, run the audit, and exit with the appropriate code.

Exit codes:
Expand Down
30 changes: 17 additions & 13 deletions src/env_auditor/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@

import re
import sys
from dataclasses import dataclass, field, replace as dataclass_replace
from dataclasses import dataclass, field
from dataclasses import replace as dataclass_replace
from pathlib import Path
from typing import Any, Optional
from typing import Any

# Config file is searched in this order within the scan root.
CONFIG_FILENAMES = (".env-auditorrc", "env-auditor.toml", "pyproject.toml")
Expand Down Expand Up @@ -107,7 +108,7 @@ def load_config_from_file(path: Path) -> EnvAuditorConfig:
return _dict_to_config(raw, path)


def _read_config_raw(path: Path) -> tuple[Optional[dict[str, Any]], bool]:
def _read_config_raw(path: Path) -> tuple[dict[str, Any] | None, bool]:
"""Validate size, parse path as TOML, and return its env-auditor section.

Shared by :func:`load_config` (auto-discovery) and
Expand Down Expand Up @@ -155,7 +156,7 @@ def _read_config_raw(path: Path) -> tuple[Optional[dict[str, Any]], bool]:
return raw, False


def _parse_toml_file(path: Path, is_pyproject: bool) -> Optional[dict[str, Any]]:
def _parse_toml_file(path: Path, is_pyproject: bool) -> dict[str, Any] | None:
"""Parse *path* as TOML and return the env-auditor section, or None.

Uses stdlib ``tomllib`` (Python 3.11+) with ``tomli`` fallback,
Expand Down Expand Up @@ -250,9 +251,12 @@ def _minimal_toml_parse(path: Path) -> dict[str, Any]:
elif value.startswith("["):
# Use pre-compiled regex — not constructed from user input
current_node[key] = _LIST_ITEMS_RE.findall(value)
elif value.startswith('"') and value.endswith('"'):
current_node[key] = value[1:-1]
elif value.startswith("'") and value.endswith("'"):
elif (
value.startswith('"')
and value.endswith('"')
or value.startswith("'")
and value.endswith("'")
):
current_node[key] = value[1:-1]
else:
current_node[key] = value
Expand Down Expand Up @@ -340,12 +344,12 @@ def _dict_to_config(raw: dict[str, Any], source: Path) -> EnvAuditorConfig:
def merge_cli_into_config(
cfg: EnvAuditorConfig,
*,
env_files: Optional[list[str]] = None,
exclude_dirs: Optional[list[str]] = None,
ignore_stale: Optional[bool] = None,
ignore_missing: Optional[bool] = None,
strict: Optional[bool] = None,
output_format: Optional[str] = None,
env_files: list[str] | None = None,
exclude_dirs: list[str] | None = None,
ignore_stale: bool | None = None,
ignore_missing: bool | None = None,
strict: bool | None = None,
output_format: str | None = None,
) -> EnvAuditorConfig:
"""Apply CLI overrides onto *cfg*, returning a new merged config.

Expand Down
3 changes: 1 addition & 2 deletions src/env_auditor/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import sys
from dataclasses import dataclass, field
from pathlib import Path
from typing import Optional

# Pre-compiled constants — never constructed from user input.
_VALID_KEY_RE: re.Pattern[str] = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$")
Expand Down Expand Up @@ -37,7 +36,7 @@ def empty_keys(self) -> frozenset[str]:
return frozenset(k for k, v in self.keys_with_values.items() if v == "")


def parse_env_file(path: Path) -> Optional[ParsedEnvFile]:
def parse_env_file(path: Path) -> ParsedEnvFile | None:
"""Parse a dotenv-format file and return its keys.

Sensitive value protection: values are stored only to detect emptiness.
Expand Down
4 changes: 2 additions & 2 deletions src/env_auditor/patterns.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from __future__ import annotations

import re
from collections.abc import Sequence
from dataclasses import dataclass, field
from typing import Optional, Sequence

# Env var name: uppercase, starts with letter, underscores/digits allowed
ENV_VAR_NAME = r"([A-Z][A-Z0-9_]*)"
Expand All @@ -20,7 +20,7 @@ class LanguagePattern:
extensions: Sequence[str]
static_patterns: Sequence[re.Pattern[str]]
dynamic_patterns: Sequence[re.Pattern[str]] = field(default_factory=list)
line_filter: Optional[re.Pattern[str]] = None
line_filter: re.Pattern[str] | None = None
"""If set, static_patterns/dynamic_patterns are only applied to lines that
match this filter first. Used by Docker: the KEY= continuation pattern
(which has no anchor of its own) must not fire on RUN/LABEL/CMD lines that
Expand Down
6 changes: 3 additions & 3 deletions src/env_auditor/reporter.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from __future__ import annotations

import json
from typing import Any, Optional
from typing import Any

from env_auditor.colors import get_colors
from env_auditor.differ import DiffResult
Expand All @@ -16,7 +16,7 @@ def render_text(
use_color: bool = True,
ignore_stale: bool = False,
ignore_missing: bool = False,
ignore_keys: Optional[set[str]] = None,
ignore_keys: set[str] | None = None,
) -> str:
"""Render a human-readable audit report.

Expand Down Expand Up @@ -124,7 +124,7 @@ def render_json(
passed: bool,
ignore_stale: bool = False,
ignore_missing: bool = False,
ignore_keys: Optional[set[str]] = None,
ignore_keys: set[str] | None = None,
) -> str:
"""Render a machine-readable JSON audit report.

Expand Down
3 changes: 1 addition & 2 deletions src/env_auditor/scanner.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import sys
from dataclasses import dataclass, field
from pathlib import Path
from typing import Optional

from env_auditor.patterns import (
DOCKERFILE_PATTERN,
Expand Down Expand Up @@ -109,7 +108,7 @@ def sanitize_raw(text: str) -> str:

def scan_directory(
root: Path,
extra_exclude: Optional[list[Path]] = None,
extra_exclude: list[Path] | None = None,
) -> ScanResult:
"""Walk *root* recursively and extract all env var references.

Expand Down
1 change: 0 additions & 1 deletion tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@

from env_auditor.cli import main


# ──────────────────────────────────────────────────────────────────────────────
# Helpers
# ──────────────────────────────────────────────────────────────────────────────
Expand Down
1 change: 0 additions & 1 deletion tests/test_colors.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from __future__ import annotations


from env_auditor.colors import Colors, NoColors, get_colors, supports_color


Expand Down
2 changes: 0 additions & 2 deletions tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import textwrap
from pathlib import Path


from env_auditor.config import (
CONFIG_FILE_SIZE_LIMIT,
EnvAuditorConfig,
Expand All @@ -15,7 +14,6 @@
merge_cli_into_config,
)


# ──────────────────────────────────────────────────────────────────────────────
# Helpers
# ──────────────────────────────────────────────────────────────────────────────
Expand Down
8 changes: 4 additions & 4 deletions tests/test_differ.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from __future__ import annotations


from env_auditor.differ import diff_keys


Expand Down Expand Up @@ -90,10 +89,11 @@ def test_returns_frozensets():


def test_ignore_keys_removes_from_undocumented():
import json

from env_auditor.differ import diff_keys
from env_auditor.reporter import render_json, render_text
from env_auditor.scanner import ScanResult
from env_auditor.reporter import render_text, render_json
import json

code = frozenset({"FOO", "IGNORED"})
documented = frozenset({"FOO"})
Expand All @@ -113,8 +113,8 @@ def test_ignore_keys_removes_from_undocumented():

def test_ignore_keys_removes_from_stale():
from env_auditor.differ import diff_keys
from env_auditor.scanner import ScanResult
from env_auditor.reporter import render_text
from env_auditor.scanner import ScanResult

code = frozenset({"FOO"})
documented = frozenset({"FOO", "STALE_BUT_IGNORED"})
Expand Down
1 change: 0 additions & 1 deletion tests/test_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@

from env_auditor.parser import parse_env_file, parse_env_files


# ──────────────────────────────────────────────────────────────────────────────
# Helpers
# ──────────────────────────────────────────────────────────────────────────────
Expand Down
6 changes: 3 additions & 3 deletions tests/test_scanner.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@

import pytest

from env_auditor.scanner import scan_directory, FILE_SIZE_LIMIT

from env_auditor.scanner import FILE_SIZE_LIMIT, scan_directory

# ──────────────────────────────────────────────────────────────────────────────
# Helpers
Expand Down Expand Up @@ -382,9 +381,10 @@ def fake_stat(self):

def test_scan_file_unicode_error_handled(tmp_path, monkeypatch):
"""Files that raise on read are skipped and logged."""
import env_auditor.scanner as sc
from pathlib import Path

import env_auditor.scanner as sc

p = tmp_path / "bad.py"
p.write_text('os.environ["GOOD_KEY"]\n', encoding="utf-8")

Expand Down
Loading