Honest CI: fix ruff and harden gates #8

Merged
ilia merged 10 commits from chore/honest-ci-ruff into main 2026-08-05 18:19:04 -05:00
16 changed files with 38 additions and 36 deletions
+6 -4
View File
@@ -32,7 +32,7 @@ jobs:
python-ci:
needs: skip-ci-check
if: needs.skip-ci-check.outputs.should-skip != '1'
runs-on: [homelab, self-hosted, linux, python]
runs-on: [homelab, self-hosted, linux]
container:
# node image: actions/checkout@v4 needs Node; install python3 in-job
image: node:20-bookworm
@@ -49,7 +49,7 @@ jobs:
pip install bandit pip-audit ruff --break-system-packages
- name: Ruff lint
run: ruff check . || true
run: python3 -m ruff check .
- name: Bandit (advisory)
run: bandit -r . -q || true
@@ -58,10 +58,12 @@ jobs:
run: pip-audit -r requirements.txt 2>/dev/null || pip-audit 2>/dev/null || true
- name: Pytest
env:
PYTHONPATH: .
run: |
if [ -d tests ] || ls test_*.py *_test.py 2>/dev/null; then
pip install pytest --break-system-packages
pytest -q || true
pip install -r requirements-dev.txt --break-system-packages
python3 -m pytest -q --tb=short
else
echo "No tests found — skip"
fi
+3
View File
@@ -0,0 +1,3 @@
[pytest]
pythonpath = .
testpaths = tests
Regular → Executable
+3 -9
View File
@@ -171,9 +171,7 @@ def text_is_factual(text: str) -> bool:
return True
if DATA_PHONE.search(text) or DATA_MONEY.search(text) or DATA_EMAIL.search(text):
return True
if DATA_ADDRESS.search(text) and DATA_DATE.search(text):
return True
return False
return bool(DATA_ADDRESS.search(text) and DATA_DATE.search(text))
def looks_conversational(text: str) -> bool:
@@ -184,9 +182,7 @@ def looks_conversational(text: str) -> bool:
if text.count("?") >= 2 and len(text) < 500:
return True
# Short back-and-forth without hard facts.
if len(text) < 80 and not has_data_signals(text):
return True
return False
return len(text) < 80 and not has_data_signals(text)
def is_name_list_message(text: str) -> bool:
@@ -295,9 +291,7 @@ def msg_selection(
if latest_draft_files and fid in latest_draft_files:
files[fid] = True
continue
if is_doc(f) or informative_filename(f.get("name") or ""):
files[fid] = True
elif is_image(f) and informative_image(f, text):
if is_doc(f) or informative_filename(f.get("name") or "") or is_image(f) and informative_image(f, text):
files[fid] = True
return {
"action": "import",
Regular → Executable
View File
+8
View File
@@ -0,0 +1,8 @@
#!/usr/bin/env bash
# Local / Gitea python-ci lane (ruff + pytest hard gates).
set -euo pipefail
export PYTHONPATH=.
python3 -m ruff check . --isolated
python3 -m pytest -q
Regular → Executable
View File
+5 -5
View File
@@ -9,8 +9,8 @@ import json
import mimetypes
import os
import sys
import zipfile
import xml.etree.ElementTree as ET
import zipfile
from datetime import datetime, timezone
from http import HTTPStatus
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
@@ -115,7 +115,7 @@ class ReviewHandler(BaseHTTPRequestHandler):
export: SlackExport
manifest_path: Path
def log_message(self, format: str, *args) -> None: # noqa: A003
def log_message(self, format: str, *args) -> None:
if os.environ.get("SLACK_IMPORT_QUIET") != "1":
super().log_message(format, *args)
@@ -166,14 +166,14 @@ class ReviewHandler(BaseHTTPRequestHandler):
except json.JSONDecodeError:
return None
def do_OPTIONS(self) -> None: # noqa: N802
def do_OPTIONS(self) -> None:
self.send_response(HTTPStatus.NO_CONTENT)
self.send_header("Access-Control-Allow-Origin", "*")
self.send_header("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
self.send_header("Access-Control-Allow-Headers", "Content-Type")
self.end_headers()
def do_GET(self) -> None: # noqa: N802
def do_GET(self) -> None:
parsed = urlparse(self.path)
path = parsed.path
qs = parse_qs(parsed.query)
@@ -364,7 +364,7 @@ class ReviewHandler(BaseHTTPRequestHandler):
self.send_error(HTTPStatus.NOT_FOUND)
def do_POST(self) -> None: # noqa: N802
def do_POST(self) -> None:
parsed = urlparse(self.path)
if parsed.path != "/api/manifest":
self.send_error(HTTPStatus.NOT_FOUND)
-1
View File
@@ -9,7 +9,6 @@ from datetime import datetime, timezone
from pathlib import Path
from typing import Any
DEFAULT_EXPORT = Path.home() / "Downloads" / "slack-export"
+2 -4
View File
@@ -6,7 +6,6 @@ import importlib.util
import json
import shutil
import socket
import sys
import time
import urllib.error
import urllib.request
@@ -15,6 +14,7 @@ from pathlib import Path
import pytest
REPO_ROOT = Path(__file__).resolve().parents[1]
FIXTURE_EXPORT = REPO_ROOT / "tests" / "fixtures" / "mini-export"
TEST_MANIFEST = REPO_ROOT / "tests" / "fixtures" / "test-manifest.json"
@@ -43,7 +43,6 @@ def fixture_export_path() -> Path:
@pytest.fixture
def export(fixture_export_path):
sys.path.insert(0, str(REPO_ROOT))
from slack_export import SlackExport
return SlackExport(fixture_export_path)
@@ -70,7 +69,7 @@ def _free_port() -> int:
return s.getsockname()[1]
def _wait_health(url: str, timeout: float = 8.0) -> None:
def _wait_health(url: str, timeout: float = 30.0) -> None:
deadline = time.time() + timeout
while time.time() < deadline:
try:
@@ -89,7 +88,6 @@ def review_server(tmp_path_factory):
import threading
from http.server import ThreadingHTTPServer
sys.path.insert(0, str(REPO_ROOT))
from server import ReviewHandler
from slack_export import SlackExport
+1 -1
View File
@@ -60,7 +60,7 @@ def test_wilson_not_in_auto_channels(cherry_pick):
def test_dry_run_pick_count(cherry_pick, export, empty_manifest, tmp_path, monkeypatch):
path, manifest = empty_manifest
_path, manifest = empty_manifest
monkeypatch.setattr(cherry_pick, "MANIFEST_PATH", tmp_path / "manifest.json")
monkeypatch.setattr(cherry_pick, "DEFAULT_EXPORT", export.paths.root)
+3 -3
View File
@@ -2,12 +2,11 @@
from __future__ import annotations
import io
import json
import urllib.error
import urllib.request
import zipfile
from pathlib import Path
import pytest
@@ -117,9 +116,10 @@ def test_manifest_post_skips_non_dict_user(api):
def test_safe_export_path_rejects_dotdot():
from unittest.mock import Mock
import server
from slack_export import SlackExport
from unittest.mock import Mock
root = Path(__file__).resolve().parents[1] / "tests/fixtures/mini-export"
handler = Mock(spec=server.ReviewHandler)
+7 -2
View File
@@ -5,11 +5,16 @@ from __future__ import annotations
import io
import json
import zipfile
from pathlib import Path
import pytest
from slack_export import SlackExport, load_json, resolve_paths, slack_ts_to_iso, user_display
from slack_export import (
SlackExport,
load_json,
resolve_paths,
slack_ts_to_iso,
user_display,
)
def test_slack_ts_to_iso():
-2
View File
@@ -3,8 +3,6 @@
from __future__ import annotations
import json
import shutil
import sys
from pathlib import Path
import pytest
-2
View File
@@ -6,8 +6,6 @@ import importlib.util
import json
from pathlib import Path
import pytest
REPO = Path(__file__).resolve().parents[1]
-2
View File
@@ -2,8 +2,6 @@
from __future__ import annotations
import json
def test_health(api):
data = api["get"]("/api/health")
-1
View File
@@ -3,7 +3,6 @@
from __future__ import annotations
import json
from pathlib import Path
import pytest