merge: fix datetime.utcnow() deprecation warnings across all files

Replaces 94 occurrences of deprecated datetime.utcnow() and
datetime.utcfromtimestamp() across 22 files with utcnow()/utcnow_iso()
helpers. Zero DeprecationWarnings remain. All 1201 tests pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Deeman
2026-02-24 10:36:26 +01:00
27 changed files with 199 additions and 166 deletions

View File

@@ -8,7 +8,7 @@ sitemap integration, admin CRUD routes, and path collision prevention.
import importlib
import json
import sqlite3
from datetime import date, datetime
from datetime import date
from pathlib import Path
import pytest
@@ -19,7 +19,7 @@ from padelnomics.content.routes import (
bake_scenario_cards,
is_reserved_path,
)
from padelnomics.core import execute, fetch_all, fetch_one, slugify
from padelnomics.core import execute, fetch_all, fetch_one, slugify, utcnow_iso
from padelnomics.planner.calculator import calc, validate_state
SCHEMA_PATH = Path(__file__).parent.parent / "src" / "padelnomics" / "migrations" / "schema.sql"
@@ -70,7 +70,7 @@ async def _create_published_scenario(slug="test-scenario", city="TestCity", coun
async def _create_article(slug="test-article", url_path="/test-article",
status="published", published_at=None):
"""Insert an article row, return its id."""
pub = published_at or datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S")
pub = published_at or utcnow_iso()
return await execute(
"""INSERT INTO articles
(url_path, slug, title, meta_description, country, region,
@@ -936,8 +936,7 @@ class TestRouteRegistration:
@pytest.fixture
async def admin_client(app, db):
"""Test client with admin user (has admin role)."""
from datetime import datetime
now = datetime.utcnow().isoformat()
now = utcnow_iso()
async with db.execute(
"INSERT INTO users (email, name, created_at) VALUES (?, ?, ?)",
("admin@test.com", "Admin", now),

View File

@@ -3,9 +3,8 @@ Tests for the credit system (credits.py).
Pure SQL operations against real in-memory SQLite — no mocking needed.
"""
from datetime import datetime
import pytest
from padelnomics.core import utcnow_iso
from padelnomics.credits import (
InsufficientCredits,
add_credits,
@@ -24,7 +23,7 @@ from padelnomics.credits import (
@pytest.fixture
async def supplier(db):
"""Supplier with credit_balance=100, monthly_credits=30, tier=growth."""
now = datetime.utcnow().isoformat()
now = utcnow_iso()
async with db.execute(
"""INSERT INTO suppliers
(name, slug, country_code, region, category, tier,
@@ -41,7 +40,7 @@ async def supplier(db):
@pytest.fixture
async def lead(db):
"""Lead request with heat_score=warm, credit_cost=20."""
now = datetime.utcnow().isoformat()
now = utcnow_iso()
async with db.execute(
"""INSERT INTO lead_requests
(lead_type, heat_score, credit_cost, status, created_at)
@@ -154,7 +153,7 @@ class TestAlreadyUnlocked:
assert await already_unlocked(supplier["id"], lead["id"]) is False
async def test_returns_true_after_unlock(self, db, supplier, lead):
now = datetime.utcnow().isoformat()
now = utcnow_iso()
await db.execute(
"""INSERT INTO lead_forwards (lead_id, supplier_id, credit_cost, created_at)
VALUES (?, ?, 20, ?)""",
@@ -210,7 +209,7 @@ class TestUnlockLead:
async def test_raises_insufficient_credits(self, db, lead):
"""Supplier with only 5 credits tries to unlock a 20-credit lead."""
now = datetime.utcnow().isoformat()
now = utcnow_iso()
async with db.execute(
"""INSERT INTO suppliers
(name, slug, country_code, region, category, tier,
@@ -247,7 +246,7 @@ class TestMonthlyRefill:
async def test_noop_when_no_monthly_credits(self, db):
"""Supplier with monthly_credits=0 gets no refill."""
now = datetime.utcnow().isoformat()
now = utcnow_iso()
async with db.execute(
"""INSERT INTO suppliers
(name, slug, country_code, region, category, tier,

View File

@@ -7,15 +7,13 @@ Integration tests exercise full request/response flows via Quart test client.
"""
import sqlite3
from datetime import datetime
from pathlib import Path
from unittest.mock import AsyncMock, patch
import pytest
from padelnomics import core
from padelnomics.core import utcnow_iso
from padelnomics.migrations.migrate import migrate
from padelnomics import core
# ── Fixtures & helpers ────────────────────────────────────────────
@@ -30,7 +28,7 @@ def mock_csrf_validation():
@pytest.fixture
async def admin_client(app, db):
"""Test client with an admin-role user session (module-level, follows test_content.py)."""
now = datetime.utcnow().isoformat()
now = utcnow_iso()
async with db.execute(
"INSERT INTO users (email, name, created_at) VALUES (?, ?, ?)",
("flags_admin@test.com", "Flags Admin", now),
@@ -293,8 +291,9 @@ class TestLeadUnlockGate:
@pytest.mark.asyncio
async def test_route_imports_is_flag_enabled(self):
"""suppliers/routes.py imports is_flag_enabled (gate is wired up)."""
from padelnomics.suppliers.routes import unlock_lead
import inspect
from padelnomics.suppliers.routes import unlock_lead
src = inspect.getsource(unlock_lead)
assert "is_flag_enabled" in src
assert "lead_unlock" in src

View File

@@ -1,9 +1,10 @@
"""Tests for the SEO metrics module: queries, sync functions, admin routes."""
from datetime import datetime, timedelta
from datetime import UTC, datetime, timedelta
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
from padelnomics.core import utcnow_iso
from padelnomics.seo._queries import (
cleanup_old_metrics,
get_article_scorecard,
@@ -21,11 +22,11 @@ from padelnomics import core
# ── Fixtures ──────────────────────────────────────────────────
def _today():
return datetime.utcnow().strftime("%Y-%m-%d")
return datetime.now(UTC).strftime("%Y-%m-%d")
def _days_ago(n: int) -> str:
return (datetime.utcnow() - timedelta(days=n)).strftime("%Y-%m-%d")
return (datetime.now(UTC) - timedelta(days=n)).strftime("%Y-%m-%d")
@pytest.fixture
@@ -72,7 +73,7 @@ async def seo_data(db):
@pytest.fixture
async def articles_data(db, seo_data):
"""Create articles that match the SEO data URLs."""
now = datetime.utcnow().isoformat()
now = utcnow_iso()
pub = _days_ago(10)
for title, url, tpl, lang in [
@@ -91,7 +92,7 @@ async def articles_data(db, seo_data):
@pytest.fixture
async def admin_client(app, db):
"""Authenticated admin client."""
now = datetime.utcnow().isoformat()
now = utcnow_iso()
async with db.execute(
"INSERT INTO users (email, name, created_at) VALUES (?, ?, ?)",
("admin@test.com", "Admin", now),
@@ -258,7 +259,7 @@ class TestSyncStatus:
"""Tests for get_sync_status()."""
async def test_returns_last_sync_per_source(self, db):
now = datetime.utcnow().isoformat()
now = utcnow_iso()
await db.execute(
"""INSERT INTO seo_sync_log (source, status, rows_synced, started_at, completed_at, duration_ms)
VALUES ('gsc', 'success', 100, ?, ?, 500)""",
@@ -286,7 +287,7 @@ class TestCleanupOldMetrics:
"""Tests for cleanup_old_metrics()."""
async def test_deletes_old_data(self, db):
old_date = (datetime.utcnow() - timedelta(days=400)).strftime("%Y-%m-%d")
old_date = (datetime.now(UTC) - timedelta(days=400)).strftime("%Y-%m-%d")
recent_date = _today()
await db.execute(

View File

@@ -8,19 +8,16 @@ supervisor.py lives in src/padelnomics/ (not a uv workspace package), so we
add src/ to sys.path before importing.
"""
import sys
# Load supervisor.py directly by path — avoids clashing with the web app's
# 'padelnomics' namespace (which is the installed web package).
import importlib.util as _ilu
import textwrap
import tomllib
from datetime import UTC, datetime, timedelta
from pathlib import Path
from unittest.mock import MagicMock, patch
import pytest
# Load supervisor.py directly by path — avoids clashing with the web app's
# 'padelnomics' namespace (which is the installed web package).
import importlib.util as _ilu
_SUP_PATH = Path(__file__).parent.parent.parent / "src" / "padelnomics" / "supervisor.py"
_spec = _ilu.spec_from_file_location("padelnomics_supervisor", _SUP_PATH)
sup = _ilu.module_from_spec(_spec)
@@ -32,7 +29,6 @@ from padelnomics_extract.proxy import (
make_sticky_selector,
)
# ── load_workflows ────────────────────────────────────────────────

View File

@@ -5,11 +5,12 @@ POST real webhook payloads to /billing/webhook/paddle and verify DB state.
Uses the existing client, db, sign_payload from conftest.
"""
import json
from datetime import datetime
from datetime import UTC, datetime
from unittest.mock import AsyncMock, patch
import pytest
from conftest import sign_payload
from padelnomics.core import utcnow_iso
WEBHOOK_PATH = "/billing/webhook/paddle"
SIG_HEADER = "Paddle-Signature"
@@ -21,7 +22,7 @@ SIG_HEADER = "Paddle-Signature"
@pytest.fixture
async def supplier(db):
"""Supplier with tier=free, credit_balance=0."""
now = datetime.utcnow().isoformat()
now = utcnow_iso()
async with db.execute(
"""INSERT INTO suppliers
(name, slug, country_code, region, category, tier,
@@ -38,7 +39,7 @@ async def supplier(db):
@pytest.fixture
async def paddle_products(db):
"""Insert paddle_products rows for all keys the handlers need."""
now = datetime.utcnow().isoformat()
now = utcnow_iso()
products = [
("credits_25", "pri_credits25", "Credit Pack 25", 999, "one_time"),
("credits_100", "pri_credits100", "Credit Pack 100", 3290, "one_time"),
@@ -175,7 +176,7 @@ class TestStickyBoostPurchase:
assert boosts[0][1] == "active"
# expires_at should be ~7 days from now
expires = datetime.fromisoformat(boosts[0][2])
assert abs((expires - datetime.utcnow()).days - 7) <= 1
assert abs((expires - datetime.now(UTC).replace(tzinfo=None)).days - 7) <= 1
# Verify sticky_until set on supplier
sup = await db.execute_fetchall(
@@ -202,7 +203,7 @@ class TestStickyBoostPurchase:
assert len(boosts) == 1
assert boosts[0][0] == "sticky_month"
expires = datetime.fromisoformat(boosts[0][1])
assert abs((expires - datetime.utcnow()).days - 30) <= 1
assert abs((expires - datetime.now(UTC).replace(tzinfo=None)).days - 30) <= 1
async def test_sticky_boost_sets_country(self, client, db, supplier, paddle_products):
payload = make_transaction_payload(
@@ -387,7 +388,7 @@ class TestBusinessPlanPurchase:
self, client, db, supplier, paddle_products, test_user,
):
# Need a scenario for the export
now = datetime.utcnow().isoformat()
now = utcnow_iso()
async with db.execute(
"""INSERT INTO scenarios (user_id, name, state_json, created_at)
VALUES (?, 'Test Scenario', '{}', ?)""",