better time usings

This commit is contained in:
Lukas 2025-10-22 08:14:42 +02:00
parent 04b516a52d
commit 4eede0c8c0
11 changed files with 62 additions and 163 deletions

View File

@ -313,107 +313,6 @@ conda run -n AniWorld python -m pytest tests/ -v -s
--- ---
## ✅ Completed Sections (Can be removed from this document)
The following sections have been successfully completed:
### Deprecated Pydantic V1 API Usage
**Count:** ~20 warnings
**Location:** `src/config/settings.py`
**Issue:**
```python
# Deprecated usage
Field(default="value", env="ENV_VAR")
# Should be:
Field(default="value", json_schema_extra={"env": "ENV_VAR"})
```
**Files to Update:**
- `src/config/settings.py` - Update Field definitions
- `src/server/models/config.py` - Update validators to `@field_validator`
---
### Deprecated datetime.utcnow() Usage
**Count:** ~100+ warnings
**Issue:**
```python
# Deprecated
datetime.utcnow()
# Should use
datetime.now(datetime.UTC)
```
**Files to Update:**
- `src/server/services/auth_service.py`
- `src/server/services/download_service.py`
- `src/server/services/progress_service.py`
- `src/server/services/websocket_service.py`
- `src/server/database/service.py`
- `src/server/database/models.py`
- All test files using `datetime.utcnow()`
---
### Deprecated FastAPI on_event
**Count:** 4 warnings
**Location:** `src/server/fastapi_app.py`
**Issue:**
```python
# Deprecated
@app.on_event("startup")
@app.on_event("shutdown")
# Should use lifespan
from contextlib import asynccontextmanager
@asynccontextmanager
async def lifespan(app: FastAPI):
# Startup
yield
# Shutdown
```
---
### Deprecated Pydantic .dict() Method
**Count:** ~5 warnings
**Issue:**
```python
# Deprecated
session.dict()
# Should be
session.model_dump()
```
**Files to Update:**
- `src/server/middleware/auth.py`
- `src/server/utils/dependencies.py`
---
## 📝 Task Checklist for AI Agent
### Phase 4: Frontend Integration 🔄 IN PROGRESS ### Phase 4: Frontend Integration 🔄 IN PROGRESS
- [ ] Fix frontend auth integration tests (42 total → 4 remaining failures) - [ ] Fix frontend auth integration tests (42 total → 4 remaining failures)
@ -440,7 +339,7 @@ session.model_dump()
- TestFrontendRealTimeUpdates (3 failures): All real-time tests - TestFrontendRealTimeUpdates (3 failures): All real-time tests
- TestFrontendDataFormats (3 failures): All format tests - TestFrontendDataFormats (3 failures): All format tests
### Phase 5: WebSocket Integration ✅ MOSTLY COMPLETE ### Phase 5: WebSocket Integration
- [x] Fix websocket integration tests (48 failures → 2 remaining) - [x] Fix websocket integration tests (48 failures → 2 remaining)
- [x] Test connection management - [x] Test connection management
@ -452,7 +351,7 @@ session.model_dump()
- `test_concurrent_broadcasts_to_different_rooms` - `test_concurrent_broadcasts_to_different_rooms`
- `test_multi_room_workflow` - `test_multi_room_workflow`
### Phase 6: Download Flow ✅ MOSTLY COMPLETE ### Phase 6: Download Flow
- [x] Fix download endpoint API tests (18 errors → All 20 tests passing!) - [x] Fix download endpoint API tests (18 errors → All 20 tests passing!)
- [x] Fix download flow integration tests (22+ errors → 11 remaining) - [x] Fix download flow integration tests (22+ errors → 11 remaining)

View File

