chore(backend): deprecation sweep — Pydantic V1 -> V2, utcnow -> tz-aware
Two low-risk Pydantic V1 idioms that will be hard errors in V3:
- Query(regex=...) -> Query(pattern=...) (audit_routes, control_generator_routes)
- class Config: from_attributes=True -> model_config = ConfigDict(...)
in source_policy_router.py (schemas.py is intentionally skipped — it is
the Phase 1 schema-split target and the ConfigDict conversion is most
efficient to do during that split).
Naive -> aware datetime sweep across 47 files:
- datetime.utcnow() -> datetime.now(timezone.utc)
- default=datetime.utcnow -> default=lambda: datetime.now(timezone.utc)
- onupdate=datetime.utcnow -> onupdate=lambda: datetime.now(timezone.utc)
All SQLAlchemy DateTime columns in the project already declare
timezone=True, so the DB schema expects aware datetimes. Before this
commit, the in-Python side was generating naive values and the driver
was silently coercing them. This is a latent-bug fix, not a behavior
change at the DB boundary.
Verified:
- 173/173 pytest compliance/tests/ pass (same as baseline)
- tests/contracts/test_openapi_baseline.py passes (360 paths,
484 operations unchanged)
- DeprecationWarning count dropped from 158 -> 35
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -10,7 +10,7 @@ import pytest
|
||||
import uuid
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timezone
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from fastapi import FastAPI
|
||||
@@ -51,7 +51,7 @@ _RawSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||
@event.listens_for(engine, "connect")
|
||||
def _register_sqlite_functions(dbapi_conn, connection_record):
|
||||
"""Register PostgreSQL-compatible functions for SQLite."""
|
||||
dbapi_conn.create_function("NOW", 0, lambda: datetime.utcnow().isoformat())
|
||||
dbapi_conn.create_function("NOW", 0, lambda: datetime.now(timezone.utc).isoformat())
|
||||
|
||||
TENANT_ID = "default"
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ Pattern: app.dependency_overrides[get_db] for FastAPI DI.
|
||||
import uuid
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
import pytest
|
||||
from fastapi import FastAPI
|
||||
@@ -75,7 +75,7 @@ def db_session():
|
||||
|
||||
def _create_dsr_in_db(db, **kwargs):
|
||||
"""Helper to create a DSR directly in DB."""
|
||||
now = datetime.utcnow()
|
||||
now = datetime.now(timezone.utc)
|
||||
defaults = {
|
||||
"tenant_id": uuid.UUID(TENANT_ID),
|
||||
"request_number": f"DSR-2026-{str(uuid.uuid4())[:6].upper()}",
|
||||
@@ -241,8 +241,8 @@ class TestListDSR:
|
||||
assert len(data["requests"]) == 2
|
||||
|
||||
def test_list_overdue_only(self, db_session):
|
||||
_create_dsr_in_db(db_session, deadline_at=datetime.utcnow() - timedelta(days=5), status="processing")
|
||||
_create_dsr_in_db(db_session, deadline_at=datetime.utcnow() + timedelta(days=20), status="processing")
|
||||
_create_dsr_in_db(db_session, deadline_at=datetime.now(timezone.utc) - timedelta(days=5), status="processing")
|
||||
_create_dsr_in_db(db_session, deadline_at=datetime.now(timezone.utc) + timedelta(days=20), status="processing")
|
||||
|
||||
resp = client.get("/api/compliance/dsr?overdue_only=true", headers=HEADERS)
|
||||
assert resp.status_code == 200
|
||||
@@ -339,7 +339,7 @@ class TestDSRStats:
|
||||
_create_dsr_in_db(db_session, status="intake", request_type="access")
|
||||
_create_dsr_in_db(db_session, status="processing", request_type="erasure")
|
||||
_create_dsr_in_db(db_session, status="completed", request_type="access",
|
||||
completed_at=datetime.utcnow())
|
||||
completed_at=datetime.now(timezone.utc))
|
||||
|
||||
resp = client.get("/api/compliance/dsr/stats", headers=HEADERS)
|
||||
assert resp.status_code == 200
|
||||
@@ -561,9 +561,9 @@ class TestDeadlineProcessing:
|
||||
|
||||
def test_process_deadlines_with_overdue(self, db_session):
|
||||
_create_dsr_in_db(db_session, status="processing",
|
||||
deadline_at=datetime.utcnow() - timedelta(days=5))
|
||||
deadline_at=datetime.now(timezone.utc) - timedelta(days=5))
|
||||
_create_dsr_in_db(db_session, status="processing",
|
||||
deadline_at=datetime.utcnow() + timedelta(days=20))
|
||||
deadline_at=datetime.now(timezone.utc) + timedelta(days=20))
|
||||
|
||||
resp = client.post("/api/compliance/dsr/deadlines/process", headers=HEADERS)
|
||||
assert resp.status_code == 200
|
||||
@@ -609,7 +609,7 @@ class TestDSRTemplates:
|
||||
subject="Bestaetigung",
|
||||
body_html="<p>Test</p>",
|
||||
status="published",
|
||||
published_at=datetime.utcnow(),
|
||||
published_at=datetime.now(timezone.utc),
|
||||
)
|
||||
db_session.add(v)
|
||||
db_session.commit()
|
||||
|
||||
@@ -7,7 +7,7 @@ Consent widerrufen, Statistiken.
|
||||
|
||||
import pytest
|
||||
from unittest.mock import MagicMock, patch
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timezone
|
||||
import uuid
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ def make_catalog(tenant_id='test-tenant'):
|
||||
rec.tenant_id = tenant_id
|
||||
rec.selected_data_point_ids = ['dp-001', 'dp-002']
|
||||
rec.custom_data_points = []
|
||||
rec.updated_at = datetime.utcnow()
|
||||
rec.updated_at = datetime.now(timezone.utc)
|
||||
return rec
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ def make_company(tenant_id='test-tenant'):
|
||||
rec.id = uuid.uuid4()
|
||||
rec.tenant_id = tenant_id
|
||||
rec.data = {'company_name': 'Test GmbH', 'email': 'datenschutz@test.de'}
|
||||
rec.updated_at = datetime.utcnow()
|
||||
rec.updated_at = datetime.now(timezone.utc)
|
||||
return rec
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ def make_cookies(tenant_id='test-tenant'):
|
||||
{'id': 'analytics', 'name': 'Analyse', 'isRequired': False, 'defaultEnabled': False},
|
||||
]
|
||||
rec.config = {'position': 'bottom', 'style': 'bar'}
|
||||
rec.updated_at = datetime.utcnow()
|
||||
rec.updated_at = datetime.now(timezone.utc)
|
||||
return rec
|
||||
|
||||
|
||||
@@ -58,13 +58,13 @@ def make_consent(tenant_id='test-tenant', user_id='user-001', data_point_id='dp-
|
||||
rec.user_id = user_id
|
||||
rec.data_point_id = data_point_id
|
||||
rec.granted = granted
|
||||
rec.granted_at = datetime.utcnow()
|
||||
rec.granted_at = datetime.now(timezone.utc)
|
||||
rec.revoked_at = None
|
||||
rec.consent_version = '1.0'
|
||||
rec.source = 'website'
|
||||
rec.ip_address = None
|
||||
rec.user_agent = None
|
||||
rec.created_at = datetime.utcnow()
|
||||
rec.created_at = datetime.now(timezone.utc)
|
||||
return rec
|
||||
|
||||
|
||||
@@ -263,7 +263,7 @@ class TestConsentDB:
|
||||
user_id='user-001',
|
||||
data_point_id='dp-marketing',
|
||||
granted=True,
|
||||
granted_at=datetime.utcnow(),
|
||||
granted_at=datetime.now(timezone.utc),
|
||||
consent_version='1.0',
|
||||
source='website',
|
||||
)
|
||||
@@ -276,13 +276,13 @@ class TestConsentDB:
|
||||
consent = make_consent()
|
||||
assert consent.revoked_at is None
|
||||
|
||||
consent.revoked_at = datetime.utcnow()
|
||||
consent.revoked_at = datetime.now(timezone.utc)
|
||||
assert consent.revoked_at is not None
|
||||
|
||||
def test_cannot_revoke_already_revoked(self):
|
||||
"""Should not be possible to revoke an already revoked consent."""
|
||||
consent = make_consent()
|
||||
consent.revoked_at = datetime.utcnow()
|
||||
consent.revoked_at = datetime.now(timezone.utc)
|
||||
|
||||
# Simulate the guard logic from the route
|
||||
already_revoked = consent.revoked_at is not None
|
||||
@@ -315,7 +315,7 @@ class TestConsentStats:
|
||||
make_consent(user_id='user-2', data_point_id='dp-1', granted=True),
|
||||
]
|
||||
# Revoke one
|
||||
consents[1].revoked_at = datetime.utcnow()
|
||||
consents[1].revoked_at = datetime.now(timezone.utc)
|
||||
|
||||
total = len(consents)
|
||||
active = sum(1 for c in consents if c.granted and not c.revoked_at)
|
||||
@@ -334,7 +334,7 @@ class TestConsentStats:
|
||||
make_consent(user_id='user-2', granted=True),
|
||||
make_consent(user_id='user-3', granted=True),
|
||||
]
|
||||
consents[2].revoked_at = datetime.utcnow() # user-3 revoked
|
||||
consents[2].revoked_at = datetime.now(timezone.utc) # user-3 revoked
|
||||
|
||||
unique_users = len(set(c.user_id for c in consents))
|
||||
users_with_active = len(set(c.user_id for c in consents if c.granted and not c.revoked_at))
|
||||
@@ -501,7 +501,7 @@ class TestConsentHistoryTracking:
|
||||
from compliance.db.einwilligungen_models import EinwilligungenConsentHistoryDB
|
||||
|
||||
consent = make_consent()
|
||||
consent.revoked_at = datetime.utcnow()
|
||||
consent.revoked_at = datetime.now(timezone.utc)
|
||||
entry = EinwilligungenConsentHistoryDB(
|
||||
consent_id=consent.id,
|
||||
tenant_id=consent.tenant_id,
|
||||
@@ -516,7 +516,7 @@ class TestConsentHistoryTracking:
|
||||
|
||||
entry_id = _uuid.uuid4()
|
||||
consent_id = _uuid.uuid4()
|
||||
now = datetime.utcnow()
|
||||
now = datetime.now(timezone.utc)
|
||||
|
||||
row = {
|
||||
"id": str(entry_id),
|
||||
|
||||
@@ -13,7 +13,7 @@ Run with: cd backend-compliance && python3 -m pytest tests/test_isms_routes.py -
|
||||
import os
|
||||
import sys
|
||||
import pytest
|
||||
from datetime import date, datetime
|
||||
from datetime import date, datetime, timezone
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
@@ -40,7 +40,7 @@ def _set_sqlite_pragma(dbapi_conn, connection_record):
|
||||
cursor = dbapi_conn.cursor()
|
||||
cursor.execute("PRAGMA foreign_keys=ON")
|
||||
cursor.close()
|
||||
dbapi_conn.create_function("NOW", 0, lambda: datetime.utcnow().isoformat())
|
||||
dbapi_conn.create_function("NOW", 0, lambda: datetime.now(timezone.utc).isoformat())
|
||||
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
@@ -7,7 +7,7 @@ Rejection-Flow, approval history.
|
||||
|
||||
import pytest
|
||||
from unittest.mock import MagicMock, patch
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timezone
|
||||
import uuid
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ def make_document(type='privacy_policy', name='Datenschutzerklärung', tenant_id
|
||||
doc.name = name
|
||||
doc.description = 'Test description'
|
||||
doc.mandatory = False
|
||||
doc.created_at = datetime.utcnow()
|
||||
doc.created_at = datetime.now(timezone.utc)
|
||||
doc.updated_at = None
|
||||
return doc
|
||||
|
||||
@@ -46,7 +46,7 @@ def make_version(document_id=None, version='1.0', status='draft', title='Test Ve
|
||||
v.approved_by = None
|
||||
v.approved_at = None
|
||||
v.rejection_reason = None
|
||||
v.created_at = datetime.utcnow()
|
||||
v.created_at = datetime.now(timezone.utc)
|
||||
v.updated_at = None
|
||||
return v
|
||||
|
||||
@@ -58,7 +58,7 @@ def make_approval(version_id=None, action='created'):
|
||||
a.action = action
|
||||
a.approver = 'admin@test.de'
|
||||
a.comment = None
|
||||
a.created_at = datetime.utcnow()
|
||||
a.created_at = datetime.now(timezone.utc)
|
||||
return a
|
||||
|
||||
|
||||
@@ -179,7 +179,7 @@ class TestVersionToResponse:
|
||||
from compliance.api.legal_document_routes import _version_to_response
|
||||
v = make_version(status='approved')
|
||||
v.approved_by = 'dpo@company.de'
|
||||
v.approved_at = datetime.utcnow()
|
||||
v.approved_at = datetime.now(timezone.utc)
|
||||
resp = _version_to_response(v)
|
||||
assert resp.status == 'approved'
|
||||
assert resp.approved_by == 'dpo@company.de'
|
||||
@@ -254,7 +254,7 @@ class TestApprovalWorkflow:
|
||||
# Step 2: Approve
|
||||
mock_db.reset_mock()
|
||||
_transition(mock_db, str(v.id), ['review'], 'approved', 'approved', 'dpo', 'Korrekt',
|
||||
extra_updates={'approved_by': 'dpo', 'approved_at': datetime.utcnow()})
|
||||
extra_updates={'approved_by': 'dpo', 'approved_at': datetime.now(timezone.utc)})
|
||||
assert v.status == 'approved'
|
||||
|
||||
# Step 3: Publish
|
||||
|
||||
@@ -5,7 +5,7 @@ Tests for Legal Document extended routes (User Consents, Audit Log, Cookie Categ
|
||||
import uuid
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timezone
|
||||
|
||||
import pytest
|
||||
from fastapi import FastAPI
|
||||
@@ -103,7 +103,7 @@ def _publish_version(version_id):
|
||||
v = db.query(LegalDocumentVersionDB).filter(LegalDocumentVersionDB.id == vid).first()
|
||||
v.status = "published"
|
||||
v.approved_by = "admin"
|
||||
v.approved_at = datetime.utcnow()
|
||||
v.approved_at = datetime.now(timezone.utc)
|
||||
db.commit()
|
||||
db.refresh(v)
|
||||
result = {"id": str(v.id), "status": v.status}
|
||||
|
||||
@@ -15,7 +15,7 @@ import pytest
|
||||
import uuid
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
@@ -40,7 +40,7 @@ TENANT_ID = "default"
|
||||
|
||||
@event.listens_for(engine, "connect")
|
||||
def _register_sqlite_functions(dbapi_conn, connection_record):
|
||||
dbapi_conn.create_function("NOW", 0, lambda: datetime.utcnow().isoformat())
|
||||
dbapi_conn.create_function("NOW", 0, lambda: datetime.now(timezone.utc).isoformat())
|
||||
|
||||
|
||||
class _DictRow(dict):
|
||||
|
||||
@@ -186,7 +186,7 @@ class TestActivityToResponse:
|
||||
act.next_review_at = kwargs.get("next_review_at", None)
|
||||
act.created_by = kwargs.get("created_by", None)
|
||||
act.dsfa_id = kwargs.get("dsfa_id", None)
|
||||
act.created_at = datetime.utcnow()
|
||||
act.created_at = datetime.now(timezone.utc)
|
||||
act.updated_at = None
|
||||
return act
|
||||
|
||||
@@ -330,7 +330,7 @@ class TestVVTConsolidationResponse:
|
||||
act.next_review_at = kwargs.get("next_review_at", None)
|
||||
act.created_by = kwargs.get("created_by", None)
|
||||
act.dsfa_id = kwargs.get("dsfa_id", None)
|
||||
act.created_at = datetime.utcnow()
|
||||
act.created_at = datetime.now(timezone.utc)
|
||||
act.updated_at = None
|
||||
return act
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ Verifies that:
|
||||
import pytest
|
||||
import uuid
|
||||
from unittest.mock import MagicMock, AsyncMock, patch
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from fastapi import HTTPException
|
||||
from fastapi.testclient import TestClient
|
||||
@@ -144,8 +144,8 @@ def _make_activity(tenant_id, vvt_id="VVT-001", name="Test", **kwargs):
|
||||
act.next_review_at = None
|
||||
act.created_by = "system"
|
||||
act.dsfa_id = None
|
||||
act.created_at = datetime.utcnow()
|
||||
act.updated_at = datetime.utcnow()
|
||||
act.created_at = datetime.now(timezone.utc)
|
||||
act.updated_at = datetime.now(timezone.utc)
|
||||
return act
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user