diff --git a/Makefile b/Makefile index d201da6a..2e174aca 100644 --- a/Makefile +++ b/Makefile @@ -175,24 +175,24 @@ test-inventory: # ============================================================================= format: - @echo "Running black..." - $(PYTHON) -m black . --exclude '/(\.)?venv/' - @echo "Running isort..." - $(PYTHON) -m isort . --skip venv --skip .venv + @echo "Formatting code with ruff..." + $(PYTHON) -m ruff format . lint: - @echo "Running linting..." - $(PYTHON) -m ruff check . --exclude venv --exclude .venv - $(PYTHON) -m mypy . --ignore-missing-imports --exclude '.*(\.)?venv.*' + @echo "Linting code with ruff..." + $(PYTHON) -m ruff check . --fix + @echo "Type checking with mypy..." + $(PYTHON) -m mypy . -lint-flake8: - @echo "Running linting..." - $(PYTHON) -m flake8 . --max-line-length=120 --extend-ignore=E203,W503,I201,I100 --exclude=venv,.venv,__pycache__,.git - $(PYTHON) -m mypy . --ignore-missing-imports --exclude '.*(\.)?venv.*' +lint-strict: + @echo "Linting (no auto-fix)..." + $(PYTHON) -m ruff check . + @echo "Type checking with mypy..." + $(PYTHON) -m mypy . check: format lint -ci: format lint test-coverage +ci: lint-strict test-coverage qa: format lint test-coverage docs-check @echo "Quality assurance checks completed!" @@ -330,10 +330,11 @@ help: @echo " test-fast - Run fast tests only" @echo "" @echo "=== CODE QUALITY ===" - @echo " format - Format code (black + isort)" - @echo " lint - Run linting (ruff + mypy)" + @echo " format - Format code with ruff" + @echo " lint - Lint and auto-fix with ruff + mypy" + @echo " lint-strict - Lint without auto-fix + mypy" @echo " check - Format + lint" - @echo " ci - Full CI pipeline" + @echo " ci - Full CI pipeline (strict)" @echo " qa - Quality assurance" @echo "" @echo "=== DOCUMENTATION ===" diff --git a/alembic/env.py b/alembic/env.py index 8605ecc3..8217dcbe 100644 --- a/alembic/env.py +++ b/alembic/env.py @@ -40,9 +40,13 @@ print("=" * 70) # ADMIN MODELS # ---------------------------------------------------------------------------- try: - from models.database.admin import (AdminAuditLog, AdminNotification, - AdminSession, AdminSetting, - PlatformAlert) + from models.database.admin import ( + AdminAuditLog, + AdminNotification, + AdminSession, + AdminSetting, + PlatformAlert, + ) print(" ā Admin models imported (5 models)") print(" - AdminAuditLog") @@ -176,8 +180,8 @@ except ImportError as e: # SUMMARY # ============================================================================ print("=" * 70) -print(f"[ALEMBIC] Model import completed") -print(f"[ALEMBIC] Tables detected in metadata:") +print("[ALEMBIC] Model import completed") +print("[ALEMBIC] Tables detected in metadata:") print("=" * 70) if Base.metadata.tables: diff --git a/app/api/deps.py b/app/api/deps.py index f2761a9a..3b2413fc 100644 --- a/app/api/deps.py +++ b/app/api/deps.py @@ -32,18 +32,20 @@ The cookie path restrictions prevent cross-context cookie leakage: """ import logging -from typing import Optional +from datetime import UTC from fastapi import Cookie, Depends, Request from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer from sqlalchemy.orm import Session from app.core.database import get_db -from app.exceptions import (AdminRequiredException, - InsufficientPermissionsException, - InvalidTokenException, - UnauthorizedVendorAccessException, - VendorNotFoundException) +from app.exceptions import ( + AdminRequiredException, + InsufficientPermissionsException, + InvalidTokenException, + UnauthorizedVendorAccessException, + VendorNotFoundException, +) from middleware.auth import AuthManager from middleware.rate_limiter import RateLimiter from models.database.user import User @@ -62,11 +64,11 @@ logger = logging.getLogger(__name__) def _get_token_from_request( - credentials: Optional[HTTPAuthorizationCredentials], - cookie_value: Optional[str], + credentials: HTTPAuthorizationCredentials | None, + cookie_value: str | None, cookie_name: str, request_path: str, -) -> tuple[Optional[str], Optional[str]]: +) -> tuple[str | None, str | None]: """ Extract token from Authorization header or cookie. @@ -86,7 +88,7 @@ def _get_token_from_request( if credentials: logger.debug(f"Token found in Authorization header for {request_path}") return credentials.credentials, "header" - elif cookie_value: + if cookie_value: logger.debug(f"Token found in {cookie_name} cookie for {request_path}") return cookie_value, "cookie" @@ -118,8 +120,8 @@ def _validate_user_token(token: str, db: Session) -> User: def get_current_admin_from_cookie_or_header( request: Request, - credentials: Optional[HTTPAuthorizationCredentials] = Depends(security), - admin_token: Optional[str] = Cookie(None), + credentials: HTTPAuthorizationCredentials | None = Depends(security), + admin_token: str | None = Cookie(None), db: Session = Depends(get_db), ) -> User: """ @@ -205,8 +207,8 @@ def get_current_admin_api( def get_current_vendor_from_cookie_or_header( request: Request, - credentials: Optional[HTTPAuthorizationCredentials] = Depends(security), - vendor_token: Optional[str] = Cookie(None), + credentials: HTTPAuthorizationCredentials | None = Depends(security), + vendor_token: str | None = Cookie(None), db: Session = Depends(get_db), ) -> User: """ @@ -305,8 +307,8 @@ def get_current_vendor_api( def get_current_customer_from_cookie_or_header( request: Request, - credentials: Optional[HTTPAuthorizationCredentials] = Depends(security), - customer_token: Optional[str] = Cookie(None), + credentials: HTTPAuthorizationCredentials | None = Depends(security), + customer_token: str | None = Cookie(None), db: Session = Depends(get_db), ): """ @@ -331,7 +333,7 @@ def get_current_customer_from_cookie_or_header( Raises: InvalidTokenException: If no token or invalid token """ - from datetime import datetime, timezone + from datetime import datetime from jose import JWTError, jwt @@ -365,8 +367,8 @@ def get_current_customer_from_cookie_or_header( # Verify token hasn't expired exp = payload.get("exp") - if exp and datetime.fromtimestamp(exp, tz=timezone.utc) < datetime.now( - timezone.utc + if exp and datetime.fromtimestamp(exp, tz=UTC) < datetime.now( + UTC ): logger.warning(f"Expired customer token for customer_id={customer_id}") raise InvalidTokenException("Token has expired") @@ -694,10 +696,10 @@ def get_user_permissions( def get_current_admin_optional( request: Request, - credentials: Optional[HTTPAuthorizationCredentials] = Depends(security), - admin_token: Optional[str] = Cookie(None), + credentials: HTTPAuthorizationCredentials | None = Depends(security), + admin_token: str | None = Cookie(None), db: Session = Depends(get_db), -) -> Optional[User]: +) -> User | None: """ Get current admin user from admin_token cookie or Authorization header. @@ -741,10 +743,10 @@ def get_current_admin_optional( def get_current_vendor_optional( request: Request, - credentials: Optional[HTTPAuthorizationCredentials] = Depends(security), - vendor_token: Optional[str] = Cookie(None), + credentials: HTTPAuthorizationCredentials | None = Depends(security), + vendor_token: str | None = Cookie(None), db: Session = Depends(get_db), -) -> Optional[User]: +) -> User | None: """ Get current vendor user from vendor_token cookie or Authorization header. @@ -788,10 +790,10 @@ def get_current_vendor_optional( def get_current_customer_optional( request: Request, - credentials: Optional[HTTPAuthorizationCredentials] = Depends(security), - customer_token: Optional[str] = Cookie(None), + credentials: HTTPAuthorizationCredentials | None = Depends(security), + customer_token: str | None = Cookie(None), db: Session = Depends(get_db), -) -> Optional[User]: +) -> User | None: """ Get current customer user from customer_token cookie or Authorization header. diff --git a/app/api/v1/admin/__init__.py b/app/api/v1/admin/__init__.py index a7b9d009..2d261c8a 100644 --- a/app/api/v1/admin/__init__.py +++ b/app/api/v1/admin/__init__.py @@ -24,9 +24,21 @@ IMPORTANT: from fastapi import APIRouter # Import all admin routers -from . import (audit, auth, code_quality, content_pages, dashboard, - marketplace, monitoring, notifications, settings, users, - vendor_domains, vendor_themes, vendors) +from . import ( + audit, + auth, + code_quality, + content_pages, + dashboard, + marketplace, + monitoring, + notifications, + settings, + users, + vendor_domains, + vendor_themes, + vendors, +) # Create admin router router = APIRouter() diff --git a/app/api/v1/admin/audit.py b/app/api/v1/admin/audit.py index d3d91511..bf78a8eb 100644 --- a/app/api/v1/admin/audit.py +++ b/app/api/v1/admin/audit.py @@ -10,7 +10,6 @@ Provides endpoints for: import logging from datetime import datetime -from typing import Optional from fastapi import APIRouter, Depends, Query from sqlalchemy.orm import Session @@ -19,9 +18,11 @@ from app.api.deps import get_current_admin_api from app.core.database import get_db from app.services.admin_audit_service import admin_audit_service from models.database.user import User -from models.schema.admin import (AdminAuditLogFilters, - AdminAuditLogListResponse, - AdminAuditLogResponse) +from models.schema.admin import ( + AdminAuditLogFilters, + AdminAuditLogListResponse, + AdminAuditLogResponse, +) router = APIRouter(prefix="/audit") logger = logging.getLogger(__name__) @@ -29,11 +30,11 @@ logger = logging.getLogger(__name__) @router.get("/logs", response_model=AdminAuditLogListResponse) def get_audit_logs( - admin_user_id: Optional[int] = Query(None, description="Filter by admin user"), - action: Optional[str] = Query(None, description="Filter by action type"), - target_type: Optional[str] = Query(None, description="Filter by target type"), - date_from: Optional[datetime] = Query(None, description="Filter from date"), - date_to: Optional[datetime] = Query(None, description="Filter to date"), + admin_user_id: int | None = Query(None, description="Filter by admin user"), + action: str | None = Query(None, description="Filter by action type"), + target_type: str | None = Query(None, description="Filter by target type"), + date_from: datetime | None = Query(None, description="Filter from date"), + date_to: datetime | None = Query(None, description="Filter to date"), skip: int = Query(0, ge=0, description="Number of records to skip"), limit: int = Query(100, ge=1, le=1000, description="Maximum records to return"), db: Session = Depends(get_db), diff --git a/app/api/v1/admin/code_quality.py b/app/api/v1/admin/code_quality.py index 8193638b..bc82d4e8 100644 --- a/app/api/v1/admin/code_quality.py +++ b/app/api/v1/admin/code_quality.py @@ -4,7 +4,6 @@ RESTful API for architecture validation and violation management """ from datetime import datetime -from typing import Optional from fastapi import APIRouter, Depends, HTTPException, Query from pydantic import BaseModel, Field @@ -32,7 +31,7 @@ class ScanResponse(BaseModel): warnings: int duration_seconds: float triggered_by: str - git_commit_hash: Optional[str] + git_commit_hash: str | None class Config: from_attributes = True @@ -49,13 +48,13 @@ class ViolationResponse(BaseModel): file_path: str line_number: int message: str - context: Optional[str] - suggestion: Optional[str] + context: str | None + suggestion: str | None status: str - assigned_to: Optional[int] - resolved_at: Optional[str] - resolved_by: Optional[int] - resolution_note: Optional[str] + assigned_to: int | None + resolved_at: str | None + resolved_by: int | None + resolution_note: str | None created_at: str class Config: @@ -83,7 +82,7 @@ class AssignViolationRequest(BaseModel): """Request model for assigning a violation""" user_id: int = Field(..., description="User ID to assign to") - due_date: Optional[datetime] = Field(None, description="Due date for resolution") + due_date: datetime | None = Field(None, description="Due date for resolution") priority: str = Field( "medium", description="Priority level (low, medium, high, critical)" ) @@ -123,7 +122,7 @@ class DashboardStatsResponse(BaseModel): by_rule: dict by_module: dict top_files: list - last_scan: Optional[str] + last_scan: str | None # API Endpoints @@ -189,17 +188,17 @@ async def list_scans( @router.get("/violations", response_model=ViolationListResponse) async def list_violations( - scan_id: Optional[int] = Query( + scan_id: int | None = Query( None, description="Filter by scan ID (defaults to latest)" ), - severity: Optional[str] = Query( + severity: str | None = Query( None, description="Filter by severity (error, warning)" ), - status: Optional[str] = Query( + status: str | None = Query( None, description="Filter by status (open, assigned, resolved, ignored)" ), - rule_id: Optional[str] = Query(None, description="Filter by rule ID"), - file_path: Optional[str] = Query( + rule_id: str | None = Query(None, description="Filter by rule ID"), + file_path: str | None = Query( None, description="Filter by file path (partial match)" ), page: int = Query(1, ge=1, description="Page number"), diff --git a/app/api/v1/admin/content_pages.py b/app/api/v1/admin/content_pages.py index aceb521c..653c8397 100644 --- a/app/api/v1/admin/content_pages.py +++ b/app/api/v1/admin/content_pages.py @@ -9,7 +9,6 @@ Platform administrators can: """ import logging -from typing import List, Optional from fastapi import APIRouter, Depends, HTTPException, Query from pydantic import BaseModel, Field @@ -46,17 +45,17 @@ class ContentPageCreate(BaseModel): max_length=50, description="Template name (default, minimal, modern)", ) - meta_description: Optional[str] = Field( + meta_description: str | None = Field( None, max_length=300, description="SEO meta description" ) - meta_keywords: Optional[str] = Field( + meta_keywords: str | None = Field( None, max_length=300, description="SEO keywords" ) is_published: bool = Field(default=False, description="Publish immediately") show_in_footer: bool = Field(default=True, description="Show in footer navigation") show_in_header: bool = Field(default=False, description="Show in header navigation") display_order: int = Field(default=0, description="Display order (lower = first)") - vendor_id: Optional[int] = Field( + vendor_id: int | None = Field( None, description="Vendor ID (None for platform default)" ) @@ -64,32 +63,32 @@ class ContentPageCreate(BaseModel): class ContentPageUpdate(BaseModel): """Schema for updating a content page.""" - title: Optional[str] = Field(None, max_length=200) - content: Optional[str] = None - content_format: Optional[str] = None - template: Optional[str] = Field(None, max_length=50) - meta_description: Optional[str] = Field(None, max_length=300) - meta_keywords: Optional[str] = Field(None, max_length=300) - is_published: Optional[bool] = None - show_in_footer: Optional[bool] = None - show_in_header: Optional[bool] = None - display_order: Optional[int] = None + title: str | None = Field(None, max_length=200) + content: str | None = None + content_format: str | None = None + template: str | None = Field(None, max_length=50) + meta_description: str | None = Field(None, max_length=300) + meta_keywords: str | None = Field(None, max_length=300) + is_published: bool | None = None + show_in_footer: bool | None = None + show_in_header: bool | None = None + display_order: int | None = None class ContentPageResponse(BaseModel): """Schema for content page response.""" id: int - vendor_id: Optional[int] - vendor_name: Optional[str] + vendor_id: int | None + vendor_name: str | None slug: str title: str content: str content_format: str - meta_description: Optional[str] - meta_keywords: Optional[str] + meta_description: str | None + meta_keywords: str | None is_published: bool - published_at: Optional[str] + published_at: str | None display_order: int show_in_footer: bool show_in_header: bool @@ -97,8 +96,8 @@ class ContentPageResponse(BaseModel): is_vendor_override: bool created_at: str updated_at: str - created_by: Optional[int] - updated_by: Optional[int] + created_by: int | None + updated_by: int | None # ============================================================================ @@ -106,7 +105,7 @@ class ContentPageResponse(BaseModel): # ============================================================================ -@router.get("/platform", response_model=List[ContentPageResponse]) +@router.get("/platform", response_model=list[ContentPageResponse]) def list_platform_pages( include_unpublished: bool = Query(False, description="Include draft pages"), current_user: User = Depends(get_current_admin_api), @@ -161,9 +160,9 @@ def create_platform_page( # ============================================================================ -@router.get("/", response_model=List[ContentPageResponse]) +@router.get("/", response_model=list[ContentPageResponse]) def list_all_pages( - vendor_id: Optional[int] = Query(None, description="Filter by vendor ID"), + vendor_id: int | None = Query(None, description="Filter by vendor ID"), include_unpublished: bool = Query(False, description="Include draft pages"), current_user: User = Depends(get_current_admin_api), db: Session = Depends(get_db), @@ -256,4 +255,4 @@ def delete_page( if not success: raise HTTPException(status_code=404, detail="Content page not found") - return None + return diff --git a/app/api/v1/admin/dashboard.py b/app/api/v1/admin/dashboard.py index 1ca77a50..0a599156 100644 --- a/app/api/v1/admin/dashboard.py +++ b/app/api/v1/admin/dashboard.py @@ -4,7 +4,6 @@ Admin dashboard and statistics endpoints. """ import logging -from typing import List from fastapi import APIRouter, Depends from sqlalchemy.orm import Session @@ -57,7 +56,7 @@ def get_comprehensive_stats( ) -@router.get("/stats/marketplace", response_model=List[MarketplaceStatsResponse]) +@router.get("/stats/marketplace", response_model=list[MarketplaceStatsResponse]) def get_marketplace_stats( db: Session = Depends(get_db), current_admin: User = Depends(get_current_admin_api), diff --git a/app/api/v1/admin/marketplace.py b/app/api/v1/admin/marketplace.py index b9456a53..3319bb51 100644 --- a/app/api/v1/admin/marketplace.py +++ b/app/api/v1/admin/marketplace.py @@ -4,7 +4,6 @@ Marketplace import job monitoring endpoints for admin. """ import logging -from typing import List, Optional from fastapi import APIRouter, Depends, Query from sqlalchemy.orm import Session @@ -20,11 +19,11 @@ router = APIRouter(prefix="/marketplace-import-jobs") logger = logging.getLogger(__name__) -@router.get("", response_model=List[MarketplaceImportJobResponse]) +@router.get("", response_model=list[MarketplaceImportJobResponse]) def get_all_marketplace_import_jobs( - marketplace: Optional[str] = Query(None), - vendor_name: Optional[str] = Query(None), - status: Optional[str] = Query(None), + marketplace: str | None = Query(None), + vendor_name: str | None = Query(None), + status: str | None = Query(None), skip: int = Query(0, ge=0), limit: int = Query(100, ge=1, le=100), db: Session = Depends(get_db), diff --git a/app/api/v1/admin/notifications.py b/app/api/v1/admin/notifications.py index 780885b7..090b2205 100644 --- a/app/api/v1/admin/notifications.py +++ b/app/api/v1/admin/notifications.py @@ -9,7 +9,6 @@ Provides endpoints for: """ import logging -from typing import Optional from fastapi import APIRouter, Depends, Query from sqlalchemy.orm import Session @@ -17,12 +16,13 @@ from sqlalchemy.orm import Session from app.api.deps import get_current_admin_api from app.core.database import get_db from models.database.user import User -from models.schema.admin import (AdminNotificationCreate, - AdminNotificationListResponse, - AdminNotificationResponse, - PlatformAlertCreate, - PlatformAlertListResponse, - PlatformAlertResolve, PlatformAlertResponse) +from models.schema.admin import ( + AdminNotificationListResponse, + PlatformAlertCreate, + PlatformAlertListResponse, + PlatformAlertResolve, + PlatformAlertResponse, +) router = APIRouter(prefix="/notifications") logger = logging.getLogger(__name__) @@ -35,8 +35,8 @@ logger = logging.getLogger(__name__) @router.get("", response_model=AdminNotificationListResponse) def get_notifications( - priority: Optional[str] = Query(None, description="Filter by priority"), - is_read: Optional[bool] = Query(None, description="Filter by read status"), + priority: str | None = Query(None, description="Filter by priority"), + is_read: bool | None = Query(None, description="Filter by read status"), skip: int = Query(0, ge=0), limit: int = Query(50, ge=1, le=100), db: Session = Depends(get_db), @@ -87,8 +87,8 @@ def mark_all_as_read( @router.get("/alerts", response_model=PlatformAlertListResponse) def get_platform_alerts( - severity: Optional[str] = Query(None, description="Filter by severity"), - is_resolved: Optional[bool] = Query( + severity: str | None = Query(None, description="Filter by severity"), + is_resolved: bool | None = Query( None, description="Filter by resolution status" ), skip: int = Query(0, ge=0), diff --git a/app/api/v1/admin/settings.py b/app/api/v1/admin/settings.py index 025b2a3c..f0806e90 100644 --- a/app/api/v1/admin/settings.py +++ b/app/api/v1/admin/settings.py @@ -9,7 +9,6 @@ Provides endpoints for: """ import logging -from typing import Optional from fastapi import APIRouter, Depends, Query from sqlalchemy.orm import Session @@ -19,8 +18,12 @@ from app.core.database import get_db from app.services.admin_audit_service import admin_audit_service from app.services.admin_settings_service import admin_settings_service from models.database.user import User -from models.schema.admin import (AdminSettingCreate, AdminSettingListResponse, - AdminSettingResponse, AdminSettingUpdate) +from models.schema.admin import ( + AdminSettingCreate, + AdminSettingListResponse, + AdminSettingResponse, + AdminSettingUpdate, +) router = APIRouter(prefix="/settings") logger = logging.getLogger(__name__) @@ -28,8 +31,8 @@ logger = logging.getLogger(__name__) @router.get("", response_model=AdminSettingListResponse) def get_all_settings( - category: Optional[str] = Query(None, description="Filter by category"), - is_public: Optional[bool] = Query(None, description="Filter by public flag"), + category: str | None = Query(None, description="Filter by category"), + is_public: bool | None = Query(None, description="Filter by public flag"), db: Session = Depends(get_db), current_admin: User = Depends(get_current_admin_api), ): diff --git a/app/api/v1/admin/users.py b/app/api/v1/admin/users.py index 2cadd850..962aee6e 100644 --- a/app/api/v1/admin/users.py +++ b/app/api/v1/admin/users.py @@ -4,7 +4,6 @@ User management endpoints for admin. """ import logging -from typing import List from fastapi import APIRouter, Depends, Query from sqlalchemy.orm import Session @@ -20,7 +19,7 @@ router = APIRouter(prefix="/users") logger = logging.getLogger(__name__) -@router.get("", response_model=List[UserResponse]) +@router.get("", response_model=list[UserResponse]) def get_all_users( skip: int = Query(0, ge=0), limit: int = Query(100, ge=1, le=1000), diff --git a/app/api/v1/admin/vendor_domains.py b/app/api/v1/admin/vendor_domains.py index c33e1786..c3c00755 100644 --- a/app/api/v1/admin/vendor_domains.py +++ b/app/api/v1/admin/vendor_domains.py @@ -10,9 +10,8 @@ Follows the architecture pattern: """ import logging -from typing import List -from fastapi import APIRouter, Body, Depends, Path, Query +from fastapi import APIRouter, Body, Depends, Path from sqlalchemy.orm import Session from app.api.deps import get_current_admin_api @@ -21,13 +20,15 @@ from app.exceptions import VendorNotFoundException from app.services.vendor_domain_service import vendor_domain_service from models.database.user import User from models.database.vendor import Vendor -from models.schema.vendor_domain import (DomainDeletionResponse, - DomainVerificationInstructions, - DomainVerificationResponse, - VendorDomainCreate, - VendorDomainListResponse, - VendorDomainResponse, - VendorDomainUpdate) +from models.schema.vendor_domain import ( + DomainDeletionResponse, + DomainVerificationInstructions, + DomainVerificationResponse, + VendorDomainCreate, + VendorDomainListResponse, + VendorDomainResponse, + VendorDomainUpdate, +) router = APIRouter(prefix="/vendors") logger = logging.getLogger(__name__) diff --git a/app/api/v1/admin/vendor_themes.py b/app/api/v1/admin/vendor_themes.py index 3a0fa06e..6c45c590 100644 --- a/app/api/v1/admin/vendor_themes.py +++ b/app/api/v1/admin/vendor_themes.py @@ -20,8 +20,11 @@ from sqlalchemy.orm import Session from app.api.deps import get_current_admin_api, get_db from app.services.vendor_theme_service import vendor_theme_service from models.database.user import User -from models.schema.vendor_theme import (ThemePresetListResponse, - VendorThemeResponse, VendorThemeUpdate) +from models.schema.vendor_theme import ( + ThemePresetListResponse, + VendorThemeResponse, + VendorThemeUpdate, +) router = APIRouter(prefix="/vendor-themes") logger = logging.getLogger(__name__) diff --git a/app/api/v1/admin/vendors.py b/app/api/v1/admin/vendors.py index 58b1f615..6066906e 100644 --- a/app/api/v1/admin/vendors.py +++ b/app/api/v1/admin/vendors.py @@ -4,7 +4,7 @@ Vendor management endpoints for admin. """ import logging -from typing import Optional +from datetime import UTC from fastapi import APIRouter, Body, Depends, Path, Query from sqlalchemy import func @@ -12,18 +12,21 @@ from sqlalchemy.orm import Session from app.api.deps import get_current_admin_api from app.core.database import get_db -from app.exceptions import (ConfirmationRequiredException, - VendorNotFoundException) +from app.exceptions import ConfirmationRequiredException, VendorNotFoundException from app.services.admin_service import admin_service from app.services.stats_service import stats_service from models.database.user import User from models.database.vendor import Vendor from models.schema.stats import VendorStatsResponse -from models.schema.vendor import (VendorCreate, VendorCreateResponse, - VendorDetailResponse, VendorListResponse, - VendorResponse, VendorTransferOwnership, - VendorTransferOwnershipResponse, - VendorUpdate) +from models.schema.vendor import ( + VendorCreate, + VendorCreateResponse, + VendorDetailResponse, + VendorListResponse, + VendorTransferOwnership, + VendorTransferOwnershipResponse, + VendorUpdate, +) router = APIRouter(prefix="/vendors") logger = logging.getLogger(__name__) @@ -126,9 +129,9 @@ def create_vendor_with_owner( def get_all_vendors_admin( skip: int = Query(0, ge=0), limit: int = Query(100, ge=1, le=1000), - search: Optional[str] = Query(None, description="Search by name or vendor code"), - is_active: Optional[bool] = Query(None), - is_verified: Optional[bool] = Query(None), + search: str | None = Query(None, description="Search by name or vendor code"), + is_active: bool | None = Query(None), + is_verified: bool | None = Query(None), db: Session = Depends(get_db), current_admin: User = Depends(get_current_admin_api), ): @@ -282,7 +285,7 @@ def transfer_vendor_ownership( - `confirm_transfer`: Must be true - `transfer_reason`: Optional reason for audit trail """ - from datetime import datetime, timezone + from datetime import datetime vendor = _get_vendor_by_identifier(db, vendor_identifier) vendor, old_owner, new_owner = admin_service.transfer_vendor_ownership( @@ -304,7 +307,7 @@ def transfer_vendor_ownership( "username": new_owner.username, "email": new_owner.email, }, - transferred_at=datetime.now(timezone.utc), + transferred_at=datetime.now(UTC), transfer_reason=transfer_data.transfer_reason, ) diff --git a/app/api/v1/shop/cart.py b/app/api/v1/shop/cart.py index ac51667c..e05ba7c7 100644 --- a/app/api/v1/shop/cart.py +++ b/app/api/v1/shop/cart.py @@ -14,9 +14,13 @@ from sqlalchemy.orm import Session from app.core.database import get_db from app.services.cart_service import cart_service -from models.schema.cart import (AddToCartRequest, CartOperationResponse, - CartResponse, ClearCartResponse, - UpdateCartItemRequest) +from models.schema.cart import ( + AddToCartRequest, + CartOperationResponse, + CartResponse, + ClearCartResponse, + UpdateCartItemRequest, +) router = APIRouter() logger = logging.getLogger(__name__) diff --git a/app/api/v1/shop/content_pages.py b/app/api/v1/shop/content_pages.py index 48e19d64..8ab17249 100644 --- a/app/api/v1/shop/content_pages.py +++ b/app/api/v1/shop/content_pages.py @@ -7,7 +7,6 @@ No authentication required. """ import logging -from typing import List from fastapi import APIRouter, Depends, HTTPException, Request from pydantic import BaseModel @@ -52,7 +51,7 @@ class ContentPageListItem(BaseModel): # ============================================================================ -@router.get("/navigation", response_model=List[ContentPageListItem]) +@router.get("/navigation", response_model=list[ContentPageListItem]) def get_navigation_pages(request: Request, db: Session = Depends(get_db)): """ Get list of content pages for navigation (footer/header). diff --git a/app/api/v1/shop/orders.py b/app/api/v1/shop/orders.py index 74ac25bf..ea7dd648 100644 --- a/app/api/v1/shop/orders.py +++ b/app/api/v1/shop/orders.py @@ -8,7 +8,6 @@ Requires customer authentication for most operations. """ import logging -from typing import Optional from fastapi import APIRouter, Depends, HTTPException, Path, Query, Request from sqlalchemy.orm import Session @@ -19,8 +18,12 @@ from app.services.customer_service import customer_service from app.services.order_service import order_service from models.database.customer import Customer from models.database.user import User -from models.schema.order import (OrderCreate, OrderDetailResponse, - OrderListResponse, OrderResponse) +from models.schema.order import ( + OrderCreate, + OrderDetailResponse, + OrderListResponse, + OrderResponse, +) router = APIRouter() logger = logging.getLogger(__name__) diff --git a/app/api/v1/shop/products.py b/app/api/v1/shop/products.py index 37836bec..cf6f76e7 100644 --- a/app/api/v1/shop/products.py +++ b/app/api/v1/shop/products.py @@ -8,15 +8,17 @@ No authentication required. """ import logging -from typing import Optional from fastapi import APIRouter, Depends, HTTPException, Path, Query, Request from sqlalchemy.orm import Session from app.core.database import get_db from app.services.product_service import product_service -from models.schema.product import (ProductDetailResponse, ProductListResponse, - ProductResponse) +from models.schema.product import ( + ProductDetailResponse, + ProductListResponse, + ProductResponse, +) router = APIRouter() logger = logging.getLogger(__name__) @@ -27,8 +29,8 @@ def get_product_catalog( request: Request, skip: int = Query(0, ge=0), limit: int = Query(100, ge=1, le=1000), - search: Optional[str] = Query(None, description="Search products by name"), - is_featured: Optional[bool] = Query( + search: str | None = Query(None, description="Search products by name"), + is_featured: bool | None = Query( None, description="Filter by featured products" ), db: Session = Depends(get_db), diff --git a/app/api/v1/vendor/__init__.py b/app/api/v1/vendor/__init__.py index b984af4f..90fefb44 100644 --- a/app/api/v1/vendor/__init__.py +++ b/app/api/v1/vendor/__init__.py @@ -13,9 +13,24 @@ IMPORTANT: from fastapi import APIRouter # Import all sub-routers (JSON API only) -from . import (analytics, auth, content_pages, customers, dashboard, info, - inventory, marketplace, media, notifications, orders, payments, - products, profile, settings, team) +from . import ( + analytics, + auth, + content_pages, + customers, + dashboard, + info, + inventory, + marketplace, + media, + notifications, + orders, + payments, + products, + profile, + settings, + team, +) # Create vendor router router = APIRouter() diff --git a/app/api/v1/vendor/content_pages.py b/app/api/v1/vendor/content_pages.py index 4a552898..4f50d8c0 100644 --- a/app/api/v1/vendor/content_pages.py +++ b/app/api/v1/vendor/content_pages.py @@ -9,7 +9,6 @@ Vendors can: """ import logging -from typing import List, Optional from fastapi import APIRouter, Depends, HTTPException, Query from pydantic import BaseModel, Field @@ -41,10 +40,10 @@ class VendorContentPageCreate(BaseModel): content_format: str = Field( default="html", description="Content format: html or markdown" ) - meta_description: Optional[str] = Field( + meta_description: str | None = Field( None, max_length=300, description="SEO meta description" ) - meta_keywords: Optional[str] = Field( + meta_keywords: str | None = Field( None, max_length=300, description="SEO keywords" ) is_published: bool = Field(default=False, description="Publish immediately") @@ -56,31 +55,31 @@ class VendorContentPageCreate(BaseModel): class VendorContentPageUpdate(BaseModel): """Schema for updating a vendor content page.""" - title: Optional[str] = Field(None, max_length=200) - content: Optional[str] = None - content_format: Optional[str] = None - meta_description: Optional[str] = Field(None, max_length=300) - meta_keywords: Optional[str] = Field(None, max_length=300) - is_published: Optional[bool] = None - show_in_footer: Optional[bool] = None - show_in_header: Optional[bool] = None - display_order: Optional[int] = None + title: str | None = Field(None, max_length=200) + content: str | None = None + content_format: str | None = None + meta_description: str | None = Field(None, max_length=300) + meta_keywords: str | None = Field(None, max_length=300) + is_published: bool | None = None + show_in_footer: bool | None = None + show_in_header: bool | None = None + display_order: int | None = None class ContentPageResponse(BaseModel): """Schema for content page response.""" id: int - vendor_id: Optional[int] - vendor_name: Optional[str] + vendor_id: int | None + vendor_name: str | None slug: str title: str content: str content_format: str - meta_description: Optional[str] - meta_keywords: Optional[str] + meta_description: str | None + meta_keywords: str | None is_published: bool - published_at: Optional[str] + published_at: str | None display_order: int show_in_footer: bool show_in_header: bool @@ -88,8 +87,8 @@ class ContentPageResponse(BaseModel): is_vendor_override: bool created_at: str updated_at: str - created_by: Optional[int] - updated_by: Optional[int] + created_by: int | None + updated_by: int | None # ============================================================================ @@ -97,7 +96,7 @@ class ContentPageResponse(BaseModel): # ============================================================================ -@router.get("/", response_model=List[ContentPageResponse]) +@router.get("/", response_model=list[ContentPageResponse]) def list_vendor_pages( include_unpublished: bool = Query(False, description="Include draft pages"), current_user: User = Depends(get_current_vendor_api), @@ -120,7 +119,7 @@ def list_vendor_pages( return [page.to_dict() for page in pages] -@router.get("/overrides", response_model=List[ContentPageResponse]) +@router.get("/overrides", response_model=list[ContentPageResponse]) def list_vendor_overrides( include_unpublished: bool = Query(False, description="Include draft pages"), current_user: User = Depends(get_current_vendor_api), @@ -284,4 +283,4 @@ def delete_vendor_page( # Delete content_page_service.delete_page(db, page_id) - return None + return diff --git a/app/api/v1/vendor/customers.py b/app/api/v1/vendor/customers.py index 3a267c17..823bb277 100644 --- a/app/api/v1/vendor/customers.py +++ b/app/api/v1/vendor/customers.py @@ -5,7 +5,6 @@ Vendor customer management endpoints. """ import logging -from typing import Optional from fastapi import APIRouter, Depends, Query from sqlalchemy.orm import Session @@ -24,8 +23,8 @@ logger = logging.getLogger(__name__) def get_vendor_customers( skip: int = Query(0, ge=0), limit: int = Query(100, ge=1, le=1000), - search: Optional[str] = Query(None), - is_active: Optional[bool] = Query(None), + search: str | None = Query(None), + is_active: bool | None = Query(None), vendor: Vendor = Depends(require_vendor_context()), current_user: User = Depends(get_current_vendor_api), db: Session = Depends(get_db), diff --git a/app/api/v1/vendor/dashboard.py b/app/api/v1/vendor/dashboard.py index b49351a6..98b9586e 100644 --- a/app/api/v1/vendor/dashboard.py +++ b/app/api/v1/vendor/dashboard.py @@ -11,9 +11,7 @@ from sqlalchemy.orm import Session from app.api.deps import get_current_vendor_api from app.core.database import get_db from app.services.stats_service import stats_service -from middleware.vendor_context import require_vendor_context from models.database.user import User -from models.database.vendor import Vendor router = APIRouter(prefix="/dashboard") logger = logging.getLogger(__name__) diff --git a/app/api/v1/vendor/info.py b/app/api/v1/vendor/info.py index e84d0a8d..53c46476 100644 --- a/app/api/v1/vendor/info.py +++ b/app/api/v1/vendor/info.py @@ -16,7 +16,7 @@ from sqlalchemy.orm import Session from app.core.database import get_db from app.exceptions import VendorNotFoundException from models.database.vendor import Vendor -from models.schema.vendor import VendorDetailResponse, VendorResponse +from models.schema.vendor import VendorDetailResponse router = APIRouter() logger = logging.getLogger(__name__) diff --git a/app/api/v1/vendor/inventory.py b/app/api/v1/vendor/inventory.py index 0cb49c2c..9e6a13f0 100644 --- a/app/api/v1/vendor/inventory.py +++ b/app/api/v1/vendor/inventory.py @@ -1,6 +1,5 @@ # app/api/v1/vendor/inventory.py import logging -from typing import List, Optional from fastapi import APIRouter, Depends, Query from sqlalchemy.orm import Session @@ -11,10 +10,15 @@ from app.services.inventory_service import inventory_service from middleware.vendor_context import require_vendor_context from models.database.user import User from models.database.vendor import Vendor -from models.schema.inventory import (InventoryAdjust, InventoryCreate, - InventoryListResponse, InventoryReserve, - InventoryResponse, InventoryUpdate, - ProductInventorySummary) +from models.schema.inventory import ( + InventoryAdjust, + InventoryCreate, + InventoryListResponse, + InventoryReserve, + InventoryResponse, + InventoryUpdate, + ProductInventorySummary, +) router = APIRouter() logger = logging.getLogger(__name__) @@ -90,8 +94,8 @@ def get_product_inventory( def get_vendor_inventory( skip: int = Query(0, ge=0), limit: int = Query(100, ge=1, le=1000), - location: Optional[str] = Query(None), - low_stock: Optional[int] = Query(None, ge=0), + location: str | None = Query(None), + low_stock: int | None = Query(None, ge=0), vendor: Vendor = Depends(require_vendor_context()), current_user: User = Depends(get_current_vendor_api), db: Session = Depends(get_db), diff --git a/app/api/v1/vendor/marketplace.py b/app/api/v1/vendor/marketplace.py index 9084bfd2..12001170 100644 --- a/app/api/v1/vendor/marketplace.py +++ b/app/api/v1/vendor/marketplace.py @@ -5,22 +5,22 @@ Vendor context is automatically injected by middleware. """ import logging -from typing import List, Optional from fastapi import APIRouter, BackgroundTasks, Depends, Query from sqlalchemy.orm import Session from app.api.deps import get_current_vendor_api from app.core.database import get_db -from app.services.marketplace_import_job_service import \ - marketplace_import_job_service +from app.services.marketplace_import_job_service import marketplace_import_job_service from app.tasks.background_tasks import process_marketplace_import from middleware.decorators import rate_limit from middleware.vendor_context import require_vendor_context # IMPORTANT from models.database.user import User from models.database.vendor import Vendor -from models.schema.marketplace_import_job import (MarketplaceImportJobRequest, - MarketplaceImportJobResponse) +from models.schema.marketplace_import_job import ( + MarketplaceImportJobRequest, + MarketplaceImportJobResponse, +) router = APIRouter() logger = logging.getLogger(__name__) @@ -93,9 +93,9 @@ def get_marketplace_import_status( return marketplace_import_job_service.convert_to_response_model(job) -@router.get("/imports", response_model=List[MarketplaceImportJobResponse]) +@router.get("/imports", response_model=list[MarketplaceImportJobResponse]) def get_marketplace_import_jobs( - marketplace: Optional[str] = Query(None, description="Filter by marketplace"), + marketplace: str | None = Query(None, description="Filter by marketplace"), skip: int = Query(0, ge=0), limit: int = Query(50, ge=1, le=100), vendor: Vendor = Depends(require_vendor_context()), diff --git a/app/api/v1/vendor/media.py b/app/api/v1/vendor/media.py index 900bbb6e..dc5aaf6d 100644 --- a/app/api/v1/vendor/media.py +++ b/app/api/v1/vendor/media.py @@ -5,7 +5,6 @@ Vendor media and file management endpoints. """ import logging -from typing import Optional from fastapi import APIRouter, Depends, File, Query, UploadFile from sqlalchemy.orm import Session @@ -24,8 +23,8 @@ logger = logging.getLogger(__name__) def get_media_library( skip: int = Query(0, ge=0), limit: int = Query(100, ge=1, le=1000), - media_type: Optional[str] = Query(None, description="image, video, document"), - search: Optional[str] = Query(None), + media_type: str | None = Query(None, description="image, video, document"), + search: str | None = Query(None), vendor: Vendor = Depends(require_vendor_context()), current_user: User = Depends(get_current_vendor_api), db: Session = Depends(get_db), @@ -52,7 +51,7 @@ def get_media_library( @router.post("/upload") async def upload_media( file: UploadFile = File(...), - folder: Optional[str] = Query(None, description="products, general, etc."), + folder: str | None = Query(None, description="products, general, etc."), vendor: Vendor = Depends(require_vendor_context()), current_user: User = Depends(get_current_vendor_api), db: Session = Depends(get_db), @@ -78,7 +77,7 @@ async def upload_media( @router.post("/upload/multiple") async def upload_multiple_media( files: list[UploadFile] = File(...), - folder: Optional[str] = Query(None), + folder: str | None = Query(None), vendor: Vendor = Depends(require_vendor_context()), current_user: User = Depends(get_current_vendor_api), db: Session = Depends(get_db), diff --git a/app/api/v1/vendor/notifications.py b/app/api/v1/vendor/notifications.py index 2b6a8205..26610a74 100644 --- a/app/api/v1/vendor/notifications.py +++ b/app/api/v1/vendor/notifications.py @@ -5,7 +5,6 @@ Vendor notification management endpoints. """ import logging -from typing import Optional from fastapi import APIRouter, Depends, Query from sqlalchemy.orm import Session @@ -24,7 +23,7 @@ logger = logging.getLogger(__name__) def get_notifications( skip: int = Query(0, ge=0), limit: int = Query(50, ge=1, le=100), - unread_only: Optional[bool] = Query(False), + unread_only: bool | None = Query(False), vendor: Vendor = Depends(require_vendor_context()), current_user: User = Depends(get_current_vendor_api), db: Session = Depends(get_db), diff --git a/app/api/v1/vendor/orders.py b/app/api/v1/vendor/orders.py index a32b4253..74d91f83 100644 --- a/app/api/v1/vendor/orders.py +++ b/app/api/v1/vendor/orders.py @@ -4,9 +4,8 @@ Vendor order management endpoints. """ import logging -from typing import Optional -from fastapi import APIRouter, Depends, HTTPException, Query, Request +from fastapi import APIRouter, Depends, Query from sqlalchemy.orm import Session from app.api.deps import get_current_vendor_api @@ -14,9 +13,13 @@ from app.core.database import get_db from app.services.order_service import order_service from middleware.vendor_context import require_vendor_context from models.database.user import User -from models.database.vendor import Vendor, VendorUser -from models.schema.order import (OrderDetailResponse, OrderListResponse, - OrderResponse, OrderUpdate) +from models.database.vendor import Vendor +from models.schema.order import ( + OrderDetailResponse, + OrderListResponse, + OrderResponse, + OrderUpdate, +) router = APIRouter(prefix="/orders") logger = logging.getLogger(__name__) @@ -26,8 +29,8 @@ logger = logging.getLogger(__name__) def get_vendor_orders( skip: int = Query(0, ge=0), limit: int = Query(100, ge=1, le=1000), - status: Optional[str] = Query(None, description="Filter by order status"), - customer_id: Optional[int] = Query(None, description="Filter by customer"), + status: str | None = Query(None, description="Filter by order status"), + customer_id: int | None = Query(None, description="Filter by customer"), vendor: Vendor = Depends(require_vendor_context()), current_user: User = Depends(get_current_vendor_api), db: Session = Depends(get_db), diff --git a/app/api/v1/vendor/products.py b/app/api/v1/vendor/products.py index f2263ef3..ed04f2fd 100644 --- a/app/api/v1/vendor/products.py +++ b/app/api/v1/vendor/products.py @@ -4,7 +4,6 @@ Vendor product catalog management endpoints. """ import logging -from typing import Optional from fastapi import APIRouter, Depends, Query from sqlalchemy.orm import Session @@ -15,9 +14,13 @@ from app.services.product_service import product_service from middleware.vendor_context import require_vendor_context from models.database.user import User from models.database.vendor import Vendor -from models.schema.product import (ProductCreate, ProductDetailResponse, - ProductListResponse, ProductResponse, - ProductUpdate) +from models.schema.product import ( + ProductCreate, + ProductDetailResponse, + ProductListResponse, + ProductResponse, + ProductUpdate, +) router = APIRouter(prefix="/products") logger = logging.getLogger(__name__) @@ -27,8 +30,8 @@ logger = logging.getLogger(__name__) def get_vendor_products( skip: int = Query(0, ge=0), limit: int = Query(100, ge=1, le=1000), - is_active: Optional[bool] = Query(None), - is_featured: Optional[bool] = Query(None), + is_active: bool | None = Query(None), + is_featured: bool | None = Query(None), vendor: Vendor = Depends(require_vendor_context()), current_user: User = Depends(get_current_vendor_api), db: Session = Depends(get_db), diff --git a/app/api/v1/vendor/team.py b/app/api/v1/vendor/team.py index fe84c858..73d0f127 100644 --- a/app/api/v1/vendor/team.py +++ b/app/api/v1/vendor/team.py @@ -11,25 +11,34 @@ Implements complete team management with: """ import logging -from typing import List from fastapi import APIRouter, Depends, Request from sqlalchemy.orm import Session -from app.api.deps import (get_current_vendor_api, get_user_permissions, - require_vendor_owner, require_vendor_permission) +from app.api.deps import ( + get_current_vendor_api, + get_user_permissions, + require_vendor_owner, + require_vendor_permission, +) from app.core.database import get_db from app.core.permissions import VendorPermissions from app.services.vendor_team_service import vendor_team_service from models.database.user import User -from models.database.vendor import Vendor -from models.schema.team import (BulkRemoveRequest, BulkRemoveResponse, - InvitationAccept, InvitationAcceptResponse, - InvitationResponse, RoleListResponse, - RoleResponse, TeamMemberInvite, - TeamMemberListResponse, TeamMemberResponse, - TeamMemberUpdate, TeamStatistics, - UserPermissionsResponse) +from models.schema.team import ( + BulkRemoveRequest, + BulkRemoveResponse, + InvitationAccept, + InvitationAcceptResponse, + InvitationResponse, + RoleListResponse, + TeamMemberInvite, + TeamMemberListResponse, + TeamMemberResponse, + TeamMemberUpdate, + TeamStatistics, + UserPermissionsResponse, +) router = APIRouter(prefix="/team") logger = logging.getLogger(__name__) @@ -382,7 +391,7 @@ def list_roles( @router.get("/me/permissions", response_model=UserPermissionsResponse) def get_my_permissions( request: Request, - permissions: List[str] = Depends(get_user_permissions), + permissions: list[str] = Depends(get_user_permissions), current_user: User = Depends(get_current_vendor_api), ): """ diff --git a/app/core/config.py b/app/core/config.py index ed0b2641..915bee47 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -13,7 +13,6 @@ Note: Environment detection is handled by app.core.environment module. This module focuses purely on configuration storage and validation. """ -from typing import List, Optional from pydantic_settings import BaseSettings @@ -82,7 +81,7 @@ class Settings(BaseSettings): # ============================================================================= # MIDDLEWARE & SECURITY # ============================================================================= - allowed_hosts: List[str] = ["*"] # Configure for production + allowed_hosts: list[str] = ["*"] # Configure for production # Rate Limiting rate_limit_enabled: bool = True @@ -93,7 +92,7 @@ class Settings(BaseSettings): # LOGGING # ============================================================================= log_level: str = "INFO" - log_file: Optional[str] = None + log_file: str | None = None # ============================================================================= # PLATFORM DOMAIN CONFIGURATION @@ -138,9 +137,13 @@ settings = Settings() # ENVIRONMENT UTILITIES - Module-level functions # ============================================================================= # Import environment detection utilities -from app.core.environment import (get_environment, is_development, - is_production, is_staging, - should_use_secure_cookies) +from app.core.environment import ( + get_environment, + is_development, + is_production, + is_staging, + should_use_secure_cookies, +) def get_current_environment() -> str: @@ -188,7 +191,7 @@ def is_staging_environment() -> bool: # ============================================================================= -def validate_production_settings() -> List[str]: +def validate_production_settings() -> list[str]: """ Validate settings for production environment. diff --git a/app/core/environment.py b/app/core/environment.py index 0a430311..1c80991c 100644 --- a/app/core/environment.py +++ b/app/core/environment.py @@ -31,18 +31,18 @@ def get_environment() -> EnvironmentType: env = os.getenv("ENV", "").lower() if env in ["development", "dev", "local"]: return "development" - elif env in ["staging", "stage"]: + if env in ["staging", "stage"]: return "staging" - elif env in ["production", "prod"]: + if env in ["production", "prod"]: return "production" # Priority 2: ENVIRONMENT variable env = os.getenv("ENVIRONMENT", "").lower() if env in ["development", "dev", "local"]: return "development" - elif env in ["staging", "stage"]: + if env in ["staging", "stage"]: return "staging" - elif env in ["production", "prod"]: + if env in ["production", "prod"]: return "production" # Priority 3: Auto-detect from common indicators diff --git a/app/core/lifespan.py b/app/core/lifespan.py index f26a8363..a4e9dddb 100644 --- a/app/core/lifespan.py +++ b/app/core/lifespan.py @@ -16,7 +16,7 @@ from sqlalchemy import text from middleware.auth import AuthManager -from .database import SessionLocal, engine +from .database import engine from .logging import setup_logging # Remove this import if not needed: from models.database.base import Base @@ -60,7 +60,6 @@ def check_database_ready(): def get_migration_status(): """Get current Alembic migration status.""" try: - from alembic import command from alembic.config import Config alembic_cfg = Config("alembic.ini") diff --git a/app/core/permissions.py b/app/core/permissions.py index 2a04968c..be02f0ef 100644 --- a/app/core/permissions.py +++ b/app/core/permissions.py @@ -7,8 +7,8 @@ This module defines: - Permission groups (for easier role creation) - Permission checking utilities """ + from enum import Enum -from typing import List, Set class VendorPermissions(str, Enum): @@ -78,10 +78,10 @@ class PermissionGroups: """Pre-defined permission groups for common roles.""" # Full access (for owners) - OWNER: Set[str] = set(p.value for p in VendorPermissions) + OWNER: set[str] = set(p.value for p in VendorPermissions) # Manager - Can do most things except team management and critical settings - MANAGER: Set[str] = { + MANAGER: set[str] = { VendorPermissions.DASHBOARD_VIEW.value, VendorPermissions.PRODUCTS_VIEW.value, VendorPermissions.PRODUCTS_CREATE.value, @@ -113,7 +113,7 @@ class PermissionGroups: } # Staff - Can view and edit products/orders but limited access - STAFF: Set[str] = { + STAFF: set[str] = { VendorPermissions.DASHBOARD_VIEW.value, VendorPermissions.PRODUCTS_VIEW.value, VendorPermissions.PRODUCTS_CREATE.value, @@ -127,7 +127,7 @@ class PermissionGroups: } # Support - Can view and assist with orders/customers - SUPPORT: Set[str] = { + SUPPORT: set[str] = { VendorPermissions.DASHBOARD_VIEW.value, VendorPermissions.PRODUCTS_VIEW.value, VendorPermissions.ORDERS_VIEW.value, @@ -137,7 +137,7 @@ class PermissionGroups: } # Viewer - Read-only access - VIEWER: Set[str] = { + VIEWER: set[str] = { VendorPermissions.DASHBOARD_VIEW.value, VendorPermissions.PRODUCTS_VIEW.value, VendorPermissions.STOCK_VIEW.value, @@ -147,7 +147,7 @@ class PermissionGroups: } # Marketing - Focused on marketing and customer communication - MARKETING: Set[str] = { + MARKETING: set[str] = { VendorPermissions.DASHBOARD_VIEW.value, VendorPermissions.CUSTOMERS_VIEW.value, VendorPermissions.CUSTOMERS_EXPORT.value, @@ -162,34 +162,34 @@ class PermissionChecker: """Utility class for permission checking.""" @staticmethod - def has_permission(permissions: List[str], required_permission: str) -> bool: + def has_permission(permissions: list[str], required_permission: str) -> bool: """Check if a permission list contains a required permission.""" return required_permission in permissions @staticmethod def has_any_permission( - permissions: List[str], required_permissions: List[str] + permissions: list[str], required_permissions: list[str] ) -> bool: """Check if a permission list contains ANY of the required permissions.""" return any(perm in permissions for perm in required_permissions) @staticmethod def has_all_permissions( - permissions: List[str], required_permissions: List[str] + permissions: list[str], required_permissions: list[str] ) -> bool: """Check if a permission list contains ALL of the required permissions.""" return all(perm in permissions for perm in required_permissions) @staticmethod def get_missing_permissions( - permissions: List[str], required_permissions: List[str] - ) -> List[str]: + permissions: list[str], required_permissions: list[str] + ) -> list[str]: """Get list of missing permissions.""" return [perm for perm in required_permissions if perm not in permissions] # Helper function to get permissions for a role preset -def get_preset_permissions(preset_name: str) -> Set[str]: +def get_preset_permissions(preset_name: str) -> set[str]: """ Get permissions for a preset role. diff --git a/app/exceptions/__init__.py b/app/exceptions/__init__.py index fa7f17f1..8dc654ff 100644 --- a/app/exceptions/__init__.py +++ b/app/exceptions/__init__.py @@ -7,108 +7,178 @@ messages, and HTTP status mappings. """ # Admin exceptions -from .admin import (AdminOperationException, BulkOperationException, - CannotModifyAdminException, CannotModifySelfException, - ConfirmationRequiredException, InvalidAdminActionException, - UserNotFoundException, UserStatusChangeException, - VendorVerificationException) +from .admin import ( + AdminOperationException, + BulkOperationException, + CannotModifyAdminException, + CannotModifySelfException, + ConfirmationRequiredException, + InvalidAdminActionException, + UserNotFoundException, + UserStatusChangeException, + VendorVerificationException, +) + # Authentication exceptions -from .auth import (AdminRequiredException, InsufficientPermissionsException, - InvalidCredentialsException, InvalidTokenException, - TokenExpiredException, UserAlreadyExistsException, - UserNotActiveException) +from .auth import ( + AdminRequiredException, + InsufficientPermissionsException, + InvalidCredentialsException, + InvalidTokenException, + TokenExpiredException, + UserAlreadyExistsException, + UserNotActiveException, +) + # Base exceptions -from .base import (AuthenticationException, AuthorizationException, - BusinessLogicException, ConflictException, - ExternalServiceException, RateLimitException, - ResourceNotFoundException, ServiceUnavailableException, - ValidationException, WizamartException) +from .base import ( + AuthenticationException, + AuthorizationException, + BusinessLogicException, + ConflictException, + ExternalServiceException, + RateLimitException, + ResourceNotFoundException, + ServiceUnavailableException, + ValidationException, + WizamartException, +) + # Cart exceptions -from .cart import (CartItemNotFoundException, CartValidationException, - EmptyCartException, InsufficientInventoryForCartException, - InvalidCartQuantityException, - ProductNotAvailableForCartException) +from .cart import ( + CartItemNotFoundException, + CartValidationException, + EmptyCartException, + InsufficientInventoryForCartException, + InvalidCartQuantityException, + ProductNotAvailableForCartException, +) + # Customer exceptions -from .customer import (CustomerAlreadyExistsException, - CustomerAuthorizationException, - CustomerNotActiveException, CustomerNotFoundException, - CustomerValidationException, - DuplicateCustomerEmailException, - InvalidCustomerCredentialsException) +from .customer import ( + CustomerAlreadyExistsException, + CustomerAuthorizationException, + CustomerNotActiveException, + CustomerNotFoundException, + CustomerValidationException, + DuplicateCustomerEmailException, + InvalidCustomerCredentialsException, +) + # Inventory exceptions -from .inventory import (InsufficientInventoryException, - InvalidInventoryOperationException, - InvalidQuantityException, InventoryNotFoundException, - InventoryValidationException, - LocationNotFoundException, NegativeInventoryException) +from .inventory import ( + InsufficientInventoryException, + InvalidInventoryOperationException, + InvalidQuantityException, + InventoryNotFoundException, + InventoryValidationException, + LocationNotFoundException, + NegativeInventoryException, +) + # Marketplace import job exceptions -from .marketplace_import_job import (ImportJobAlreadyProcessingException, - ImportJobCannotBeCancelledException, - ImportJobCannotBeDeletedException, - ImportJobNotFoundException, - ImportJobNotOwnedException, - ImportRateLimitException, - InvalidImportDataException, - InvalidMarketplaceException, - MarketplaceConnectionException, - MarketplaceDataParsingException, - MarketplaceImportException) +from .marketplace_import_job import ( + ImportJobAlreadyProcessingException, + ImportJobCannotBeCancelledException, + ImportJobCannotBeDeletedException, + ImportJobNotFoundException, + ImportJobNotOwnedException, + ImportRateLimitException, + InvalidImportDataException, + InvalidMarketplaceException, + MarketplaceConnectionException, + MarketplaceDataParsingException, + MarketplaceImportException, +) + # Marketplace product exceptions -from .marketplace_product import (InvalidGTINException, - InvalidMarketplaceProductDataException, - MarketplaceProductAlreadyExistsException, - MarketplaceProductCSVImportException, - MarketplaceProductNotFoundException, - MarketplaceProductValidationException) +from .marketplace_product import ( + InvalidGTINException, + InvalidMarketplaceProductDataException, + MarketplaceProductAlreadyExistsException, + MarketplaceProductCSVImportException, + MarketplaceProductNotFoundException, + MarketplaceProductValidationException, +) + # Order exceptions -from .order import (InvalidOrderStatusException, OrderAlreadyExistsException, - OrderCannotBeCancelledException, OrderNotFoundException, - OrderValidationException) +from .order import ( + InvalidOrderStatusException, + OrderAlreadyExistsException, + OrderCannotBeCancelledException, + OrderNotFoundException, + OrderValidationException, +) + # Product exceptions -from .product import (CannotDeleteProductWithInventoryException, - CannotDeleteProductWithOrdersException, - InvalidProductDataException, - ProductAlreadyExistsException, ProductNotActiveException, - ProductNotFoundException, ProductNotInCatalogException, - ProductValidationException) +from .product import ( + CannotDeleteProductWithInventoryException, + CannotDeleteProductWithOrdersException, + InvalidProductDataException, + ProductAlreadyExistsException, + ProductNotActiveException, + ProductNotFoundException, + ProductNotInCatalogException, + ProductValidationException, +) + # Team exceptions -from .team import (CannotModifyOwnRoleException, CannotRemoveOwnerException, - InsufficientTeamPermissionsException, - InvalidInvitationDataException, - InvalidInvitationTokenException, InvalidRoleException, - MaxTeamMembersReachedException, RoleNotFoundException, - TeamInvitationAlreadyAcceptedException, - TeamInvitationExpiredException, - TeamInvitationNotFoundException, - TeamMemberAlreadyExistsException, - TeamMemberNotFoundException, TeamValidationException, - UnauthorizedTeamActionException) +from .team import ( + CannotModifyOwnRoleException, + CannotRemoveOwnerException, + InsufficientTeamPermissionsException, + InvalidInvitationDataException, + InvalidInvitationTokenException, + InvalidRoleException, + MaxTeamMembersReachedException, + RoleNotFoundException, + TeamInvitationAlreadyAcceptedException, + TeamInvitationExpiredException, + TeamInvitationNotFoundException, + TeamMemberAlreadyExistsException, + TeamMemberNotFoundException, + TeamValidationException, + UnauthorizedTeamActionException, +) + # Vendor exceptions -from .vendor import (InvalidVendorDataException, MaxVendorsReachedException, - UnauthorizedVendorAccessException, - VendorAlreadyExistsException, VendorNotActiveException, - VendorNotFoundException, VendorNotVerifiedException, - VendorValidationException) +from .vendor import ( + InvalidVendorDataException, + MaxVendorsReachedException, + UnauthorizedVendorAccessException, + VendorAlreadyExistsException, + VendorNotActiveException, + VendorNotFoundException, + VendorNotVerifiedException, + VendorValidationException, +) + # Vendor domain exceptions -from .vendor_domain import (DNSVerificationException, - DomainAlreadyVerifiedException, - DomainNotVerifiedException, - DomainVerificationFailedException, - InvalidDomainFormatException, - MaxDomainsReachedException, - MultiplePrimaryDomainsException, - ReservedDomainException, - UnauthorizedDomainAccessException, - VendorDomainAlreadyExistsException, - VendorDomainNotFoundException) +from .vendor_domain import ( + DNSVerificationException, + DomainAlreadyVerifiedException, + DomainNotVerifiedException, + DomainVerificationFailedException, + InvalidDomainFormatException, + MaxDomainsReachedException, + MultiplePrimaryDomainsException, + ReservedDomainException, + UnauthorizedDomainAccessException, + VendorDomainAlreadyExistsException, + VendorDomainNotFoundException, +) + # Vendor theme exceptions -from .vendor_theme import (InvalidColorFormatException, - InvalidFontFamilyException, - InvalidThemeDataException, ThemeOperationException, - ThemePresetAlreadyAppliedException, - ThemePresetNotFoundException, - ThemeValidationException, - VendorThemeNotFoundException) +from .vendor_theme import ( + InvalidColorFormatException, + InvalidFontFamilyException, + InvalidThemeDataException, + ThemeOperationException, + ThemePresetAlreadyAppliedException, + ThemePresetNotFoundException, + ThemeValidationException, + VendorThemeNotFoundException, +) __all__ = [ # Base exceptions diff --git a/app/exceptions/admin.py b/app/exceptions/admin.py index c98abdb0..c385f925 100644 --- a/app/exceptions/admin.py +++ b/app/exceptions/admin.py @@ -3,10 +3,14 @@ Admin operations specific exceptions. """ -from typing import Any, Dict, Optional +from typing import Any -from .base import (AuthorizationException, BusinessLogicException, - ResourceNotFoundException, ValidationException) +from .base import ( + AuthorizationException, + BusinessLogicException, + ResourceNotFoundException, + ValidationException, +) class UserNotFoundException(ResourceNotFoundException): @@ -36,7 +40,7 @@ class UserStatusChangeException(BusinessLogicException): user_id: int, current_status: str, attempted_action: str, - reason: Optional[str] = None, + reason: str | None = None, ): message = f"Cannot {attempted_action} user {user_id} (current status: {current_status})" if reason: @@ -61,7 +65,7 @@ class ShopVerificationException(BusinessLogicException): self, shop_id: int, reason: str, - current_verification_status: Optional[bool] = None, + current_verification_status: bool | None = None, ): details = { "shop_id": shop_id, @@ -85,8 +89,8 @@ class AdminOperationException(BusinessLogicException): self, operation: str, reason: str, - target_type: Optional[str] = None, - target_id: Optional[str] = None, + target_type: str | None = None, + target_id: str | None = None, ): message = f"Admin operation '{operation}' failed: {reason}" @@ -142,7 +146,7 @@ class InvalidAdminActionException(ValidationException): self, action: str, reason: str, - valid_actions: Optional[list] = None, + valid_actions: list | None = None, ): details = { "action": action, @@ -167,7 +171,7 @@ class BulkOperationException(BusinessLogicException): operation: str, total_items: int, failed_items: int, - errors: Optional[Dict[str, Any]] = None, + errors: dict[str, Any] | None = None, ): message = f"Bulk {operation} completed with errors: {failed_items}/{total_items} failed" @@ -194,7 +198,7 @@ class ConfirmationRequiredException(BusinessLogicException): def __init__( self, operation: str, - message: Optional[str] = None, + message: str | None = None, confirmation_param: str = "confirm", ): if not message: @@ -217,7 +221,7 @@ class VendorVerificationException(BusinessLogicException): self, vendor_id: int, reason: str, - current_verification_status: Optional[bool] = None, + current_verification_status: bool | None = None, ): details = { "vendor_id": vendor_id, diff --git a/app/exceptions/auth.py b/app/exceptions/auth.py index 48c96c15..8af58f40 100644 --- a/app/exceptions/auth.py +++ b/app/exceptions/auth.py @@ -3,10 +3,8 @@ Authentication and authorization specific exceptions. """ -from typing import Optional -from .base import (AuthenticationException, AuthorizationException, - ConflictException) +from .base import AuthenticationException, AuthorizationException, ConflictException class InvalidCredentialsException(AuthenticationException): @@ -45,7 +43,7 @@ class InsufficientPermissionsException(AuthorizationException): def __init__( self, message: str = "Insufficient permissions for this action", - required_permission: Optional[str] = None, + required_permission: str | None = None, ): details = {} if required_permission: @@ -84,7 +82,7 @@ class UserAlreadyExistsException(ConflictException): def __init__( self, message: str = "User already exists", - field: Optional[str] = None, + field: str | None = None, ): details = {} if field: diff --git a/app/exceptions/base.py b/app/exceptions/base.py index 5cfe05a3..b8eeb371 100644 --- a/app/exceptions/base.py +++ b/app/exceptions/base.py @@ -8,7 +8,7 @@ This module provides classes and functions for: - Standardized error response structure """ -from typing import Any, Dict, Optional +from typing import Any class WizamartException(Exception): @@ -19,7 +19,7 @@ class WizamartException(Exception): message: str, error_code: str, status_code: int = 400, - details: Optional[Dict[str, Any]] = None, + details: dict[str, Any] | None = None, ): self.message = message self.error_code = error_code @@ -27,7 +27,7 @@ class WizamartException(Exception): self.details = details or {} super().__init__(self.message) - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: """Convert exception to dictionary for JSON response.""" result = { "error_code": self.error_code, @@ -45,8 +45,8 @@ class ValidationException(WizamartException): def __init__( self, message: str, - field: Optional[str] = None, - details: Optional[Dict[str, Any]] = None, + field: str | None = None, + details: dict[str, Any] | None = None, ): validation_details = details or {} if field: @@ -67,7 +67,7 @@ class AuthenticationException(WizamartException): self, message: str = "Authentication failed", error_code: str = "AUTHENTICATION_FAILED", - details: Optional[Dict[str, Any]] = None, + details: dict[str, Any] | None = None, ): super().__init__( message=message, @@ -84,7 +84,7 @@ class AuthorizationException(WizamartException): self, message: str = "Access denied", error_code: str = "AUTHORIZATION_FAILED", - details: Optional[Dict[str, Any]] = None, + details: dict[str, Any] | None = None, ): super().__init__( message=message, @@ -101,8 +101,8 @@ class ResourceNotFoundException(WizamartException): self, resource_type: str, identifier: str, - message: Optional[str] = None, - error_code: Optional[str] = None, + message: str | None = None, + error_code: str | None = None, ): if not message: message = f"{resource_type} with identifier '{identifier}' not found" @@ -127,7 +127,7 @@ class ConflictException(WizamartException): self, message: str, error_code: str = "RESOURCE_CONFLICT", - details: Optional[Dict[str, Any]] = None, + details: dict[str, Any] | None = None, ): super().__init__( message=message, @@ -144,7 +144,7 @@ class BusinessLogicException(WizamartException): self, message: str, error_code: str, - details: Optional[Dict[str, Any]] = None, + details: dict[str, Any] | None = None, ): super().__init__( message=message, @@ -162,7 +162,7 @@ class ExternalServiceException(WizamartException): message: str, service_name: str, error_code: str = "EXTERNAL_SERVICE_ERROR", - details: Optional[Dict[str, Any]] = None, + details: dict[str, Any] | None = None, ): service_details = details or {} service_details["service_name"] = service_name @@ -181,8 +181,8 @@ class RateLimitException(WizamartException): def __init__( self, message: str = "Rate limit exceeded", - retry_after: Optional[int] = None, - details: Optional[Dict[str, Any]] = None, + retry_after: int | None = None, + details: dict[str, Any] | None = None, ): rate_limit_details = details or {} if retry_after: diff --git a/app/exceptions/cart.py b/app/exceptions/cart.py index 6a980730..fd62b890 100644 --- a/app/exceptions/cart.py +++ b/app/exceptions/cart.py @@ -3,10 +3,8 @@ Shopping cart specific exceptions. """ -from typing import Optional -from .base import (BusinessLogicException, ResourceNotFoundException, - ValidationException) +from .base import BusinessLogicException, ResourceNotFoundException, ValidationException class CartItemNotFoundException(ResourceNotFoundException): @@ -36,8 +34,8 @@ class CartValidationException(ValidationException): def __init__( self, message: str = "Cart validation failed", - field: Optional[str] = None, - details: Optional[dict] = None, + field: str | None = None, + details: dict | None = None, ): super().__init__( message=message, @@ -75,7 +73,7 @@ class InvalidCartQuantityException(ValidationException): """Raised when cart quantity is invalid.""" def __init__( - self, quantity: int, min_quantity: int = 1, max_quantity: Optional[int] = None + self, quantity: int, min_quantity: int = 1, max_quantity: int | None = None ): if quantity < min_quantity: message = f"Quantity must be at least {min_quantity}" diff --git a/app/exceptions/customer.py b/app/exceptions/customer.py index a9f50ace..055cf6ff 100644 --- a/app/exceptions/customer.py +++ b/app/exceptions/customer.py @@ -3,11 +3,15 @@ Customer management specific exceptions. """ -from typing import Any, Dict, Optional +from typing import Any -from .base import (AuthenticationException, BusinessLogicException, - ConflictException, ResourceNotFoundException, - ValidationException) +from .base import ( + AuthenticationException, + BusinessLogicException, + ConflictException, + ResourceNotFoundException, + ValidationException, +) class CustomerNotFoundException(ResourceNotFoundException): @@ -71,8 +75,8 @@ class CustomerValidationException(ValidationException): def __init__( self, message: str = "Customer validation failed", - field: Optional[str] = None, - details: Optional[Dict[str, Any]] = None, + field: str | None = None, + details: dict[str, Any] | None = None, ): super().__init__(message=message, field=field, details=details) self.error_code = "CUSTOMER_VALIDATION_FAILED" diff --git a/app/exceptions/error_renderer.py b/app/exceptions/error_renderer.py index cc804722..8a983d33 100644 --- a/app/exceptions/error_renderer.py +++ b/app/exceptions/error_renderer.py @@ -5,9 +5,10 @@ Error Page Renderer Renders context-aware error pages using Jinja2 templates. Handles fallback logic and context-specific customization. """ + import logging from pathlib import Path -from typing import Any, Dict, Optional +from typing import Any from fastapi import Request from fastapi.responses import HTMLResponse @@ -60,7 +61,7 @@ class ErrorPageRenderer: status_code: int, error_code: str, message: str, - details: Optional[Dict[str, Any]] = None, + details: dict[str, Any] | None = None, show_debug: bool = False, ) -> HTMLResponse: """ @@ -190,9 +191,9 @@ class ErrorPageRenderer: status_code: int, error_code: str, message: str, - details: Optional[Dict[str, Any]], + details: dict[str, Any] | None, show_debug: bool, - ) -> Dict[str, Any]: + ) -> dict[str, Any]: """Prepare data dictionary for error template.""" # Get friendly status name status_name = ErrorPageRenderer.STATUS_CODE_NAMES.get( @@ -229,7 +230,7 @@ class ErrorPageRenderer: @staticmethod def _get_context_data( request: Request, context_type: RequestContext - ) -> Dict[str, Any]: + ) -> dict[str, Any]: """Get context-specific data for error templates.""" data = {} diff --git a/app/exceptions/handler.py b/app/exceptions/handler.py index c1b38edf..c5b4dc0e 100644 --- a/app/exceptions/handler.py +++ b/app/exceptions/handler.py @@ -11,7 +11,6 @@ This module provides classes and functions for: """ import logging -from typing import Union from fastapi import HTTPException, Request from fastapi.exceptions import RequestValidationError @@ -280,7 +279,7 @@ def setup_exception_handlers(app): request=request, status_code=404, error_code="ENDPOINT_NOT_FOUND", - message=f"The page you're looking for doesn't exist or has been moved.", + message="The page you're looking for doesn't exist or has been moved.", details={"path": request.url.path}, show_debug=True, ) @@ -364,10 +363,10 @@ def _redirect_to_login(request: Request) -> RedirectResponse: if context_type == RequestContext.ADMIN: logger.debug("Redirecting to /admin/login") return RedirectResponse(url="/admin/login", status_code=302) - elif context_type == RequestContext.VENDOR_DASHBOARD: + if context_type == RequestContext.VENDOR_DASHBOARD: logger.debug("Redirecting to /vendor/login") return RedirectResponse(url="/vendor/login", status_code=302) - elif context_type == RequestContext.SHOP: + if context_type == RequestContext.SHOP: # For shop context, redirect to shop login (customer login) # Calculate base_url for proper routing (supports domain, subdomain, and path-based access) vendor = getattr(request.state, "vendor", None) @@ -390,10 +389,9 @@ def _redirect_to_login(request: Request) -> RedirectResponse: login_url = f"{base_url}shop/account/login" logger.debug(f"Redirecting to {login_url}") return RedirectResponse(url=login_url, status_code=302) - else: - # Fallback to root for unknown contexts - logger.debug("Unknown context, redirecting to /") - return RedirectResponse(url="/", status_code=302) + # Fallback to root for unknown contexts + logger.debug("Unknown context, redirecting to /") + return RedirectResponse(url="/", status_code=302) # Utility functions for common exception scenarios diff --git a/app/exceptions/inventory.py b/app/exceptions/inventory.py index c2c73fd7..e62c73a8 100644 --- a/app/exceptions/inventory.py +++ b/app/exceptions/inventory.py @@ -3,10 +3,9 @@ Inventory management specific exceptions. """ -from typing import Any, Dict, Optional +from typing import Any -from .base import (BusinessLogicException, ResourceNotFoundException, - ValidationException) +from .base import BusinessLogicException, ResourceNotFoundException, ValidationException class InventoryNotFoundException(ResourceNotFoundException): @@ -58,8 +57,8 @@ class InvalidInventoryOperationException(ValidationException): def __init__( self, message: str, - operation: Optional[str] = None, - details: Optional[Dict[str, Any]] = None, + operation: str | None = None, + details: dict[str, Any] | None = None, ): if not details: details = {} @@ -80,8 +79,8 @@ class InventoryValidationException(ValidationException): def __init__( self, message: str = "Inventory validation failed", - field: Optional[str] = None, - validation_errors: Optional[Dict[str, str]] = None, + field: str | None = None, + validation_errors: dict[str, str] | None = None, ): details = {} if validation_errors: diff --git a/app/exceptions/marketplace_import_job.py b/app/exceptions/marketplace_import_job.py index c442e1f9..abf5a9e3 100644 --- a/app/exceptions/marketplace_import_job.py +++ b/app/exceptions/marketplace_import_job.py @@ -3,11 +3,15 @@ Marketplace import specific exceptions. """ -from typing import Any, Dict, Optional +from typing import Any -from .base import (AuthorizationException, BusinessLogicException, - ExternalServiceException, ResourceNotFoundException, - ValidationException) +from .base import ( + AuthorizationException, + BusinessLogicException, + ExternalServiceException, + ResourceNotFoundException, + ValidationException, +) class MarketplaceImportException(BusinessLogicException): @@ -17,8 +21,8 @@ class MarketplaceImportException(BusinessLogicException): self, message: str, error_code: str = "MARKETPLACE_IMPORT_ERROR", - marketplace: Optional[str] = None, - details: Optional[Dict[str, Any]] = None, + marketplace: str | None = None, + details: dict[str, Any] | None = None, ): if not details: details = {} @@ -48,7 +52,7 @@ class ImportJobNotFoundException(ResourceNotFoundException): class ImportJobNotOwnedException(AuthorizationException): """Raised when user tries to access import job they don't own.""" - def __init__(self, job_id: int, user_id: Optional[int] = None): + def __init__(self, job_id: int, user_id: int | None = None): details = {"job_id": job_id} if user_id: details["user_id"] = user_id @@ -66,9 +70,9 @@ class InvalidImportDataException(ValidationException): def __init__( self, message: str = "Invalid import data", - field: Optional[str] = None, - row_number: Optional[int] = None, - details: Optional[Dict[str, Any]] = None, + field: str | None = None, + row_number: int | None = None, + details: dict[str, Any] | None = None, ): if not details: details = {} @@ -132,7 +136,7 @@ class MarketplaceDataParsingException(ValidationException): self, marketplace: str, message: str = "Failed to parse marketplace data", - details: Optional[Dict[str, Any]] = None, + details: dict[str, Any] | None = None, ): if not details: details = {} @@ -152,7 +156,7 @@ class ImportRateLimitException(BusinessLogicException): self, max_imports: int, time_window: str, - retry_after: Optional[int] = None, + retry_after: int | None = None, ): details = { "max_imports": max_imports, @@ -172,7 +176,7 @@ class ImportRateLimitException(BusinessLogicException): class InvalidMarketplaceException(ValidationException): """Raised when marketplace is not supported.""" - def __init__(self, marketplace: str, supported_marketplaces: Optional[list] = None): + def __init__(self, marketplace: str, supported_marketplaces: list | None = None): details = {"marketplace": marketplace} if supported_marketplaces: details["supported_marketplaces"] = supported_marketplaces diff --git a/app/exceptions/marketplace_product.py b/app/exceptions/marketplace_product.py index 8fbc3e71..a5b47e32 100644 --- a/app/exceptions/marketplace_product.py +++ b/app/exceptions/marketplace_product.py @@ -3,10 +3,14 @@ MarketplaceProduct management specific exceptions. """ -from typing import Any, Dict, Optional +from typing import Any -from .base import (BusinessLogicException, ConflictException, - ResourceNotFoundException, ValidationException) +from .base import ( + BusinessLogicException, + ConflictException, + ResourceNotFoundException, + ValidationException, +) class MarketplaceProductNotFoundException(ResourceNotFoundException): @@ -38,8 +42,8 @@ class InvalidMarketplaceProductDataException(ValidationException): def __init__( self, message: str = "Invalid product data", - field: Optional[str] = None, - details: Optional[Dict[str, Any]] = None, + field: str | None = None, + details: dict[str, Any] | None = None, ): super().__init__( message=message, @@ -55,8 +59,8 @@ class MarketplaceProductValidationException(ValidationException): def __init__( self, message: str, - field: Optional[str] = None, - validation_errors: Optional[Dict[str, str]] = None, + field: str | None = None, + validation_errors: dict[str, str] | None = None, ): details = {} if validation_errors: @@ -88,8 +92,8 @@ class MarketplaceProductCSVImportException(BusinessLogicException): def __init__( self, message: str = "MarketplaceProduct CSV import failed", - row_number: Optional[int] = None, - errors: Optional[Dict[str, Any]] = None, + row_number: int | None = None, + errors: dict[str, Any] | None = None, ): details = {} if row_number: diff --git a/app/exceptions/order.py b/app/exceptions/order.py index d5b74705..8a72de98 100644 --- a/app/exceptions/order.py +++ b/app/exceptions/order.py @@ -3,10 +3,8 @@ Order management specific exceptions. """ -from typing import Optional -from .base import (BusinessLogicException, ResourceNotFoundException, - ValidationException) +from .base import BusinessLogicException, ResourceNotFoundException, ValidationException class OrderNotFoundException(ResourceNotFoundException): @@ -35,7 +33,7 @@ class OrderAlreadyExistsException(ValidationException): class OrderValidationException(ValidationException): """Raised when order data validation fails.""" - def __init__(self, message: str, details: Optional[dict] = None): + def __init__(self, message: str, details: dict | None = None): super().__init__( message=message, error_code="ORDER_VALIDATION_FAILED", details=details ) diff --git a/app/exceptions/product.py b/app/exceptions/product.py index 72a3d8ba..72e8d377 100644 --- a/app/exceptions/product.py +++ b/app/exceptions/product.py @@ -3,16 +3,19 @@ Product (vendor catalog) specific exceptions. """ -from typing import Optional -from .base import (BusinessLogicException, ConflictException, - ResourceNotFoundException, ValidationException) +from .base import ( + BusinessLogicException, + ConflictException, + ResourceNotFoundException, + ValidationException, +) class ProductNotFoundException(ResourceNotFoundException): """Raised when a product is not found in vendor catalog.""" - def __init__(self, product_id: int, vendor_id: Optional[int] = None): + def __init__(self, product_id: int, vendor_id: int | None = None): details = {"product_id": product_id} if vendor_id: details["vendor_id"] = vendor_id @@ -79,8 +82,8 @@ class InvalidProductDataException(ValidationException): def __init__( self, message: str = "Invalid product data", - field: Optional[str] = None, - details: Optional[dict] = None, + field: str | None = None, + details: dict | None = None, ): super().__init__( message=message, @@ -96,8 +99,8 @@ class ProductValidationException(ValidationException): def __init__( self, message: str = "Product validation failed", - field: Optional[str] = None, - validation_errors: Optional[dict] = None, + field: str | None = None, + validation_errors: dict | None = None, ): details = {} if validation_errors: diff --git a/app/exceptions/team.py b/app/exceptions/team.py index 21283db1..70f229d2 100644 --- a/app/exceptions/team.py +++ b/app/exceptions/team.py @@ -3,17 +3,21 @@ Team management specific exceptions. """ -from typing import Any, Dict, Optional +from typing import Any -from .base import (AuthorizationException, BusinessLogicException, - ConflictException, ResourceNotFoundException, - ValidationException) +from .base import ( + AuthorizationException, + BusinessLogicException, + ConflictException, + ResourceNotFoundException, + ValidationException, +) class TeamMemberNotFoundException(ResourceNotFoundException): """Raised when a team member is not found.""" - def __init__(self, user_id: int, vendor_id: Optional[int] = None): + def __init__(self, user_id: int, vendor_id: int | None = None): details = {"user_id": user_id} if vendor_id: details["vendor_id"] = vendor_id @@ -63,7 +67,7 @@ class TeamInvitationExpiredException(BusinessLogicException): def __init__(self, invitation_token: str): super().__init__( - message=f"Team invitation has expired", + message="Team invitation has expired", error_code="TEAM_INVITATION_EXPIRED", details={"invitation_token": invitation_token}, ) @@ -86,8 +90,8 @@ class UnauthorizedTeamActionException(AuthorizationException): def __init__( self, action: str, - user_id: Optional[int] = None, - required_permission: Optional[str] = None, + user_id: int | None = None, + required_permission: str | None = None, ): details = {"action": action} if user_id: @@ -130,7 +134,7 @@ class CannotModifyOwnRoleException(BusinessLogicException): class RoleNotFoundException(ResourceNotFoundException): """Raised when a role is not found.""" - def __init__(self, role_id: int, vendor_id: Optional[int] = None): + def __init__(self, role_id: int, vendor_id: int | None = None): details = {"role_id": role_id} if vendor_id: details["vendor_id"] = vendor_id @@ -153,8 +157,8 @@ class InvalidRoleException(ValidationException): def __init__( self, message: str = "Invalid role data", - field: Optional[str] = None, - details: Optional[Dict[str, Any]] = None, + field: str | None = None, + details: dict[str, Any] | None = None, ): super().__init__( message=message, @@ -170,8 +174,8 @@ class InsufficientTeamPermissionsException(AuthorizationException): def __init__( self, required_permission: str, - user_id: Optional[int] = None, - action: Optional[str] = None, + user_id: int | None = None, + action: str | None = None, ): details = {"required_permission": required_permission} if user_id: @@ -208,8 +212,8 @@ class TeamValidationException(ValidationException): def __init__( self, message: str = "Team operation validation failed", - field: Optional[str] = None, - validation_errors: Optional[Dict[str, str]] = None, + field: str | None = None, + validation_errors: dict[str, str] | None = None, ): details = {} if validation_errors: @@ -229,8 +233,8 @@ class InvalidInvitationDataException(ValidationException): def __init__( self, message: str = "Invalid invitation data", - field: Optional[str] = None, - details: Optional[Dict[str, Any]] = None, + field: str | None = None, + details: dict[str, Any] | None = None, ): super().__init__( message=message, @@ -255,7 +259,7 @@ class InvalidInvitationTokenException(ValidationException): def __init__( self, message: str = "Invalid or expired invitation token", - invitation_token: Optional[str] = None, + invitation_token: str | None = None, ): details = {} if invitation_token: diff --git a/app/exceptions/vendor.py b/app/exceptions/vendor.py index 44380e67..d10f42ab 100644 --- a/app/exceptions/vendor.py +++ b/app/exceptions/vendor.py @@ -3,11 +3,15 @@ Vendor management specific exceptions. """ -from typing import Any, Dict, Optional +from typing import Any -from .base import (AuthorizationException, BusinessLogicException, - ConflictException, ResourceNotFoundException, - ValidationException) +from .base import ( + AuthorizationException, + BusinessLogicException, + ConflictException, + ResourceNotFoundException, + ValidationException, +) class VendorNotFoundException(ResourceNotFoundException): @@ -63,7 +67,7 @@ class VendorNotVerifiedException(BusinessLogicException): class UnauthorizedVendorAccessException(AuthorizationException): """Raised when user tries to access vendor they don't own.""" - def __init__(self, vendor_code: str, user_id: Optional[int] = None): + def __init__(self, vendor_code: str, user_id: int | None = None): details = {"vendor_code": vendor_code} if user_id: details["user_id"] = user_id @@ -81,8 +85,8 @@ class InvalidVendorDataException(ValidationException): def __init__( self, message: str = "Invalid vendor data", - field: Optional[str] = None, - details: Optional[Dict[str, Any]] = None, + field: str | None = None, + details: dict[str, Any] | None = None, ): super().__init__( message=message, @@ -98,8 +102,8 @@ class VendorValidationException(ValidationException): def __init__( self, message: str = "Vendor validation failed", - field: Optional[str] = None, - validation_errors: Optional[Dict[str, str]] = None, + field: str | None = None, + validation_errors: dict[str, str] | None = None, ): details = {} if validation_errors: @@ -134,7 +138,7 @@ class IncompleteVendorDataException(ValidationException): class MaxVendorsReachedException(BusinessLogicException): """Raised when user tries to create more vendors than allowed.""" - def __init__(self, max_vendors: int, user_id: Optional[int] = None): + def __init__(self, max_vendors: int, user_id: int | None = None): details = {"max_vendors": max_vendors} if user_id: details["user_id"] = user_id diff --git a/app/exceptions/vendor_domain.py b/app/exceptions/vendor_domain.py index 8a8e704b..147cc26d 100644 --- a/app/exceptions/vendor_domain.py +++ b/app/exceptions/vendor_domain.py @@ -3,11 +3,14 @@ Vendor domain management specific exceptions. """ -from typing import Any, Dict, Optional -from .base import (BusinessLogicException, ConflictException, - ExternalServiceException, ResourceNotFoundException, - ValidationException) +from .base import ( + BusinessLogicException, + ConflictException, + ExternalServiceException, + ResourceNotFoundException, + ValidationException, +) class VendorDomainNotFoundException(ResourceNotFoundException): @@ -30,7 +33,7 @@ class VendorDomainNotFoundException(ResourceNotFoundException): class VendorDomainAlreadyExistsException(ConflictException): """Raised when trying to add a domain that already exists.""" - def __init__(self, domain: str, existing_vendor_id: Optional[int] = None): + def __init__(self, domain: str, existing_vendor_id: int | None = None): details = {"domain": domain} if existing_vendor_id: details["existing_vendor_id"] = existing_vendor_id @@ -104,7 +107,7 @@ class MultiplePrimaryDomainsException(BusinessLogicException): def __init__(self, vendor_id: int): super().__init__( - message=f"Vendor can only have one primary domain", + message="Vendor can only have one primary domain", error_code="MULTIPLE_PRIMARY_DOMAINS", details={"vendor_id": vendor_id}, ) diff --git a/app/exceptions/vendor_theme.py b/app/exceptions/vendor_theme.py index 08000432..35b58222 100644 --- a/app/exceptions/vendor_theme.py +++ b/app/exceptions/vendor_theme.py @@ -3,10 +3,13 @@ Vendor theme management specific exceptions. """ -from typing import Any, Dict, Optional +from typing import Any -from .base import (BusinessLogicException, ConflictException, - ResourceNotFoundException, ValidationException) +from .base import ( + BusinessLogicException, + ResourceNotFoundException, + ValidationException, +) class VendorThemeNotFoundException(ResourceNotFoundException): @@ -27,8 +30,8 @@ class InvalidThemeDataException(ValidationException): def __init__( self, message: str = "Invalid theme data", - field: Optional[str] = None, - details: Optional[Dict[str, Any]] = None, + field: str | None = None, + details: dict[str, Any] | None = None, ): super().__init__( message=message, @@ -41,7 +44,7 @@ class InvalidThemeDataException(ValidationException): class ThemePresetNotFoundException(ResourceNotFoundException): """Raised when a theme preset is not found.""" - def __init__(self, preset_name: str, available_presets: Optional[list] = None): + def __init__(self, preset_name: str, available_presets: list | None = None): details = {"preset_name": preset_name} if available_presets: details["available_presets"] = available_presets @@ -61,8 +64,8 @@ class ThemeValidationException(ValidationException): def __init__( self, message: str = "Theme validation failed", - field: Optional[str] = None, - validation_errors: Optional[Dict[str, str]] = None, + field: str | None = None, + validation_errors: dict[str, str] | None = None, ): details = {} if validation_errors: diff --git a/app/models/architecture_scan.py b/app/models/architecture_scan.py index 403fbcbf..d0af9569 100644 --- a/app/models/architecture_scan.py +++ b/app/models/architecture_scan.py @@ -3,8 +3,17 @@ Architecture Scan Models Database models for tracking code quality scans and violations """ -from sqlalchemy import (JSON, Boolean, Column, DateTime, Float, ForeignKey, - Integer, String, Text) +from sqlalchemy import ( + JSON, + Boolean, + Column, + DateTime, + Float, + ForeignKey, + Integer, + String, + Text, +) from sqlalchemy.orm import relationship from sqlalchemy.sql import func diff --git a/app/routes/admin_pages.py b/app/routes/admin_pages.py index 736b27db..4cb6f26e 100644 --- a/app/routes/admin_pages.py +++ b/app/routes/admin_pages.py @@ -30,15 +30,17 @@ Routes: - GET /code-quality/violations/{violation_id} ā Violation details (auth required) """ -from typing import Optional from fastapi import APIRouter, Depends, Path, Request from fastapi.responses import HTMLResponse, RedirectResponse from fastapi.templating import Jinja2Templates from sqlalchemy.orm import Session -from app.api.deps import (get_current_admin_from_cookie_or_header, - get_current_admin_optional, get_db) +from app.api.deps import ( + get_current_admin_from_cookie_or_header, + get_current_admin_optional, + get_db, +) from models.database.user import User router = APIRouter() @@ -52,7 +54,7 @@ templates = Jinja2Templates(directory="app/templates") @router.get("/", response_class=RedirectResponse, include_in_schema=False) async def admin_root( - current_user: Optional[User] = Depends(get_current_admin_optional), + current_user: User | None = Depends(get_current_admin_optional), ): """ Redirect /admin/ based on authentication status. @@ -69,7 +71,7 @@ async def admin_root( @router.get("/login", response_class=HTMLResponse, include_in_schema=False) async def admin_login_page( - request: Request, current_user: Optional[User] = Depends(get_current_admin_optional) + request: Request, current_user: User | None = Depends(get_current_admin_optional) ): """ Render admin login page. diff --git a/app/routes/shop_pages.py b/app/routes/shop_pages.py index 8103d809..364a31e8 100644 --- a/app/routes/shop_pages.py +++ b/app/routes/shop_pages.py @@ -129,7 +129,7 @@ def get_shop_context(request: Request, db: Session = None, **extra_context) -> d ) except Exception as e: logger.error( - f"[SHOP_CONTEXT] Failed to load navigation pages", + "[SHOP_CONTEXT] Failed to load navigation pages", extra={"error": str(e), "vendor_id": vendor.id if vendor else None}, ) @@ -149,7 +149,7 @@ def get_shop_context(request: Request, db: Session = None, **extra_context) -> d context.update(extra_context) logger.debug( - f"[SHOP_CONTEXT] Context built", + "[SHOP_CONTEXT] Context built", extra={ "vendor_id": vendor.id if vendor else None, "vendor_name": vendor.name if vendor else None, @@ -179,7 +179,7 @@ async def shop_products_page(request: Request, db: Session = Depends(get_db)): Shows featured products and categories. """ logger.debug( - f"[SHOP_HANDLER] shop_products_page REACHED", + "[SHOP_HANDLER] shop_products_page REACHED", extra={ "path": request.url.path, "vendor": getattr(request.state, "vendor", "NOT SET"), @@ -203,7 +203,7 @@ async def shop_product_detail_page( Shows product information, images, reviews, and buy options. """ logger.debug( - f"[SHOP_HANDLER] shop_products_page REACHED", + "[SHOP_HANDLER] shop_products_page REACHED", extra={ "path": request.url.path, "vendor": getattr(request.state, "vendor", "NOT SET"), @@ -227,7 +227,7 @@ async def shop_category_page( Shows all products in a specific category. """ logger.debug( - f"[SHOP_HANDLER] shop_products_page REACHED", + "[SHOP_HANDLER] shop_products_page REACHED", extra={ "path": request.url.path, "vendor": getattr(request.state, "vendor", "NOT SET"), @@ -247,7 +247,7 @@ async def shop_cart_page(request: Request): Shows cart items and allows quantity updates. """ logger.debug( - f"[SHOP_HANDLER] shop_products_page REACHED", + "[SHOP_HANDLER] shop_products_page REACHED", extra={ "path": request.url.path, "vendor": getattr(request.state, "vendor", "NOT SET"), @@ -265,7 +265,7 @@ async def shop_checkout_page(request: Request): Handles shipping, payment, and order confirmation. """ logger.debug( - f"[SHOP_HANDLER] shop_products_page REACHED", + "[SHOP_HANDLER] shop_products_page REACHED", extra={ "path": request.url.path, "vendor": getattr(request.state, "vendor", "NOT SET"), @@ -283,7 +283,7 @@ async def shop_search_page(request: Request): Shows products matching search query. """ logger.debug( - f"[SHOP_HANDLER] shop_products_page REACHED", + "[SHOP_HANDLER] shop_products_page REACHED", extra={ "path": request.url.path, "vendor": getattr(request.state, "vendor", "NOT SET"), @@ -306,7 +306,7 @@ async def shop_register_page(request: Request): No authentication required. """ logger.debug( - f"[SHOP_HANDLER] shop_products_page REACHED", + "[SHOP_HANDLER] shop_products_page REACHED", extra={ "path": request.url.path, "vendor": getattr(request.state, "vendor", "NOT SET"), @@ -326,7 +326,7 @@ async def shop_login_page(request: Request): No authentication required. """ logger.debug( - f"[SHOP_HANDLER] shop_products_page REACHED", + "[SHOP_HANDLER] shop_products_page REACHED", extra={ "path": request.url.path, "vendor": getattr(request.state, "vendor", "NOT SET"), @@ -348,7 +348,7 @@ async def shop_forgot_password_page(request: Request): Allows customers to reset their password. """ logger.debug( - f"[SHOP_HANDLER] shop_products_page REACHED", + "[SHOP_HANDLER] shop_products_page REACHED", extra={ "path": request.url.path, "vendor": getattr(request.state, "vendor", "NOT SET"), @@ -373,7 +373,7 @@ async def shop_account_root(request: Request): Redirect /shop/account or /shop/account/ to dashboard. """ logger.debug( - f"[SHOP_HANDLER] shop_products_page REACHED", + "[SHOP_HANDLER] shop_products_page REACHED", extra={ "path": request.url.path, "vendor": getattr(request.state, "vendor", "NOT SET"), @@ -414,7 +414,7 @@ async def shop_account_dashboard_page( Requires customer authentication. """ logger.debug( - f"[SHOP_HANDLER] shop_products_page REACHED", + "[SHOP_HANDLER] shop_products_page REACHED", extra={ "path": request.url.path, "vendor": getattr(request.state, "vendor", "NOT SET"), @@ -439,7 +439,7 @@ async def shop_orders_page( Requires customer authentication. """ logger.debug( - f"[SHOP_HANDLER] shop_products_page REACHED", + "[SHOP_HANDLER] shop_products_page REACHED", extra={ "path": request.url.path, "vendor": getattr(request.state, "vendor", "NOT SET"), @@ -467,7 +467,7 @@ async def shop_order_detail_page( Requires customer authentication. """ logger.debug( - f"[SHOP_HANDLER] shop_products_page REACHED", + "[SHOP_HANDLER] shop_products_page REACHED", extra={ "path": request.url.path, "vendor": getattr(request.state, "vendor", "NOT SET"), @@ -493,7 +493,7 @@ async def shop_profile_page( Requires customer authentication. """ logger.debug( - f"[SHOP_HANDLER] shop_products_page REACHED", + "[SHOP_HANDLER] shop_products_page REACHED", extra={ "path": request.url.path, "vendor": getattr(request.state, "vendor", "NOT SET"), @@ -518,7 +518,7 @@ async def shop_addresses_page( Requires customer authentication. """ logger.debug( - f"[SHOP_HANDLER] shop_products_page REACHED", + "[SHOP_HANDLER] shop_products_page REACHED", extra={ "path": request.url.path, "vendor": getattr(request.state, "vendor", "NOT SET"), @@ -543,7 +543,7 @@ async def shop_wishlist_page( Requires customer authentication. """ logger.debug( - f"[SHOP_HANDLER] shop_products_page REACHED", + "[SHOP_HANDLER] shop_products_page REACHED", extra={ "path": request.url.path, "vendor": getattr(request.state, "vendor", "NOT SET"), @@ -568,7 +568,7 @@ async def shop_settings_page( Requires customer authentication. """ logger.debug( - f"[SHOP_HANDLER] shop_products_page REACHED", + "[SHOP_HANDLER] shop_products_page REACHED", extra={ "path": request.url.path, "vendor": getattr(request.state, "vendor", "NOT SET"), @@ -609,7 +609,7 @@ async def generic_content_page( from fastapi import HTTPException logger.debug( - f"[SHOP_HANDLER] generic_content_page REACHED", + "[SHOP_HANDLER] generic_content_page REACHED", extra={ "path": request.url.path, "slug": slug, @@ -628,7 +628,7 @@ async def generic_content_page( if not page: logger.warning( - f"[SHOP_HANDLER] Content page not found", + "[SHOP_HANDLER] Content page not found", extra={ "slug": slug, "vendor_id": vendor_id, @@ -638,7 +638,7 @@ async def generic_content_page( raise HTTPException(status_code=404, detail=f"Page not found: {slug}") logger.info( - f"[SHOP_HANDLER] Content page found", + "[SHOP_HANDLER] Content page found", extra={ "slug": slug, "page_id": page.id, @@ -709,14 +709,14 @@ async def debug_context(request: Request):
{json.dumps(debug_info, indent=2)}
- Vendor: {'ā Found' if vendor else 'ā Not Found'} +
+ Vendor: {"ā Found" if vendor else "ā Not Found"}
-- Theme: {'ā Found' if theme else 'ā Not Found'} +
+ Theme: {"ā Found" if theme else "ā Not Found"}
-- Context Type: {str(getattr(request.state, 'context_type', 'NOT SET'))} +
+ Context Type: {str(getattr(request.state, "context_type", "NOT SET"))}