2 Commits
v10 ... v12

Author SHA1 Message Date
Deeman
c8b86569ff chore: consolidate to single ruff config in root pyproject.toml
All checks were successful
CI / test-cli (push) Successful in 11s
CI / test-sqlmesh (push) Successful in 14s
CI / test-web (push) Successful in 14s
CI / tag (push) Successful in 2s
- Merge web ruff settings (select E/F/I/UP, line-length 100) into root config
- Remove [tool.ruff] section from web/pyproject.toml
- Remove "web" from root ruff exclude list
- Simplify pre-commit hook to one command: ruff check .
- Update CI to use: uv run ruff check . (from repo root)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 12:21:01 +01:00
Deeman
42c1309b20 chore: add pre-commit ruff hook with auto-fix
Some checks failed
CI / test-cli (push) Successful in 11s
CI / test-sqlmesh (push) Successful in 12s
CI / test-web (push) Failing after 14s
CI / tag (push) Has been skipped
- scripts/hooks/pre-commit: runs ruff --fix for root and web/ (matching CI)
  and re-stages any auto-fixed files so they land in the commit
- Makefile: add install-hooks target (run once after clone)
- pyproject.toml: exclude web/ from root ruff (web has its own config)
- Fix remaining import sort warnings caught by the new hook

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 10:19:29 +01:00
19 changed files with 56 additions and 41 deletions

View File

@@ -45,7 +45,7 @@ test:web:
script: script:
- uv sync --all-packages - uv sync --all-packages
- cd web && uv run pytest tests/ -x -q - cd web && uv run pytest tests/ -x -q
- cd web && uv run ruff check src/ tests/ - uv run ruff check .
# ── Tag (pull-based deploy) ─────────────────────────────────────────────────── # ── Tag (pull-based deploy) ───────────────────────────────────────────────────
# Creates v<N> tag after all tests pass. The on-server supervisor polls for new # Creates v<N> tag after all tests pass. The on-server supervisor polls for new

View File

@@ -2,7 +2,7 @@ TAILWIND_VERSION := v4.1.18
TAILWIND := web/bin/tailwindcss TAILWIND := web/bin/tailwindcss
SOPS_DOTENV := sops --input-type dotenv --output-type dotenv SOPS_DOTENV := sops --input-type dotenv --output-type dotenv
.PHONY: help dev css-build css-watch \ .PHONY: help dev css-build css-watch install-hooks \
secrets-decrypt-dev secrets-decrypt-prod \ secrets-decrypt-dev secrets-decrypt-prod \
secrets-edit-dev secrets-edit-prod \ secrets-edit-dev secrets-edit-prod \
secrets-encrypt-dev secrets-encrypt-prod \ secrets-encrypt-dev secrets-encrypt-prod \
@@ -13,6 +13,7 @@ help:
@echo " dev Start full dev environment (migrate, seed, app + worker + CSS watcher)" @echo " dev Start full dev environment (migrate, seed, app + worker + CSS watcher)"
@echo " css-build Build + minify Tailwind CSS" @echo " css-build Build + minify Tailwind CSS"
@echo " css-watch Watch + rebuild Tailwind CSS" @echo " css-watch Watch + rebuild Tailwind CSS"
@echo " install-hooks Install git pre-commit hook (run once after cloning)"
@echo " secrets-decrypt-dev Decrypt .env.dev.sops → .env" @echo " secrets-decrypt-dev Decrypt .env.dev.sops → .env"
@echo " secrets-decrypt-prod Decrypt .env.prod.sops → .env" @echo " secrets-decrypt-prod Decrypt .env.prod.sops → .env"
@echo " secrets-edit-dev Edit .env.dev.sops in \$$EDITOR" @echo " secrets-edit-dev Edit .env.dev.sops in \$$EDITOR"
@@ -23,6 +24,11 @@ help:
# ── Dev environment ─────────────────────────────────────────────────────────── # ── Dev environment ───────────────────────────────────────────────────────────
install-hooks:
cp scripts/hooks/pre-commit .git/hooks/pre-commit
chmod +x .git/hooks/pre-commit
@echo "✓ pre-commit hook installed"
dev: dev:
@./web/scripts/dev_run.sh @./web/scripts/dev_run.sh

View File