@ -3,7 +3,7 @@
This module provides the base class that all ORM models inherit from, This module provides the base class that all ORM models inherit from,
along with common functionality and mixins. along with common functionality and mixins.
""" """
from datetime import datetime from datetime import datetime, timezone
from typing import Any from typing import Any
from sqlalchemy import DateTime, func from sqlalchemy import DateTime, func
@ -67,7 +67,7 @@ class SoftDeleteMixin:
def soft_delete(self) -> None: def soft_delete(self) -> None:
"""Mark record as deleted without removing from database.""" """Mark record as deleted without removing from database."""
self.deleted_at = datetime.utcnow() self.deleted_at = datetime.now(timezone.utc)
def restore(self) -> None: def restore(self) -> None:
"""Restore a soft deleted record.""" """Restore a soft deleted record."""

View File

@ -11,7 +11,7 @@ Models:
""" """
from __future__ import annotations from __future__ import annotations
from datetime import datetime from datetime import datetime, timezone
from enum import Enum from enum import Enum
from typing import List, Optional from typing import List, Optional
@ -422,7 +422,7 @@ class UserSession(Base, TimestampMixin):
@property @property
def is_expired(self) -> bool: def is_expired(self) -> bool:
"""Check if session has expired.""" """Check if session has expired."""
return datetime.utcnow() > self.expires_at return datetime.now(timezone.utc) > self.expires_at
def revoke(self) -> None: def revoke(self) -> None:
"""Revoke this session.""" """Revoke this session."""

View File

@ -14,7 +14,7 @@ All services support both async and sync operations for flexibility.
from __future__ import annotations from __future__ import annotations
import logging import logging
from datetime import datetime, timedelta from datetime import datetime, timedelta, timezone
from typing import Dict, List, Optional from typing import Dict, List, Optional
from sqlalchemy import delete, select, update from sqlalchemy import delete, select, update
@ -276,7 +276,7 @@ class EpisodeService:
file_path=file_path, file_path=file_path,
file_size=file_size, file_size=file_size,
is_downloaded=is_downloaded, is_downloaded=is_downloaded,
download_date=datetime.utcnow() if is_downloaded else None, download_date=datetime.now(timezone.utc) if is_downloaded else None,
) )
db.add(episode) db.add(episode)
await db.flush() await db.flush()
@ -380,7 +380,7 @@ class EpisodeService:
episode.is_downloaded = True episode.is_downloaded = True
episode.file_path = file_path episode.file_path = file_path
episode.file_size = file_size episode.file_size = file_size
episode.download_date = datetime.utcnow() episode.download_date = datetime.now(timezone.utc)
await db.flush() await db.flush()
await db.refresh(episode) await db.refresh(episode)
@ -597,9 +597,9 @@ class DownloadQueueService:
# Update timestamps based on status # Update timestamps based on status
if status == DownloadStatus.DOWNLOADING and not item.started_at: if status == DownloadStatus.DOWNLOADING and not item.started_at:
item.started_at = datetime.utcnow() item.started_at = datetime.now(timezone.utc)
elif status in (DownloadStatus.COMPLETED, DownloadStatus.FAILED): elif status in (DownloadStatus.COMPLETED, DownloadStatus.FAILED):
item.completed_at = datetime.utcnow() item.completed_at = datetime.now(timezone.utc)
# Set error message for failed downloads # Set error message for failed downloads
if status == DownloadStatus.FAILED and error_message: if status == DownloadStatus.FAILED and error_message:
@ -807,7 +807,7 @@ class UserSessionService:
""" """
query = select(UserSession).where( query = select(UserSession).where(
UserSession.is_active == True, UserSession.is_active == True,
UserSession.expires_at > datetime.utcnow(), UserSession.expires_at > datetime.now(timezone.utc),
) )
if user_id: if user_id:
@ -833,8 +833,8 @@ class UserSessionService:
session = await UserSessionService.get_by_session_id(db, session_id) session = await UserSessionService.get_by_session_id(db, session_id)
if not session: if not session:
return None return None
session.last_activity = datetime.utcnow() session.last_activity = datetime.now(timezone.utc)
await db.flush() await db.flush()
await db.refresh(session) await db.refresh(session)
return session return session
@ -871,7 +871,7 @@ class UserSessionService:
""" """
result = await db.execute( result = await db.execute(
delete(UserSession).where( delete(UserSession).where(
UserSession.expires_at < datetime.utcnow() UserSession.expires_at < datetime.now(timezone.utc)
) )
) )
count = result.rowcount count = result.rowcount

