better time usings
This commit is contained in:
parent
04b516a52d
commit
4eede0c8c0
@ -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)
|
||||||
|
|||||||
@ -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."""
|
||||||
|
|||||||
@ -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."""
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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"
|
||||||
|
|||||||
@ -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(
|
||||||
|
|||||||
@ -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(),
|
||||||
|
|||||||
@ -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)
|
||||||
|
|
||||||
|
|||||||
@ -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]
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user