@@ -15,8 +15,14 @@ import sys
from pathlib import Path from pathlib import Path
import yfinance as yf import yfinance as yf
from extract_core import content_hash, end_run, landing_path, open_state_db, start_run from extract_core import (
from extract_core import write_bytes_atomic content_hash,
end_run,
landing_path,
open_state_db,
start_run,
write_bytes_atomic,
)
logging.basicConfig( logging.basicConfig(
level=logging.INFO, level=logging.INFO,

View File

@@ -1,4 +1,3 @@
from .normalize import normalize_zipped_csv
import logging import logging
import os import os
import sys import sys
@@ -7,8 +6,16 @@ from io import BytesIO
from pathlib import Path from pathlib import Path
import niquests import niquests
from extract_core import end_run, landing_path, normalize_etag, open_state_db, start_run from extract_core import (
from extract_core import write_bytes_atomic end_run,
landing_path,
normalize_etag,
open_state_db,
start_run,
write_bytes_atomic,
)
from .normalize import normalize_zipped_csv
logging.basicConfig( logging.basicConfig(
level=logging.INFO, level=logging.INFO,

View File

@@ -1,8 +1,7 @@
import zipfile
import gzip import gzip
from io import BytesIO
import pathlib import pathlib
import zipfile
from io import BytesIO
def normalize_zipped_csv(buffer: BytesIO)->BytesIO: def normalize_zipped_csv(buffer: BytesIO)->BytesIO:

View File

@@ -90,11 +90,13 @@ exclude = [
"notebooks", "notebooks",
] ]
line-length = 100
indent-width = 4 indent-width = 4
target-version = "py313" target-version = "py313"
[tool.ruff.lint] [tool.ruff.lint]
select = ["E", "F", "I", "UP"]
ignore = [ ignore = [
"E501", # line too long (handled by formatter) "E501", # line too long (handled by formatter)

18
scripts/hooks/pre-commit Normal file
View File

@@ -0,0 +1,18 @@
#!/usr/bin/env bash
# Pre-commit hook: ruff lint + auto-fix.
# Install: make install-hooks
set -euo pipefail
REPO_ROOT="$(git rev-parse --show-toplevel)"
RUFF="$REPO_ROOT/.venv/bin/ruff"
if [[ ! -x "$RUFF" ]]; then
echo "pre-commit: ruff not found at $RUFF — run 'uv sync' first" >&2
exit 1
fi
echo "→ ruff check"
"$RUFF" check --fix "$REPO_ROOT"
# Re-stage any files ruff fixed so they land in the commit.
git diff --name-only | xargs -r git add

View File

@@ -7,7 +7,6 @@ from unittest.mock import MagicMock
from cftc_cot.normalize import find_csv_inner_filename, normalize_zipped_csv from cftc_cot.normalize import find_csv_inner_filename, normalize_zipped_csv
# ============================================================================= # =============================================================================
# normalize.py # normalize.py
# ============================================================================= # =============================================================================

View File

@@ -6,7 +6,6 @@ from unittest.mock import MagicMock
import pytest import pytest
import xlwt # noqa: F401 — needed to create XLS fixtures; skip tests if missing import xlwt # noqa: F401 — needed to create XLS fixtures; skip tests if missing
from ice_stocks.ice_api import fetch_report_listings, find_latest_report from ice_stocks.ice_api import fetch_report_listings, find_latest_report
from ice_stocks.xls_parse import OLE2_MAGIC, detect_file_format, xls_to_rows from ice_stocks.xls_parse import OLE2_MAGIC, detect_file_format, xls_to_rows

View File

@@ -36,14 +36,6 @@ dev-dependencies = [
"ruff>=0.3.0", "ruff>=0.3.0",
] ]
[tool.ruff]
line-length = 100
target-version = "py311"
[tool.ruff.lint]
select = ["E", "F", "I", "UP"]
ignore = ["E501"]
[tool.pytest.ini_options] [tool.pytest.ini_options]
asyncio_mode = "auto" asyncio_mode = "auto"
testpaths = ["tests"] testpaths = ["tests"]

View File

@@ -23,8 +23,7 @@ def admin_required(f):
async def decorated(*args, **kwargs): async def decorated(*args, **kwargs):
if "admin" not in (g.get("user") or {}).get("roles", []): if "admin" not in (g.get("user") or {}).get("roles", []):
await flash("Admin access required.", "error") await flash("Admin access required.", "error")
from quart import redirect as _redirect from quart import redirect as _redirect, url_for as _url_for
from quart import url_for as _url_for
return _redirect(_url_for("auth.login")) return _redirect(_url_for("auth.login"))
return await f(*args, **kwargs) return await f(*args, **kwargs)
return decorated return decorated

View File

@@ -105,8 +105,7 @@ def create_app() -> Quart:
# Health check # Health check
@app.route("/health") @app.route("/health")
async def health(): async def health():
from .analytics import _db_path as serving_db_path from .analytics import _db_path as serving_db_path, fetch_analytics
from .analytics import fetch_analytics
from .core import fetch_one from .core import fetch_one
result = {"status": "healthy", "sqlite": "ok", "duckdb": "ok"} result = {"status": "healthy", "sqlite": "ok", "duckdb": "ok"}
try: try:

View File

@@ -13,8 +13,7 @@ import os
import sys import sys
from dotenv import load_dotenv from dotenv import load_dotenv
from paddle_billing import Client as PaddleClient from paddle_billing import Client as PaddleClient, Environment, Options
from paddle_billing import Environment, Options
from paddle_billing.Entities.Shared import CurrencyCode, Money, TaxCategory from paddle_billing.Entities.Shared import CurrencyCode, Money, TaxCategory
from paddle_billing.Resources.Prices.Operations import CreatePrice from paddle_billing.Resources.Prices.Operations import CreatePrice
from paddle_billing.Resources.Products.Operations import CreateProduct from paddle_billing.Resources.Products.Operations import CreateProduct

View File

@@ -7,7 +7,6 @@ from unittest.mock import AsyncMock, patch
import aiosqlite import aiosqlite
import pytest import pytest
from beanflows import core from beanflows import core
from beanflows.app import create_app from beanflows.app import create_app

View File

@@ -2,10 +2,6 @@
Unit tests for billing SQL helpers, feature/limit access, and plan determination. Unit tests for billing SQL helpers, feature/limit access, and plan determination.
""" """
import pytest import pytest
from hypothesis import HealthCheck, given
from hypothesis import settings as h_settings
from hypothesis import strategies as st
from beanflows.billing.routes import ( from beanflows.billing.routes import (
can_access_feature, can_access_feature,
get_billing_customer, get_billing_customer,
@@ -18,6 +14,7 @@ from beanflows.billing.routes import (
upsert_subscription, upsert_subscription,
) )
from beanflows.core import config from beanflows.core import config
from hypothesis import HealthCheck, given, settings as h_settings, strategies as st
# ════════════════════════════════════════════════════════════ # ════════════════════════════════════════════════════════════
# get_subscription # get_subscription

View File

@@ -2,7 +2,6 @@
Tests for the billing event hook system. Tests for the billing event hook system.
""" """
import pytest import pytest
from beanflows.billing.routes import _billing_hooks, _fire_hooks, on_billing_event from beanflows.billing.routes import _billing_hooks, _fire_hooks, on_billing_event

View File

@@ -144,9 +144,8 @@ class TestCancelRoute:
# subscription_required decorator # subscription_required decorator
# ════════════════════════════════════════════════════════════ # ════════════════════════════════════════════════════════════
from quart import Blueprint # noqa: E402
from beanflows.auth.routes import subscription_required # noqa: E402 from beanflows.auth.routes import subscription_required # noqa: E402
from quart import Blueprint # noqa: E402
test_bp = Blueprint("test", __name__) test_bp = Blueprint("test", __name__)

View File

@@ -5,12 +5,9 @@ Covers signature verification, event parsing, subscription lifecycle transitions
import json import json
import pytest import pytest
from conftest import make_webhook_payload, sign_payload
from hypothesis import HealthCheck, given
from hypothesis import settings as h_settings
from hypothesis import strategies as st
from beanflows.billing.routes import get_billing_customer, get_subscription from beanflows.billing.routes import get_billing_customer, get_subscription
from conftest import make_webhook_payload, sign_payload
from hypothesis import HealthCheck, given, settings as h_settings, strategies as st
WEBHOOK_PATH = "/billing/webhook/paddle" WEBHOOK_PATH = "/billing/webhook/paddle"
SIG_HEADER = "Paddle-Signature" SIG_HEADER = "Paddle-Signature"

View File

@@ -3,8 +3,6 @@ Tests for role-based access control: role_required decorator, grant/revoke/ensur
and admin route protection. and admin route protection.
""" """
import pytest import pytest
from quart import Blueprint
from beanflows import core from beanflows import core
from beanflows.auth.routes import ( from beanflows.auth.routes import (
ensure_admin_role, ensure_admin_role,
@@ -12,6 +10,7 @@ from beanflows.auth.routes import (
revoke_role, revoke_role,
role_required, role_required,
) )
from quart import Blueprint
# ════════════════════════════════════════════════════════════ # ════════════════════════════════════════════════════════════
# grant_role / revoke_role # grant_role / revoke_role