View File

@ -6,7 +6,7 @@ easy to validate and test.
""" """
from __future__ import annotations from __future__ import annotations
from datetime import datetime from datetime import datetime, timezone
from typing import Optional from typing import Optional
from pydantic import BaseModel, Field, constr from pydantic import BaseModel, Field, constr
@ -53,5 +53,5 @@ class SessionModel(BaseModel):
session_id: str = Field(..., description="Unique session identifier") session_id: str = Field(..., description="Unique session identifier")
user: Optional[str] = Field(None, description="Username or identifier") user: Optional[str] = Field(None, description="Username or identifier")
created_at: datetime = Field(default_factory=datetime.utcnow) created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
expires_at: Optional[datetime] = Field(None) expires_at: Optional[datetime] = Field(None)

View File

@ -6,7 +6,7 @@ on serialization, validation, and OpenAPI documentation.
""" """
from __future__ import annotations from __future__ import annotations
from datetime import datetime from datetime import datetime, timezone
from enum import Enum from enum import Enum
from typing import List, Optional from typing import List, Optional
@ -80,8 +80,8 @@ class DownloadItem(BaseModel):
# Timestamps # Timestamps
added_at: datetime = Field( added_at: datetime = Field(
default_factory=datetime.utcnow, default_factory=lambda: datetime.now(timezone.utc),
description="When item was added to queue" description="When item was added to queue",
) )
started_at: Optional[datetime] = Field( started_at: Optional[datetime] = Field(
None, description="When download started" None, description="When download started"

View File

@ -6,7 +6,7 @@ for real-time updates.
""" """
from __future__ import annotations from __future__ import annotations
from datetime import datetime from datetime import datetime, timezone
from enum import Enum from enum import Enum
from typing import Any, Dict, Optional from typing import Any, Dict, Optional
@ -56,7 +56,7 @@ class WebSocketMessage(BaseModel):
..., description="Type of the message" ..., description="Type of the message"
) )
timestamp: str = Field( timestamp: str = Field(
default_factory=lambda: datetime.utcnow().isoformat(), default_factory=lambda: datetime.now(timezone.utc).isoformat(),
description="ISO 8601 timestamp when message was created", description="ISO 8601 timestamp when message was created",
) )
data: Dict[str, Any] = Field( data: Dict[str, Any] = Field(
@ -72,7 +72,7 @@ class DownloadProgressMessage(BaseModel):
description="Message type", description="Message type",
) )
timestamp: str = Field( timestamp: str = Field(
default_factory=lambda: datetime.utcnow().isoformat(), default_factory=lambda: datetime.now(timezone.utc).isoformat(),
description="ISO 8601 timestamp", description="ISO 8601 timestamp",
) )
data: Dict[str, Any] = Field( data: Dict[str, Any] = Field(
@ -89,7 +89,7 @@ class DownloadCompleteMessage(BaseModel):
description="Message type", description="Message type",
) )
timestamp: str = Field( timestamp: str = Field(
default_factory=lambda: datetime.utcnow().isoformat(), default_factory=lambda: datetime.now(timezone.utc).isoformat(),
description="ISO 8601 timestamp", description="ISO 8601 timestamp",
) )
data: Dict[str, Any] = Field( data: Dict[str, Any] = Field(
@ -105,7 +105,7 @@ class DownloadFailedMessage(BaseModel):
description="Message type", description="Message type",
) )
timestamp: str = Field( timestamp: str = Field(
default_factory=lambda: datetime.utcnow().isoformat(), default_factory=lambda: datetime.now(timezone.utc).isoformat(),
description="ISO 8601 timestamp", description="ISO 8601 timestamp",
) )
data: Dict[str, Any] = Field( data: Dict[str, Any] = Field(
@ -121,7 +121,7 @@ class QueueStatusMessage(BaseModel):
description="Message type", description="Message type",
) )
timestamp: str = Field( timestamp: str = Field(
default_factory=lambda: datetime.utcnow().isoformat(), default_factory=lambda: datetime.now(timezone.utc).isoformat(),
description="ISO 8601 timestamp", description="ISO 8601 timestamp",
) )
data: Dict[str, Any] = Field( data: Dict[str, Any] = Field(
@ -137,7 +137,7 @@ class SystemMessage(BaseModel):
..., description="System message type" ..., description="System message type"
) )
timestamp: str = Field( timestamp: str = Field(
default_factory=lambda: datetime.utcnow().isoformat(), default_factory=lambda: datetime.now(timezone.utc).isoformat(),
description="ISO 8601 timestamp", description="ISO 8601 timestamp",
) )
data: Dict[str, Any] = Field( data: Dict[str, Any] = Field(
@ -152,7 +152,7 @@ class ErrorMessage(BaseModel):
default=WebSocketMessageType.ERROR, description="Message type" default=WebSocketMessageType.ERROR, description="Message type"
) )
timestamp: str = Field( timestamp: str = Field(
default_factory=lambda: datetime.utcnow().isoformat(), default_factory=lambda: datetime.now(timezone.utc).isoformat(),
description="ISO 8601 timestamp", description="ISO 8601 timestamp",
) )
data: Dict[str, Any] = Field( data: Dict[str, Any] = Field(
@ -167,7 +167,7 @@ class ConnectionMessage(BaseModel):
..., description="Connection message type" ..., description="Connection message type"
) )
timestamp: str = Field( timestamp: str = Field(
default_factory=lambda: datetime.utcnow().isoformat(), default_factory=lambda: datetime.now(timezone.utc).isoformat(),
description="ISO 8601 timestamp", description="ISO 8601 timestamp",
) )
data: Dict[str, Any] = Field( data: Dict[str, Any] = Field(
@ -203,7 +203,7 @@ class ScanProgressMessage(BaseModel):
description="Message type", description="Message type",
) )
timestamp: str = Field( timestamp: str = Field(
default_factory=lambda: datetime.utcnow().isoformat(), default_factory=lambda: datetime.now(timezone.utc).isoformat(),
description="ISO 8601 timestamp", description="ISO 8601 timestamp",
) )
data: Dict[str, Any] = Field( data: Dict[str, Any] = Field(
@ -220,7 +220,7 @@ class ScanCompleteMessage(BaseModel):
description="Message type", description="Message type",
) )
timestamp: str = Field( timestamp: str = Field(
default_factory=lambda: datetime.utcnow().isoformat(), default_factory=lambda: datetime.now(timezone.utc).isoformat(),
description="ISO 8601 timestamp", description="ISO 8601 timestamp",
) )
data: Dict[str, Any] = Field( data: Dict[str, Any] = Field(
@ -237,7 +237,7 @@ class ScanFailedMessage(BaseModel):
description="Message type", description="Message type",
) )
timestamp: str = Field( timestamp: str = Field(
default_factory=lambda: datetime.utcnow().isoformat(), default_factory=lambda: datetime.now(timezone.utc).isoformat(),
description="ISO 8601 timestamp", description="ISO 8601 timestamp",
) )
data: Dict[str, Any] = Field( data: Dict[str, Any] = Field(

View File

@ -12,7 +12,7 @@ can call it from async routes via threadpool if needed.
from __future__ import annotations from __future__ import annotations
import hashlib import hashlib
from datetime import datetime, timedelta from datetime import datetime, timedelta, timezone
from typing import Dict, Optional from typing import Dict, Optional
from jose import JWTError, jwt # type: ignore from jose import JWTError, jwt # type: ignore
@ -103,10 +103,10 @@ class AuthService:
def _record_failure(self, identifier: str) -> None: def _record_failure(self, identifier: str) -> None:
rec = self._get_fail_record(identifier) rec = self._get_fail_record(identifier)
rec["count"] += 1 rec["count"] += 1
rec["last"] = datetime.utcnow() rec["last"] = datetime.now(timezone.utc)
if rec["count"] >= self.max_attempts: if rec["count"] >= self.max_attempts:
rec["locked_until"] = ( rec["locked_until"] = (
datetime.utcnow() + timedelta(seconds=self.lockout_seconds) datetime.now(timezone.utc) + timedelta(seconds=self.lockout_seconds)
) )
def _clear_failures(self, identifier: str) -> None: def _clear_failures(self, identifier: str) -> None:
@ -116,11 +116,11 @@ class AuthService:
def _check_locked(self, identifier: str) -> None: def _check_locked(self, identifier: str) -> None:
rec = self._get_fail_record(identifier) rec = self._get_fail_record(identifier)
lu = rec.get("locked_until") lu = rec.get("locked_until")
if lu and datetime.utcnow() < lu: if lu and datetime.now(timezone.utc) < lu:
raise LockedOutError( raise LockedOutError(
"Too many failed attempts - temporarily locked out" "Too many failed attempts - temporarily locked out"
) )
if lu and datetime.utcnow() >= lu: if lu and datetime.now(timezone.utc) >= lu:
# lock expired, reset # lock expired, reset
self._failed[identifier] = { self._failed[identifier] = {
"count": 0, "count": 0,
@ -155,13 +155,13 @@ class AuthService:
def create_access_token( def create_access_token(
self, subject: str = "master", remember: bool = False self, subject: str = "master", remember: bool = False
) -> LoginResponse: ) -> LoginResponse:
expiry = datetime.utcnow() + timedelta( expiry = datetime.now(timezone.utc) + timedelta(
hours=(168 if remember else self.token_expiry_hours) hours=(168 if remember else self.token_expiry_hours)
) )
payload = { payload = {
"sub": subject, "sub": subject,
"exp": int(expiry.timestamp()), "exp": int(expiry.timestamp()),
"iat": int(datetime.utcnow().timestamp()), "iat": int(datetime.now(timezone.utc).timestamp()),
} }
token = jwt.encode(payload, self.secret, algorithm="HS256") token = jwt.encode(payload, self.secret, algorithm="HS256")
@ -180,7 +180,7 @@ class AuthService:
data = self.decode_token(token) data = self.decode_token(token)
exp_val = data.get("exp") exp_val = data.get("exp")
expires_at = ( expires_at = (
datetime.utcfromtimestamp(exp_val) if exp_val is not None else None datetime.fromtimestamp(exp_val, timezone.utc) if exp_val is not None else None
) )
return SessionModel( return SessionModel(
session_id=hashlib.sha256(token.encode()).hexdigest(), session_id=hashlib.sha256(token.encode()).hexdigest(),

View File

@ -11,7 +11,7 @@ import json
import uuid import uuid
from collections import deque from collections import deque
from concurrent.futures import ThreadPoolExecutor from concurrent.futures import ThreadPoolExecutor
from datetime import datetime from datetime import datetime, timezone
from pathlib import Path from pathlib import Path
from typing import Callable, Dict, List, Optional from typing import Callable, Dict, List, Optional
@ -183,7 +183,7 @@ class DownloadService:
item.model_dump(mode="json") item.model_dump(mode="json")
for item in self._failed_items for item in self._failed_items
], ],
"timestamp": datetime.utcnow().isoformat(), "timestamp": datetime.now(timezone.utc).isoformat(),
} }
with open(self._persistence_path, "w", encoding="utf-8") as f: with open(self._persistence_path, "w", encoding="utf-8") as f:
@ -225,7 +225,7 @@ class DownloadService:
episode=episode, episode=episode,
status=DownloadStatus.PENDING, status=DownloadStatus.PENDING,
priority=priority, priority=priority,
added_at=datetime.utcnow(), added_at=datetime.now(timezone.utc),
) )
# Insert based on priority # Insert based on priority
@ -284,7 +284,7 @@ class DownloadService:
if item_id in self._active_downloads: if item_id in self._active_downloads:
item = self._active_downloads[item_id] item = self._active_downloads[item_id]
item.status = DownloadStatus.CANCELLED item.status = DownloadStatus.CANCELLED
item.completed_at = datetime.utcnow() item.completed_at = datetime.now(timezone.utc)
self._failed_items.append(item) self._failed_items.append(item)
del self._active_downloads[item_id] del self._active_downloads[item_id]
removed_ids.append(item_id) removed_ids.append(item_id)
@ -673,7 +673,7 @@ class DownloadService:
try: try:
# Update status # Update status
item.status = DownloadStatus.DOWNLOADING item.status = DownloadStatus.DOWNLOADING
item.started_at = datetime.utcnow() item.started_at = datetime.now(timezone.utc)
self._active_downloads[item.id] = item self._active_downloads[item.id] = item
logger.info( logger.info(
@ -715,7 +715,7 @@ class DownloadService:
# Handle result # Handle result
if success: if success:
item.status = DownloadStatus.COMPLETED item.status = DownloadStatus.COMPLETED
item.completed_at = datetime.utcnow() item.completed_at = datetime.now(timezone.utc)
# Track downloaded size # Track downloaded size
if item.progress and item.progress.downloaded_mb: if item.progress and item.progress.downloaded_mb:
@ -757,7 +757,7 @@ class DownloadService:
except Exception as e: except Exception as e:
# Handle failure # Handle failure
item.status = DownloadStatus.FAILED item.status = DownloadStatus.FAILED
item.completed_at = datetime.utcnow() item.completed_at = datetime.now(timezone.utc)
item.error = str(e) item.error = str(e)
self._failed_items.append(item) self._failed_items.append(item)

View File

@ -9,7 +9,7 @@ from __future__ import annotations
import asyncio import asyncio
from dataclasses import dataclass, field from dataclasses import dataclass, field
from datetime import datetime from datetime import datetime, timezone
from enum import Enum from enum import Enum
from typing import Any, Callable, Dict, Optional from typing import Any, Callable, Dict, Optional
@ -65,8 +65,8 @@ class ProgressUpdate:
current: int = 0 current: int = 0
total: int = 0 total: int = 0
metadata: Dict[str, Any] = field(default_factory=dict) metadata: Dict[str, Any] = field(default_factory=dict)
started_at: datetime = field(default_factory=datetime.utcnow) started_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
updated_at: datetime = field(default_factory=datetime.utcnow) updated_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
def to_dict(self) -> Dict[str, Any]: def to_dict(self) -> Dict[str, Any]:
"""Convert progress update to dictionary.""" """Convert progress update to dictionary."""
@ -254,7 +254,7 @@ class ProgressService:
update.percent = 0.0 update.percent = 0.0
update.status = ProgressStatus.IN_PROGRESS update.status = ProgressStatus.IN_PROGRESS
update.updated_at = datetime.utcnow() update.updated_at = datetime.now(timezone.utc)
# Only broadcast if significant change or forced # Only broadcast if significant change or forced
percent_change = abs(update.percent - old_percent) percent_change = abs(update.percent - old_percent)
@ -296,7 +296,7 @@ class ProgressService:
update.message = message update.message = message
update.percent = 100.0 update.percent = 100.0
update.current = update.total update.current = update.total
update.updated_at = datetime.utcnow() update.updated_at = datetime.now(timezone.utc)
if metadata: if metadata:
update.metadata.update(metadata) update.metadata.update(metadata)
@ -345,7 +345,7 @@ class ProgressService:
update = self._active_progress[progress_id] update = self._active_progress[progress_id]
update.status = ProgressStatus.FAILED update.status = ProgressStatus.FAILED
update.message = error_message update.message = error_message
update.updated_at = datetime.utcnow() update.updated_at = datetime.now(timezone.utc)
if metadata: if metadata:
update.metadata.update(metadata) update.metadata.update(metadata)
@ -393,7 +393,7 @@ class ProgressService:
update = self._active_progress[progress_id] update = self._active_progress[progress_id]
update.status = ProgressStatus.CANCELLED update.status = ProgressStatus.CANCELLED
update.message = message update.message = message
update.updated_at = datetime.utcnow() update.updated_at = datetime.now(timezone.utc)
# Move to history # Move to history
del self._active_progress[progress_id] del self._active_progress[progress_id]

View File

@ -8,7 +8,7 @@ from __future__ import annotations
import asyncio import asyncio
from collections import defaultdict from collections import defaultdict
from datetime import datetime from datetime import datetime, timezone
from typing import Any, Dict, List, Optional, Set from typing import Any, Dict, List, Optional, Set
import structlog import structlog
@ -346,7 +346,7 @@ class WebSocketService:
user_id: Optional user identifier for authentication user_id: Optional user identifier for authentication
""" """
metadata = { metadata = {
"connected_at": datetime.utcnow().isoformat(), "connected_at": datetime.now(timezone.utc).isoformat(),
"user_id": user_id, "user_id": user_id,
} }
await self._manager.connect(websocket, connection_id, metadata) await self._manager.connect(websocket, connection_id, metadata)
@ -366,7 +366,7 @@ class WebSocketService:
""" """
message = { message = {
"type": "download_progress", "type": "download_progress",
"timestamp": datetime.utcnow().isoformat(), "timestamp": datetime.now(timezone.utc).isoformat(),
"data": { "data": {
"download_id": download_id, "download_id": download_id,
**progress_data, **progress_data,
@ -385,7 +385,7 @@ class WebSocketService:
""" """
message = { message = {
"type": "download_complete", "type": "download_complete",
"timestamp": datetime.utcnow().isoformat(), "timestamp": datetime.now(timezone.utc).isoformat(),
"data": { "data": {
"download_id": download_id, "download_id": download_id,
**result_data, **result_data,
@ -404,7 +404,7 @@ class WebSocketService:
""" """
message = { message = {
"type": "download_failed", "type": "download_failed",
"timestamp": datetime.utcnow().isoformat(), "timestamp": datetime.now(timezone.utc).isoformat(),
"data": { "data": {
"download_id": download_id, "download_id": download_id,
**error_data, **error_data,
@ -420,7 +420,7 @@ class WebSocketService:
""" """
message = { message = {
"type": "queue_status", "type": "queue_status",
"timestamp": datetime.utcnow().isoformat(), "timestamp": datetime.now(timezone.utc).isoformat(),
"data": status_data, "data": status_data,
} }
await self._manager.broadcast_to_room(message, "downloads") await self._manager.broadcast_to_room(message, "downloads")
@ -436,7 +436,7 @@ class WebSocketService:
""" """
message = { message = {
"type": f"system_{message_type}", "type": f"system_{message_type}",
"timestamp": datetime.utcnow().isoformat(), "timestamp": datetime.now(timezone.utc).isoformat(),
"data": data, "data": data,
} }
await self._manager.broadcast(message) await self._manager.broadcast(message)
@ -453,7 +453,7 @@ class WebSocketService:
""" """
message = { message = {
"type": "error", "type": "error",
"timestamp": datetime.utcnow().isoformat(), "timestamp": datetime.now(timezone.utc).isoformat(),
"data": { "data": {
"code": error_code, "code": error_code,
"message": error_message, "message": error_message,