Replace deprecated datetime.utcnow() with datetime.now(UTC) throughout

Python 3.12 deprecates datetime.utcnow() with a DeprecationWarning and
it will be removed in a future release. Replace all call-site usages in
services, routes, and utils with datetime.now(UTC), and replace the
SQLAlchemy ORM column defaults (which used the bare function reference
datetime.utcnow) with lambda: datetime.now(UTC) so that the returned
objects are timezone-aware and consistent with Python best practice.

Affected: database/models.py, services/{stories,history,profiles,
channels,export_import}.py, routes/{profiles,tasks}.py, utils/tasks.py
This commit is contained in:
Will Anderson
2026-10-03 09:26:05 +00:00
committed by jamiepine
parent 51f49dea19
commit 851defa859
9 changed files with 54 additions and 54 deletions
+17 -17
View File
@@ -1,6 +1,6 @@
"""ORM model definitions for the voicebox SQLite database."""
from datetime import datetime
from datetime import UTC, datetime
import uuid
from sqlalchemy import Column, String, Integer, Float, DateTime, Text, ForeignKey, Boolean, JSON
@@ -44,8 +44,8 @@ class VoiceProfile(Base):
# cloning metadata above).
personality = Column(Text, nullable=True)
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
created_at = Column(DateTime, default=lambda: datetime.now(UTC))
updated_at = Column(DateTime, default=lambda: datetime.now(UTC), onupdate=lambda: datetime.now(UTC))
class ProfileSample(Base):
@@ -82,7 +82,7 @@ class Generation(Base):
# profile's personality LLM before TTS. Future sources (bulk import,
# agent replies, etc.) can extend this.
source = Column(String, nullable=False, default="manual")
created_at = Column(DateTime, default=datetime.utcnow)
created_at = Column(DateTime, default=lambda: datetime.now(UTC))
class Story(Base):
@@ -93,8 +93,8 @@ class Story(Base):
id = Column(String, primary_key=True, default=lambda: str(uuid.uuid4()))
name = Column(String, nullable=False)
description = Column(Text)
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
created_at = Column(DateTime, default=lambda: datetime.now(UTC))
updated_at = Column(DateTime, default=lambda: datetime.now(UTC), onupdate=lambda: datetime.now(UTC))
class StoryItem(Base):
@@ -111,7 +111,7 @@ class StoryItem(Base):
trim_start_ms = Column(Integer, nullable=False, default=0)
trim_end_ms = Column(Integer, nullable=False, default=0)
volume = Column(Float, nullable=False, default=1.0)
created_at = Column(DateTime, default=datetime.utcnow)
created_at = Column(DateTime, default=lambda: datetime.now(UTC))
class Project(Base):
@@ -122,8 +122,8 @@ class Project(Base):
id = Column(String, primary_key=True, default=lambda: str(uuid.uuid4()))
name = Column(String, nullable=False)
data = Column(Text)
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
created_at = Column(DateTime, default=lambda: datetime.now(UTC))
updated_at = Column(DateTime, default=lambda: datetime.now(UTC), onupdate=lambda: datetime.now(UTC))
class GenerationVersion(Base):
@@ -138,7 +138,7 @@ class GenerationVersion(Base):
effects_chain = Column(Text, nullable=True)
source_version_id = Column(String, ForeignKey("generation_versions.id"), nullable=True)
is_default = Column(Boolean, default=False)
created_at = Column(DateTime, default=datetime.utcnow)
created_at = Column(DateTime, default=lambda: datetime.now(UTC))
class EffectPreset(Base):
@@ -152,7 +152,7 @@ class EffectPreset(Base):
effects_chain = Column(Text, nullable=False)
is_builtin = Column(Boolean, default=False)
sort_order = Column(Integer, default=100)
created_at = Column(DateTime, default=datetime.utcnow)
created_at = Column(DateTime, default=lambda: datetime.now(UTC))
class AudioChannel(Base):
@@ -163,7 +163,7 @@ class AudioChannel(Base):
id = Column(String, primary_key=True, default=lambda: str(uuid.uuid4()))
name = Column(String, nullable=False)
is_default = Column(Boolean, default=False)
created_at = Column(DateTime, default=datetime.utcnow)
created_at = Column(DateTime, default=lambda: datetime.now(UTC))
class ChannelDeviceMapping(Base):
@@ -218,7 +218,7 @@ class CaptureSettings(Base):
chord_toggle_to_talk_keys = Column(
JSON, nullable=False, default=default_toggle_to_talk_chord
)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
updated_at = Column(DateTime, default=lambda: datetime.now(UTC), onupdate=lambda: datetime.now(UTC))
class GenerationSettings(Base):
@@ -231,7 +231,7 @@ class GenerationSettings(Base):
crossfade_ms = Column(Integer, nullable=False, default=50)
normalize_audio = Column(Boolean, nullable=False, default=True)
autoplay_on_generate = Column(Boolean, nullable=False, default=True)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
updated_at = Column(DateTime, default=lambda: datetime.now(UTC), onupdate=lambda: datetime.now(UTC))
class CloudSettings(Base):
@@ -276,8 +276,8 @@ class MCPClientBinding(Base):
# (rewrite) before TTS by default. Callers can still override per call.
default_personality = Column(Boolean, nullable=False, default=False)
last_seen_at = Column(DateTime, nullable=True)
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
created_at = Column(DateTime, default=lambda: datetime.now(UTC))
updated_at = Column(DateTime, default=lambda: datetime.now(UTC), onupdate=lambda: datetime.now(UTC))
class Capture(Base):
@@ -300,4 +300,4 @@ class Capture(Base):
stt_model = Column(String, nullable=True)
llm_model = Column(String, nullable=True)
refinement_flags = Column(Text, nullable=True) # JSON blob
created_at = Column(DateTime, default=datetime.utcnow)
created_at = Column(DateTime, default=lambda: datetime.now(UTC))
+2 -2
View File
@@ -4,7 +4,7 @@ import io
import json as _json
import logging
import tempfile
from datetime import datetime
from datetime import UTC, datetime
from pathlib import Path
from fastapi import APIRouter, Depends, File, Form, HTTPException, UploadFile
@@ -356,7 +356,7 @@ async def update_profile_effects(
else:
profile.effects_chain = None
profile.updated_at = datetime.utcnow()
profile.updated_at = datetime.now(UTC)
db.commit()
db.refresh(profile)
+3 -3
View File
@@ -1,6 +1,6 @@
"""Task and cache management endpoints."""
from datetime import datetime
from datetime import UTC, datetime
from fastapi import APIRouter
@@ -91,9 +91,9 @@ async def get_active_tasks():
try:
started_at = datetime.fromisoformat(timestamp_str.replace("Z", "+00:00"))
except (ValueError, AttributeError):
started_at = datetime.utcnow()
started_at = datetime.now(UTC)
else:
started_at = datetime.utcnow()
started_at = datetime.now(UTC)
active_downloads.append(
models.ActiveDownloadTask(
+2 -2
View File
@@ -3,7 +3,7 @@ Audio channel management module.
"""
from typing import List, Optional
from datetime import datetime
from datetime import UTC, datetime
import uuid
from sqlalchemy.orm import Session
@@ -81,7 +81,7 @@ async def create_channel(
id=str(uuid.uuid4()),
name=data.name,
is_default=False,
created_at=datetime.utcnow(),
created_at=datetime.now(UTC),
)
db.add(channel)
db.flush()
+1 -1
View File
@@ -432,7 +432,7 @@ async def import_generation_from_zip(file_bytes: bytes, db: Session) -> dict:
duration=generation_data["duration"],
seed=generation_data.get("seed"),
instruct=generation_data.get("instruct"),
created_at=datetime.utcnow(),
created_at=datetime.now(UTC),
)
db.add(db_generation)
+2 -2
View File
@@ -3,7 +3,7 @@ Generation history management module.
"""
from typing import List, Optional, Tuple
from datetime import datetime
from datetime import UTC, datetime
import uuid
import shutil
from pathlib import Path
@@ -104,7 +104,7 @@ async def create_generation(
model_size=model_size,
status=status,
source=source,
created_at=datetime.utcnow(),
created_at=datetime.now(UTC),
)
db.add(db_generation)
+7 -7
View File
@@ -4,7 +4,7 @@ import json as _json
import logging
import shutil
import uuid
from datetime import datetime
from datetime import UTC, datetime
from pathlib import Path
from sqlalchemy import func
@@ -183,8 +183,8 @@ async def create_profile(
design_prompt=data.design_prompt,
default_engine=default_engine,
personality=data.personality,
created_at=datetime.utcnow(),
updated_at=datetime.utcnow(),
created_at=datetime.now(UTC),
updated_at=datetime.now(UTC),
)
db.add(db_profile)
@@ -244,7 +244,7 @@ async def add_profile_sample(
db.add(db_sample)
profile.updated_at = datetime.utcnow()
profile.updated_at = datetime.now(UTC)
db.commit()
db.refresh(db_sample)
@@ -403,7 +403,7 @@ async def update_profile(
profile.personality = data.personality
if data.default_engine is not None:
profile.default_engine = data.default_engine or None # empty string → NULL
profile.updated_at = datetime.utcnow()
profile.updated_at = datetime.now(UTC)
db.commit()
db.refresh(profile)
@@ -672,7 +672,7 @@ async def upload_avatar(
process_avatar(image_path, str(output_path))
profile.avatar_path = config.to_storage_path(output_path)
profile.updated_at = datetime.utcnow()
profile.updated_at = datetime.now(UTC)
db.commit()
db.refresh(profile)
@@ -703,7 +703,7 @@ async def delete_avatar(
avatar_path.unlink()
profile.avatar_path = None
profile.updated_at = datetime.utcnow()
profile.updated_at = datetime.now(UTC)
db.commit()
+17 -17
View File
@@ -3,7 +3,7 @@ Story management module.
"""
from typing import List, Optional
from datetime import datetime
from datetime import UTC, datetime
import uuid
import tempfile
from pathlib import Path
@@ -96,8 +96,8 @@ async def create_story(
id=str(uuid.uuid4()),
name=data.name,
description=data.description,
created_at=datetime.utcnow(),
updated_at=datetime.utcnow(),
created_at=datetime.now(UTC),
updated_at=datetime.now(UTC),
)
db.add(db_story)
@@ -206,7 +206,7 @@ async def update_story(
story.name = data.name
story.description = data.description
story.updated_at = datetime.utcnow()
story.updated_at = datetime.now(UTC)
db.commit()
db.refresh(story)
@@ -314,13 +314,13 @@ async def add_item_to_story(
generation_id=data.generation_id,
start_time_ms=start_time_ms,
track=track,
created_at=datetime.utcnow(),
created_at=datetime.now(UTC),
)
db.add(item)
# Update story updated_at
story.updated_at = datetime.utcnow()
story.updated_at = datetime.now(UTC)
db.commit()
db.refresh(item)
@@ -373,7 +373,7 @@ async def move_story_item(
# Update story updated_at
story = db.query(DBStory).filter_by(id=story_id).first()
if story:
story.updated_at = datetime.utcnow()
story.updated_at = datetime.now(UTC)
db.commit()
db.refresh(item)
@@ -417,7 +417,7 @@ async def remove_item_from_story(
# Update story updated_at
story = db.query(DBStory).filter_by(id=story_id).first()
if story:
story.updated_at = datetime.utcnow()
story.updated_at = datetime.now(UTC)
db.commit()
return True
@@ -470,7 +470,7 @@ async def trim_story_item(
# Update story updated_at
story = db.query(DBStory).filter_by(id=story_id).first()
if story:
story.updated_at = datetime.utcnow()
story.updated_at = datetime.now(UTC)
db.commit()
db.refresh(item)
@@ -503,7 +503,7 @@ async def update_story_item_volume(
story = db.query(DBStory).filter_by(id=story_id).first()
if story:
story.updated_at = datetime.utcnow()
story.updated_at = datetime.now(UTC)
db.commit()
db.refresh(item)
@@ -576,7 +576,7 @@ async def split_story_item(
trim_start_ms=absolute_split_ms,
trim_end_ms=current_trim_end,
volume=getattr(item, "volume", 1.0),
created_at=datetime.utcnow(),
created_at=datetime.now(UTC),
)
db.add(new_item)
@@ -584,7 +584,7 @@ async def split_story_item(
# Update story updated_at
story = db.query(DBStory).filter_by(id=story_id).first()
if story:
story.updated_at = datetime.utcnow()
story.updated_at = datetime.now(UTC)
db.commit()
db.refresh(item)
@@ -650,7 +650,7 @@ async def duplicate_story_item(
trim_start_ms=current_trim_start,
trim_end_ms=current_trim_end,
volume=getattr(original_item, "volume", 1.0),
created_at=datetime.utcnow(),
created_at=datetime.now(UTC),
)
db.add(new_item)
@@ -658,7 +658,7 @@ async def duplicate_story_item(
# Update story updated_at
story = db.query(DBStory).filter_by(id=story_id).first()
if story:
story.updated_at = datetime.utcnow()
story.updated_at = datetime.now(UTC)
db.commit()
db.refresh(new_item)
@@ -700,7 +700,7 @@ async def update_story_item_times(
item_map[update.generation_id].start_time_ms = update.start_time_ms
# Update story updated_at
story.updated_at = datetime.utcnow()
story.updated_at = datetime.now(UTC)
db.commit()
return True
@@ -764,7 +764,7 @@ async def reorder_story_items(
updated_items.append(_build_item_detail(item, generation, profile_name, db))
# Update story updated_at
story.updated_at = datetime.utcnow()
story.updated_at = datetime.now(UTC)
db.commit()
return updated_items
@@ -823,7 +823,7 @@ async def set_story_item_version(
# Update story updated_at
story = db.query(DBStory).filter_by(id=story_id).first()
if story:
story.updated_at = datetime.utcnow()
story.updated_at = datetime.now(UTC)
db.commit()
db.refresh(item)
+3 -3
View File
@@ -3,7 +3,7 @@ Task tracking for active downloads and generations.
"""
from typing import Optional, Dict, List
from datetime import datetime
from datetime import UTC, datetime
from dataclasses import dataclass, field
@@ -12,7 +12,7 @@ class DownloadTask:
"""Represents an active download task."""
model_name: str
status: str = "downloading" # downloading, extracting, complete, error
started_at: datetime = field(default_factory=datetime.utcnow)
started_at: datetime = field(default_factory=lambda: datetime.now(UTC))
error: Optional[str] = None
@@ -22,7 +22,7 @@ class GenerationTask:
task_id: str
profile_id: str
text_preview: str # First 50 chars of text
started_at: datetime = field(default_factory=datetime.utcnow)
started_at: datetime = field(default_factory=lambda: datetime.now(UTC))
class TaskManager: