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