refactor: modernize code quality tooling with Ruff

- Replace black, isort, and flake8 with Ruff (all-in-one linter and formatter)
- Add comprehensive pyproject.toml configuration
- Simplify Makefile code quality targets
- Configure exclusions for venv/.venv in pyproject.toml
- Auto-fix 1,359 linting issues across codebase

Benefits:
- Much faster builds (Ruff is written in Rust)
- Single tool replaces multiple tools
- More comprehensive rule set (UP, B, C4, SIM, PIE, RET, Q)
- All configuration centralized in pyproject.toml
- Better import sorting and formatting consistency

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-28 19:37:38 +01:00
parent 21c13ca39b
commit 238c1ec9b8
169 changed files with 2183 additions and 1784 deletions

View File

@@ -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.

View File

@@ -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()

View File

@@ -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),

View File

@@ -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"),

View File

@@ -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

View File

@@ -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),

View File

@@ -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),

View File

@@ -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),

View File

@@ -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),
):

View File

@@ -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),

View File

@@ -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__)

View File

@@ -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__)

View File

@@ -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,
)

View File

@@ -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__)

View File

@@ -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).

View File

@@ -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__)

View File

@@ -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),

View File

@@ -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()

View File

@@ -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

View File

@@ -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),

View File

@@ -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__)

View File

@@ -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__)

View File

@@ -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),

View File

@@ -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()),

View File

@@ -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),

View File

@@ -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),

View File

@@ -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),

View File

@@ -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),

View File

@@ -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),
):
"""

View File

@@ -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.

View File

@@ -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

View File

@@ -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")

View File

@@ -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.

View File

@@ -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

View File

@@ -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,

View File

@@ -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:

View File

@@ -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:

View File

@@ -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}"

View File

@@ -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"

View File

@@ -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 = {}

View File

@@ -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

View File

@@ -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:

View File

@@ -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

View File

@@ -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:

View File

@@ -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
)

View File

@@ -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:

View File

@@ -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:

View File

@@ -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

View File

@@ -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},
)

View File

@@ -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:

View File

@@ -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

View File

@@ -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.

View File

@@ -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):
<pre>{json.dumps(debug_info, indent=2)}</pre>
<h2>Status</h2>
<p class="{'good' if vendor else 'bad'}">
Vendor: {'✓ Found' if vendor else '✗ Not Found'}
<p class="{"good" if vendor else "bad"}">
Vendor: {"✓ Found" if vendor else "✗ Not Found"}
</p>
<p class="{'good' if theme else 'bad'}">
Theme: {'✓ Found' if theme else '✗ Not Found'}
<p class="{"good" if theme else "bad"}">
Theme: {"✓ Found" if theme else "✗ Not Found"}
</p>
<p class="{'good' if str(getattr(request.state, 'context_type', 'NOT SET')) == 'shop' else 'bad'}">
Context Type: {str(getattr(request.state, 'context_type', 'NOT SET'))}
<p class="{"good" if str(getattr(request.state, "context_type", "NOT SET")) == "shop" else "bad"}">
Context Type: {str(getattr(request.state, "context_type", "NOT SET"))}
</p>
</body>
</html>

View File

@@ -22,15 +22,17 @@ Routes:
"""
import logging
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException, 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_vendor_from_cookie_or_header,
get_current_vendor_optional, get_db)
from app.api.deps import (
get_current_vendor_from_cookie_or_header,
get_current_vendor_optional,
get_db,
)
from app.services.content_page_service import content_page_service
from models.database.user import User
@@ -57,7 +59,7 @@ async def vendor_root_no_slash(vendor_code: str = Path(..., description="Vendor
@router.get("/{vendor_code}/", response_class=RedirectResponse, include_in_schema=False)
async def vendor_root(
vendor_code: str = Path(..., description="Vendor code"),
current_user: Optional[User] = Depends(get_current_vendor_optional),
current_user: User | None = Depends(get_current_vendor_optional),
):
"""
Redirect /vendor/{code}/ based on authentication status.
@@ -78,7 +80,7 @@ async def vendor_root(
async def vendor_login_page(
request: Request,
vendor_code: str = Path(..., description="Vendor code"),
current_user: Optional[User] = Depends(get_current_vendor_optional),
current_user: User | None = Depends(get_current_vendor_optional),
):
"""
Render vendor login page.
@@ -374,7 +376,7 @@ async def vendor_content_page(
shadowing other specific routes.
"""
logger.debug(
f"[VENDOR_HANDLER] vendor_content_page REACHED",
"[VENDOR_HANDLER] vendor_content_page REACHED",
extra={
"path": request.url.path,
"vendor_code": vendor_code,

View File

@@ -9,10 +9,9 @@ This module provides functions for:
"""
import logging
from datetime import datetime, timezone
from typing import Any, Dict, List, Optional
from typing import Any
from sqlalchemy import and_, or_
from sqlalchemy import and_
from sqlalchemy.orm import Session
from app.exceptions import AdminOperationException
@@ -33,10 +32,10 @@ class AdminAuditService:
action: str,
target_type: str,
target_id: str,
details: Optional[Dict[str, Any]] = None,
ip_address: Optional[str] = None,
user_agent: Optional[str] = None,
request_id: Optional[str] = None,
details: dict[str, Any] | None = None,
ip_address: str | None = None,
user_agent: str | None = None,
request_id: str | None = None,
) -> AdminAuditLog:
"""
Log an admin action to the audit trail.
@@ -85,7 +84,7 @@ class AdminAuditService:
def get_audit_logs(
self, db: Session, filters: AdminAuditLogFilters
) -> List[AdminAuditLogResponse]:
) -> list[AdminAuditLogResponse]:
"""
Get filtered admin audit logs with pagination.
@@ -187,14 +186,14 @@ class AdminAuditService:
def get_recent_actions_by_admin(
self, db: Session, admin_user_id: int, limit: int = 10
) -> List[AdminAuditLogResponse]:
) -> list[AdminAuditLogResponse]:
"""Get recent actions by a specific admin."""
filters = AdminAuditLogFilters(admin_user_id=admin_user_id, limit=limit)
return self.get_audit_logs(db, filters)
def get_actions_by_target(
self, db: Session, target_type: str, target_id: str, limit: int = 50
) -> List[AdminAuditLogResponse]:
) -> list[AdminAuditLogResponse]:
"""Get all actions performed on a specific target."""
try:
logs = (

View File

@@ -13,17 +13,21 @@ This module provides classes and functions for:
import logging
import secrets
import string
from datetime import datetime, timezone
from typing import List, Optional, Tuple
from datetime import UTC, datetime
from sqlalchemy import func, or_
from sqlalchemy.orm import Session
from app.exceptions import (AdminOperationException, CannotModifySelfException,
UserNotFoundException, UserStatusChangeException,
ValidationException, VendorAlreadyExistsException,
VendorNotFoundException,
VendorVerificationException)
from app.exceptions import (
AdminOperationException,
CannotModifySelfException,
UserNotFoundException,
UserStatusChangeException,
ValidationException,
VendorAlreadyExistsException,
VendorNotFoundException,
VendorVerificationException,
)
from models.database.marketplace_import_job import MarketplaceImportJob
from models.database.user import User
from models.database.vendor import Role, Vendor, VendorUser
@@ -40,7 +44,7 @@ class AdminService:
# USER MANAGEMENT
# ============================================================================
def get_all_users(self, db: Session, skip: int = 0, limit: int = 100) -> List[User]:
def get_all_users(self, db: Session, skip: int = 0, limit: int = 100) -> list[User]:
"""Get paginated list of all users."""
try:
return db.query(User).offset(skip).limit(limit).all()
@@ -52,7 +56,7 @@ class AdminService:
def toggle_user_status(
self, db: Session, user_id: int, current_admin_id: int
) -> Tuple[User, str]:
) -> tuple[User, str]:
"""Toggle user active status."""
user = self._get_user_by_id_or_raise(db, user_id)
@@ -72,7 +76,7 @@ class AdminService:
try:
original_status = user.is_active
user.is_active = not user.is_active
user.updated_at = datetime.now(timezone.utc)
user.updated_at = datetime.now(UTC)
db.commit()
db.refresh(user)
@@ -98,7 +102,7 @@ class AdminService:
def create_vendor_with_owner(
self, db: Session, vendor_data: VendorCreate
) -> Tuple[Vendor, User, str]:
) -> tuple[Vendor, User, str]:
"""
Create vendor with owner user account.
@@ -222,10 +226,10 @@ class AdminService:
db: Session,
skip: int = 0,
limit: int = 100,
search: Optional[str] = None,
is_active: Optional[bool] = None,
is_verified: Optional[bool] = None,
) -> Tuple[List[Vendor], int]:
search: str | None = None,
is_active: bool | None = None,
is_verified: bool | None = None,
) -> tuple[list[Vendor], int]:
"""Get paginated list of all vendors with filtering."""
try:
query = db.query(Vendor)
@@ -261,17 +265,17 @@ class AdminService:
"""Get vendor by ID."""
return self._get_vendor_by_id_or_raise(db, vendor_id)
def verify_vendor(self, db: Session, vendor_id: int) -> Tuple[Vendor, str]:
def verify_vendor(self, db: Session, vendor_id: int) -> tuple[Vendor, str]:
"""Toggle vendor verification status."""
vendor = self._get_vendor_by_id_or_raise(db, vendor_id)
try:
original_status = vendor.is_verified
vendor.is_verified = not vendor.is_verified
vendor.updated_at = datetime.now(timezone.utc)
vendor.updated_at = datetime.now(UTC)
if vendor.is_verified:
vendor.verified_at = datetime.now(timezone.utc)
vendor.verified_at = datetime.now(UTC)
db.commit()
db.refresh(vendor)
@@ -291,14 +295,14 @@ class AdminService:
current_verification_status=original_status,
)
def toggle_vendor_status(self, db: Session, vendor_id: int) -> Tuple[Vendor, str]:
def toggle_vendor_status(self, db: Session, vendor_id: int) -> tuple[Vendor, str]:
"""Toggle vendor active status."""
vendor = self._get_vendor_by_id_or_raise(db, vendor_id)
try:
original_status = vendor.is_active
vendor.is_active = not vendor.is_active
vendor.updated_at = datetime.now(timezone.utc)
vendor.updated_at = datetime.now(UTC)
db.commit()
db.refresh(vendor)
@@ -347,7 +351,10 @@ class AdminService:
)
def update_vendor(
self, db: Session, vendor_id: int, vendor_update # VendorUpdate schema
self,
db: Session,
vendor_id: int,
vendor_update, # VendorUpdate schema
) -> Vendor:
"""
Update vendor information (Admin only).
@@ -402,7 +409,7 @@ class AdminService:
for field, value in update_data.items():
setattr(vendor, field, value)
vendor.updated_at = datetime.now(timezone.utc)
vendor.updated_at = datetime.now(UTC)
db.commit()
db.refresh(vendor)
@@ -430,7 +437,7 @@ class AdminService:
db: Session,
vendor_id: int,
transfer_data, # VendorTransferOwnership schema
) -> Tuple[Vendor, User, User]:
) -> tuple[Vendor, User, User]:
"""
Transfer vendor ownership to another user.
@@ -556,7 +563,7 @@ class AdminService:
# Update vendor owner_user_id
vendor.owner_user_id = new_owner.id
vendor.updated_at = datetime.now(timezone.utc)
vendor.updated_at = datetime.now(UTC)
db.commit()
db.refresh(vendor)
@@ -593,12 +600,12 @@ class AdminService:
def get_marketplace_import_jobs(
self,
db: Session,
marketplace: Optional[str] = None,
vendor_name: Optional[str] = None,
status: Optional[str] = None,
marketplace: str | None = None,
vendor_name: str | None = None,
status: str | None = None,
skip: int = 0,
limit: int = 100,
) -> List[MarketplaceImportJobResponse]:
) -> list[MarketplaceImportJobResponse]:
"""Get filtered and paginated marketplace import jobs."""
try:
query = db.query(MarketplaceImportJob)
@@ -633,7 +640,7 @@ class AdminService:
# STATISTICS
# ============================================================================
def get_recent_vendors(self, db: Session, limit: int = 5) -> List[dict]:
def get_recent_vendors(self, db: Session, limit: int = 5) -> list[dict]:
"""Get recently created vendors."""
try:
vendors = (
@@ -656,7 +663,7 @@ class AdminService:
logger.error(f"Failed to get recent vendors: {str(e)}")
return []
def get_recent_import_jobs(self, db: Session, limit: int = 10) -> List[dict]:
def get_recent_import_jobs(self, db: Session, limit: int = 10) -> list[dict]:
"""Get recent marketplace import jobs."""
try:
jobs = (

View File

@@ -10,17 +10,23 @@ This module provides functions for:
import json
import logging
from datetime import datetime, timezone
from typing import Any, Dict, List, Optional
from datetime import UTC, datetime
from typing import Any
from sqlalchemy import func
from sqlalchemy.orm import Session
from app.exceptions import (AdminOperationException, ResourceNotFoundException,
ValidationException)
from app.exceptions import (
AdminOperationException,
ResourceNotFoundException,
ValidationException,
)
from models.database.admin import AdminSetting
from models.schema.admin import (AdminSettingCreate, AdminSettingResponse,
AdminSettingUpdate)
from models.schema.admin import (
AdminSettingCreate,
AdminSettingResponse,
AdminSettingUpdate,
)
logger = logging.getLogger(__name__)
@@ -28,7 +34,7 @@ logger = logging.getLogger(__name__)
class AdminSettingsService:
"""Service for managing platform-wide settings."""
def get_setting_by_key(self, db: Session, key: str) -> Optional[AdminSetting]:
def get_setting_by_key(self, db: Session, key: str) -> AdminSetting | None:
"""Get setting by key."""
try:
return (
@@ -60,14 +66,13 @@ class AdminSettingsService:
try:
if setting.value_type == "integer":
return int(setting.value)
elif setting.value_type == "float":
if setting.value_type == "float":
return float(setting.value)
elif setting.value_type == "boolean":
if setting.value_type == "boolean":
return setting.value.lower() in ("true", "1", "yes")
elif setting.value_type == "json":
if setting.value_type == "json":
return json.loads(setting.value)
else:
return setting.value
return setting.value
except Exception as e:
logger.error(f"Failed to convert setting {key} value: {str(e)}")
return default
@@ -75,9 +80,9 @@ class AdminSettingsService:
def get_all_settings(
self,
db: Session,
category: Optional[str] = None,
is_public: Optional[bool] = None,
) -> List[AdminSettingResponse]:
category: str | None = None,
is_public: bool | None = None,
) -> list[AdminSettingResponse]:
"""Get all settings with optional filtering."""
try:
query = db.query(AdminSetting)
@@ -100,7 +105,7 @@ class AdminSettingsService:
operation="get_all_settings", reason="Database query failed"
)
def get_settings_by_category(self, db: Session, category: str) -> Dict[str, Any]:
def get_settings_by_category(self, db: Session, category: str) -> dict[str, Any]:
"""
Get all settings in a category as a dictionary.
@@ -198,7 +203,7 @@ class AdminSettingsService:
if update_data.description is not None:
setting.description = update_data.description
setting.last_modified_by_user_id = admin_user_id
setting.updated_at = datetime.now(timezone.utc)
setting.updated_at = datetime.now(UTC)
db.commit()
db.refresh(setting)
@@ -228,8 +233,7 @@ class AdminSettingsService:
value=setting_data.value, description=setting_data.description
)
return self.update_setting(db, setting_data.key, update_data, admin_user_id)
else:
return self.create_setting(db, setting_data, admin_user_id)
return self.create_setting(db, setting_data, admin_user_id)
def delete_setting(self, db: Session, key: str, admin_user_id: int) -> str:
"""Delete setting."""

View File

@@ -9,13 +9,17 @@ This module provides classes and functions for:
"""
import logging
from typing import Any, Dict, Optional
from datetime import UTC
from typing import Any
from sqlalchemy.orm import Session
from app.exceptions import (InvalidCredentialsException,
UserAlreadyExistsException, UserNotActiveException,
ValidationException)
from app.exceptions import (
InvalidCredentialsException,
UserAlreadyExistsException,
UserNotActiveException,
ValidationException,
)
from middleware.auth import AuthManager
from models.database.user import User
from models.schema.auth import UserLogin, UserRegister
@@ -82,7 +86,7 @@ class AuthService:
logger.error(f"Error registering user: {str(e)}")
raise ValidationException("Registration failed")
def login_user(self, db: Session, user_credentials: UserLogin) -> Dict[str, Any]:
def login_user(self, db: Session, user_credentials: UserLogin) -> dict[str, Any]:
"""
Login user and return JWT token with user data.
@@ -120,7 +124,7 @@ class AuthService:
logger.error(f"Error during login: {str(e)}")
raise InvalidCredentialsException()
def get_user_by_email(self, db: Session, email: str) -> Optional[User]:
def get_user_by_email(self, db: Session, email: str) -> User | None:
"""Get user by email."""
try:
return db.query(User).filter(User.email == email).first()
@@ -128,7 +132,7 @@ class AuthService:
logger.error(f"Error getting user by email: {str(e)}")
return None
def get_user_by_username(self, db: Session, username: str) -> Optional[User]:
def get_user_by_username(self, db: Session, username: str) -> User | None:
"""Get user by username."""
try:
return db.query(User).filter(User.username == username).first()
@@ -138,7 +142,7 @@ class AuthService:
def authenticate_user(
self, db: Session, username: str, password: str
) -> Optional[User]:
) -> User | None:
"""Authenticate user with username/password."""
try:
return self.auth_manager.authenticate_user(db, username, password)
@@ -146,7 +150,7 @@ class AuthService:
logger.error(f"Error authenticating user: {str(e)}")
return None
def create_access_token(self, user: User) -> Dict[str, Any]:
def create_access_token(self, user: User) -> dict[str, Any]:
"""Create access token for user."""
try:
return self.auth_manager.create_access_token(user)
@@ -182,7 +186,7 @@ class AuthService:
Returns:
Dictionary with access_token, token_type, and expires_in
"""
from datetime import datetime, timedelta, timezone
from datetime import datetime, timedelta
from jose import jwt
@@ -190,13 +194,13 @@ class AuthService:
try:
expires_delta = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
expire = datetime.now(timezone.utc) + expires_delta
expire = datetime.now(UTC) + expires_delta
# Build payload with provided data
payload = {
**data,
"exp": expire,
"iat": datetime.now(timezone.utc),
"iat": datetime.now(UTC),
}
token = jwt.encode(payload, settings.SECRET_KEY, algorithm="HS256")

View File

@@ -9,20 +9,18 @@ This module provides:
"""
import logging
from datetime import datetime, timezone
from typing import Dict, List, Optional
from sqlalchemy import and_
from sqlalchemy.orm import Session
from app.exceptions import (CartItemNotFoundException, CartValidationException,
InsufficientInventoryForCartException,
InvalidCartQuantityException,
ProductNotAvailableForCartException,
ProductNotFoundException)
from app.exceptions import (
CartItemNotFoundException,
InsufficientInventoryForCartException,
InvalidCartQuantityException,
ProductNotFoundException,
)
from models.database.cart import CartItem
from models.database.product import Product
from models.database.vendor import Vendor
logger = logging.getLogger(__name__)
@@ -30,7 +28,7 @@ logger = logging.getLogger(__name__)
class CartService:
"""Service for managing shopping carts."""
def get_cart(self, db: Session, vendor_id: int, session_id: str) -> Dict:
def get_cart(self, db: Session, vendor_id: int, session_id: str) -> dict:
"""
Get cart contents for a session.
@@ -43,7 +41,7 @@ class CartService:
Cart data with items and totals
"""
logger.info(
f"[CART_SERVICE] get_cart called",
"[CART_SERVICE] get_cart called",
extra={
"vendor_id": vendor_id,
"session_id": session_id,
@@ -111,7 +109,7 @@ class CartService:
session_id: str,
product_id: int,
quantity: int = 1,
) -> Dict:
) -> dict:
"""
Add product to cart.
@@ -130,7 +128,7 @@ class CartService:
InsufficientInventoryException: If not enough inventory
"""
logger.info(
f"[CART_SERVICE] add_to_cart called",
"[CART_SERVICE] add_to_cart called",
extra={
"vendor_id": vendor_id,
"session_id": session_id,
@@ -154,7 +152,7 @@ class CartService:
if not product:
logger.error(
f"[CART_SERVICE] Product not found",
"[CART_SERVICE] Product not found",
extra={"product_id": product_id, "vendor_id": vendor_id},
)
raise ProductNotFoundException(product_id=product_id, vendor_id=vendor_id)
@@ -191,7 +189,7 @@ class CartService:
# Check inventory for new total quantity
if product.available_inventory < new_quantity:
logger.warning(
f"[CART_SERVICE] Insufficient inventory for update",
"[CART_SERVICE] Insufficient inventory for update",
extra={
"product_id": product_id,
"current_in_cart": existing_item.quantity,
@@ -212,7 +210,7 @@ class CartService:
db.refresh(existing_item)
logger.info(
f"[CART_SERVICE] Updated existing cart item",
"[CART_SERVICE] Updated existing cart item",
extra={"cart_item_id": existing_item.id, "new_quantity": new_quantity},
)
@@ -221,50 +219,49 @@ class CartService:
"product_id": product_id,
"quantity": new_quantity,
}
else:
# Check inventory for new item
if product.available_inventory < quantity:
logger.warning(
f"[CART_SERVICE] Insufficient inventory",
extra={
"product_id": product_id,
"requested": quantity,
"available": product.available_inventory,
},
)
raise InsufficientInventoryForCartException(
product_id=product_id,
product_name=product.marketplace_product.title,
requested=quantity,
available=product.available_inventory,
)
# Create new cart item
cart_item = CartItem(
vendor_id=vendor_id,
session_id=session_id,
product_id=product_id,
quantity=quantity,
price_at_add=current_price,
)
db.add(cart_item)
db.commit()
db.refresh(cart_item)
logger.info(
f"[CART_SERVICE] Created new cart item",
# Check inventory for new item
if product.available_inventory < quantity:
logger.warning(
"[CART_SERVICE] Insufficient inventory",
extra={
"cart_item_id": cart_item.id,
"quantity": quantity,
"price": current_price,
"product_id": product_id,
"requested": quantity,
"available": product.available_inventory,
},
)
raise InsufficientInventoryForCartException(
product_id=product_id,
product_name=product.marketplace_product.title,
requested=quantity,
available=product.available_inventory,
)
return {
"message": "Product added to cart",
"product_id": product_id,
# Create new cart item
cart_item = CartItem(
vendor_id=vendor_id,
session_id=session_id,
product_id=product_id,
quantity=quantity,
price_at_add=current_price,
)
db.add(cart_item)
db.commit()
db.refresh(cart_item)
logger.info(
"[CART_SERVICE] Created new cart item",
extra={
"cart_item_id": cart_item.id,
"quantity": quantity,
}
"price": current_price,
},
)
return {
"message": "Product added to cart",
"product_id": product_id,
"quantity": quantity,
}
def update_cart_item(
self,
@@ -273,7 +270,7 @@ class CartService:
session_id: str,
product_id: int,
quantity: int,
) -> Dict:
) -> dict:
"""
Update quantity of item in cart.
@@ -344,7 +341,7 @@ class CartService:
db.refresh(cart_item)
logger.info(
f"[CART_SERVICE] Updated cart item quantity",
"[CART_SERVICE] Updated cart item quantity",
extra={
"cart_item_id": cart_item.id,
"product_id": product_id,
@@ -360,7 +357,7 @@ class CartService:
def remove_from_cart(
self, db: Session, vendor_id: int, session_id: str, product_id: int
) -> Dict:
) -> dict:
"""
Remove item from cart.
@@ -398,7 +395,7 @@ class CartService:
db.commit()
logger.info(
f"[CART_SERVICE] Removed item from cart",
"[CART_SERVICE] Removed item from cart",
extra={
"cart_item_id": cart_item.id,
"product_id": product_id,
@@ -408,7 +405,7 @@ class CartService:
return {"message": "Item removed from cart", "product_id": product_id}
def clear_cart(self, db: Session, vendor_id: int, session_id: str) -> Dict:
def clear_cart(self, db: Session, vendor_id: int, session_id: str) -> dict:
"""
Clear all items from cart.
@@ -432,7 +429,7 @@ class CartService:
db.commit()
logger.info(
f"[CART_SERVICE] Cleared cart",
"[CART_SERVICE] Cleared cart",
extra={
"session_id": session_id,
"vendor_id": vendor_id,

View File

@@ -7,16 +7,16 @@ import json
import logging
import subprocess
from datetime import datetime
from pathlib import Path
from typing import Dict, List, Optional, Tuple
from sqlalchemy import desc, func
from sqlalchemy.orm import Session
from app.models.architecture_scan import (ArchitectureRule, ArchitectureScan,
ArchitectureViolation,
ViolationAssignment,
ViolationComment)
from app.models.architecture_scan import (
ArchitectureScan,
ArchitectureViolation,
ViolationAssignment,
ViolationComment,
)
logger = logging.getLogger(__name__)
@@ -118,7 +118,7 @@ class CodeQualityService:
logger.info(f"Scan completed: {scan.total_violations} violations found")
return scan
def get_latest_scan(self, db: Session) -> Optional[ArchitectureScan]:
def get_latest_scan(self, db: Session) -> ArchitectureScan | None:
"""Get the most recent scan"""
return (
db.query(ArchitectureScan)
@@ -126,11 +126,11 @@ class CodeQualityService:
.first()
)
def get_scan_by_id(self, db: Session, scan_id: int) -> Optional[ArchitectureScan]:
def get_scan_by_id(self, db: Session, scan_id: int) -> ArchitectureScan | None:
"""Get scan by ID"""
return db.query(ArchitectureScan).filter(ArchitectureScan.id == scan_id).first()
def get_scan_history(self, db: Session, limit: int = 30) -> List[ArchitectureScan]:
def get_scan_history(self, db: Session, limit: int = 30) -> list[ArchitectureScan]:
"""
Get scan history for trend graphs
@@ -158,7 +158,7 @@ class CodeQualityService:
file_path: str = None,
limit: int = 100,
offset: int = 0,
) -> Tuple[List[ArchitectureViolation], int]:
) -> tuple[list[ArchitectureViolation], int]:
"""
Get violations with filtering and pagination
@@ -217,7 +217,7 @@ class CodeQualityService:
def get_violation_by_id(
self, db: Session, violation_id: int
) -> Optional[ArchitectureViolation]:
) -> ArchitectureViolation | None:
"""Get single violation with details"""
return (
db.query(ArchitectureViolation)
@@ -348,7 +348,7 @@ class CodeQualityService:
logger.info(f"Comment added to violation {violation_id} by user {user_id}")
return comment_obj
def get_dashboard_stats(self, db: Session) -> Dict:
def get_dashboard_stats(self, db: Session) -> dict:
"""
Get statistics for dashboard
@@ -507,7 +507,7 @@ class CodeQualityService:
score = 100 - (scan.errors * 0.5 + scan.warnings * 0.05)
return max(0, min(100, int(score))) # Clamp to 0-100
def _get_git_commit_hash(self) -> Optional[str]:
def _get_git_commit_hash(self) -> str | None:
"""Get current git commit hash"""
try:
result = subprocess.run(

View File

@@ -17,10 +17,9 @@ This allows:
"""
import logging
from datetime import datetime, timezone
from typing import List, Optional
from datetime import UTC, datetime
from sqlalchemy import and_, or_
from sqlalchemy import and_
from sqlalchemy.orm import Session
from models.database.content_page import ContentPage
@@ -35,9 +34,9 @@ class ContentPageService:
def get_page_for_vendor(
db: Session,
slug: str,
vendor_id: Optional[int] = None,
vendor_id: int | None = None,
include_unpublished: bool = False,
) -> Optional[ContentPage]:
) -> ContentPage | None:
"""
Get content page for a vendor with fallback to platform default.
@@ -90,11 +89,11 @@ class ContentPageService:
@staticmethod
def list_pages_for_vendor(
db: Session,
vendor_id: Optional[int] = None,
vendor_id: int | None = None,
include_unpublished: bool = False,
footer_only: bool = False,
header_only: bool = False,
) -> List[ContentPage]:
) -> list[ContentPage]:
"""
List all available pages for a vendor (includes vendor overrides + platform defaults).
@@ -156,16 +155,16 @@ class ContentPageService:
slug: str,
title: str,
content: str,
vendor_id: Optional[int] = None,
vendor_id: int | None = None,
content_format: str = "html",
template: str = "default",
meta_description: Optional[str] = None,
meta_keywords: Optional[str] = None,
meta_description: str | None = None,
meta_keywords: str | None = None,
is_published: bool = False,
show_in_footer: bool = True,
show_in_header: bool = False,
display_order: int = 0,
created_by: Optional[int] = None,
created_by: int | None = None,
) -> ContentPage:
"""
Create a new content page.
@@ -199,7 +198,7 @@ class ContentPageService:
meta_description=meta_description,
meta_keywords=meta_keywords,
is_published=is_published,
published_at=datetime.now(timezone.utc) if is_published else None,
published_at=datetime.now(UTC) if is_published else None,
show_in_footer=show_in_footer,
show_in_header=show_in_header,
display_order=display_order,
@@ -220,18 +219,18 @@ class ContentPageService:
def update_page(
db: Session,
page_id: int,
title: Optional[str] = None,
content: Optional[str] = None,
content_format: Optional[str] = None,
template: Optional[str] = None,
meta_description: Optional[str] = None,
meta_keywords: Optional[str] = None,
is_published: Optional[bool] = None,
show_in_footer: Optional[bool] = None,
show_in_header: Optional[bool] = None,
display_order: Optional[int] = None,
updated_by: Optional[int] = None,
) -> Optional[ContentPage]:
title: str | None = None,
content: str | None = None,
content_format: str | None = None,
template: str | None = None,
meta_description: str | None = None,
meta_keywords: str | None = None,
is_published: bool | None = None,
show_in_footer: bool | None = None,
show_in_header: bool | None = None,
display_order: int | None = None,
updated_by: int | None = None,
) -> ContentPage | None:
"""
Update an existing content page.
@@ -275,7 +274,7 @@ class ContentPageService:
if is_published is not None:
page.is_published = is_published
if is_published and not page.published_at:
page.published_at = datetime.now(timezone.utc)
page.published_at = datetime.now(UTC)
if show_in_footer is not None:
page.show_in_footer = show_in_footer
if show_in_header is not None:
@@ -316,14 +315,14 @@ class ContentPageService:
return True
@staticmethod
def get_page_by_id(db: Session, page_id: int) -> Optional[ContentPage]:
def get_page_by_id(db: Session, page_id: int) -> ContentPage | None:
"""Get content page by ID."""
return db.query(ContentPage).filter(ContentPage.id == page_id).first()
@staticmethod
def list_all_vendor_pages(
db: Session, vendor_id: int, include_unpublished: bool = False
) -> List[ContentPage]:
) -> list[ContentPage]:
"""
List only vendor-specific pages (no platform defaults).
@@ -350,7 +349,7 @@ class ContentPageService:
@staticmethod
def list_all_platform_pages(
db: Session, include_unpublished: bool = False
) -> List[ContentPage]:
) -> list[ContentPage]:
"""
List only platform default pages.

View File

@@ -7,22 +7,22 @@ with complete vendor isolation.
"""
import logging
from datetime import datetime, timedelta
from typing import Any, Dict, Optional
from datetime import UTC, datetime, timedelta
from typing import Any
from sqlalchemy import and_
from sqlalchemy.orm import Session
from app.exceptions.customer import (CustomerAlreadyExistsException,
CustomerNotActiveException,
CustomerNotFoundException,
CustomerValidationException,
DuplicateCustomerEmailException,
InvalidCustomerCredentialsException)
from app.exceptions.vendor import (VendorNotActiveException,
VendorNotFoundException)
from app.exceptions.customer import (
CustomerNotActiveException,
CustomerNotFoundException,
CustomerValidationException,
DuplicateCustomerEmailException,
InvalidCustomerCredentialsException,
)
from app.exceptions.vendor import VendorNotActiveException, VendorNotFoundException
from app.services.auth_service import AuthService
from models.database.customer import Customer, CustomerAddress
from models.database.customer import Customer
from models.database.vendor import Vendor
from models.schema.auth import UserLogin
from models.schema.customer import CustomerRegister, CustomerUpdate
@@ -128,7 +128,7 @@ class CustomerService:
def login_customer(
self, db: Session, vendor_id: int, credentials: UserLogin
) -> Dict[str, Any]:
) -> dict[str, Any]:
"""
Authenticate customer and generate JWT token.
@@ -177,13 +177,13 @@ class CustomerService:
# Generate JWT token with customer context
# Use auth_manager directly since Customer is not a User model
from datetime import datetime, timedelta, timezone
from datetime import datetime
from jose import jwt
auth_manager = self.auth_service.auth_manager
expires_delta = timedelta(minutes=auth_manager.token_expire_minutes)
expire = datetime.now(timezone.utc) + expires_delta
expire = datetime.now(UTC) + expires_delta
payload = {
"sub": str(customer.id),
@@ -191,7 +191,7 @@ class CustomerService:
"vendor_id": vendor_id,
"type": "customer",
"exp": expire,
"iat": datetime.now(timezone.utc),
"iat": datetime.now(UTC),
}
token = jwt.encode(
@@ -239,7 +239,7 @@ class CustomerService:
def get_customer_by_email(
self, db: Session, vendor_id: int, email: str
) -> Optional[Customer]:
) -> Customer | None:
"""
Get customer by email (vendor-scoped).

View File

@@ -1,24 +1,27 @@
# app/services/inventory_service.py
import logging
from datetime import datetime, timezone
from typing import List, Optional
from datetime import UTC, datetime
from sqlalchemy.orm import Session
from app.exceptions import (InsufficientInventoryException,
InvalidInventoryOperationException,
InvalidQuantityException,
InventoryNotFoundException,
InventoryValidationException,
NegativeInventoryException,
ProductNotFoundException, ValidationException)
from app.exceptions import (
InsufficientInventoryException,
InvalidQuantityException,
InventoryNotFoundException,
InventoryValidationException,
ProductNotFoundException,
ValidationException,
)
from models.database.inventory import Inventory
from models.database.product import Product
from models.database.vendor import Vendor
from models.schema.inventory import (InventoryAdjust, InventoryCreate,
InventoryLocationResponse,
InventoryReserve, InventoryUpdate,
ProductInventorySummary)
from models.schema.inventory import (
InventoryAdjust,
InventoryCreate,
InventoryLocationResponse,
InventoryReserve,
InventoryUpdate,
ProductInventorySummary,
)
logger = logging.getLogger(__name__)
@@ -58,7 +61,7 @@ class InventoryService:
if existing:
old_qty = existing.quantity
existing.quantity = inventory_data.quantity
existing.updated_at = datetime.now(timezone.utc)
existing.updated_at = datetime.now(UTC)
db.commit()
db.refresh(existing)
@@ -67,24 +70,23 @@ class InventoryService:
f"{old_qty}{inventory_data.quantity}"
)
return existing
else:
# Create new inventory entry
new_inventory = Inventory(
product_id=inventory_data.product_id,
vendor_id=vendor_id,
location=location,
quantity=inventory_data.quantity,
gtin=product.marketplace_product.gtin, # Optional reference
)
db.add(new_inventory)
db.commit()
db.refresh(new_inventory)
# Create new inventory entry
new_inventory = Inventory(
product_id=inventory_data.product_id,
vendor_id=vendor_id,
location=location,
quantity=inventory_data.quantity,
gtin=product.marketplace_product.gtin, # Optional reference
)
db.add(new_inventory)
db.commit()
db.refresh(new_inventory)
logger.info(
f"Created inventory for product {inventory_data.product_id} at {location}: "
f"{inventory_data.quantity}"
)
return new_inventory
logger.info(
f"Created inventory for product {inventory_data.product_id} at {location}: "
f"{inventory_data.quantity}"
)
return new_inventory
except (
ProductNotFoundException,
@@ -162,7 +164,7 @@ class InventoryService:
)
existing.quantity = new_qty
existing.updated_at = datetime.now(timezone.utc)
existing.updated_at = datetime.now(UTC)
db.commit()
db.refresh(existing)
@@ -224,7 +226,7 @@ class InventoryService:
# Reserve inventory
inventory.reserved_quantity += reserve_data.quantity
inventory.updated_at = datetime.now(timezone.utc)
inventory.updated_at = datetime.now(UTC)
db.commit()
db.refresh(inventory)
@@ -284,7 +286,7 @@ class InventoryService:
else:
inventory.reserved_quantity -= reserve_data.quantity
inventory.updated_at = datetime.now(timezone.utc)
inventory.updated_at = datetime.now(UTC)
db.commit()
db.refresh(inventory)
@@ -350,7 +352,7 @@ class InventoryService:
inventory.reserved_quantity = max(
0, inventory.reserved_quantity - reserve_data.quantity
)
inventory.updated_at = datetime.now(timezone.utc)
inventory.updated_at = datetime.now(UTC)
db.commit()
db.refresh(inventory)
@@ -443,9 +445,9 @@ class InventoryService:
vendor_id: int,
skip: int = 0,
limit: int = 100,
location: Optional[str] = None,
low_stock_threshold: Optional[int] = None,
) -> List[Inventory]:
location: str | None = None,
low_stock_threshold: int | None = None,
) -> list[Inventory]:
"""
Get all inventory for a vendor with filtering.
@@ -504,7 +506,7 @@ class InventoryService:
if inventory_update.location:
inventory.location = self._validate_location(inventory_update.location)
inventory.updated_at = datetime.now(timezone.utc)
inventory.updated_at = datetime.now(UTC)
db.commit()
db.refresh(inventory)
@@ -565,7 +567,7 @@ class InventoryService:
def _get_inventory_entry(
self, db: Session, product_id: int, location: str
) -> Optional[Inventory]:
) -> Inventory | None:
"""Get inventory entry by product and location."""
return (
db.query(Inventory)

View File

@@ -1,19 +1,20 @@
# app/services/marketplace_import_job_service.py
import logging
from datetime import datetime, timezone
from typing import List, Optional
from sqlalchemy.orm import Session
from app.exceptions import (ImportJobCannotBeCancelledException,
ImportJobCannotBeDeletedException,
ImportJobNotFoundException,
ImportJobNotOwnedException, ValidationException)
from app.exceptions import (
ImportJobNotFoundException,
ImportJobNotOwnedException,
ValidationException,
)
from models.database.marketplace_import_job import MarketplaceImportJob
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,
)
logger = logging.getLogger(__name__)
@@ -98,10 +99,10 @@ class MarketplaceImportJobService:
db: Session,
vendor: Vendor, # ADDED: Vendor filter
user: User,
marketplace: Optional[str] = None,
marketplace: str | None = None,
skip: int = 0,
limit: int = 50,
) -> List[MarketplaceImportJob]:
) -> list[MarketplaceImportJob]:
"""Get marketplace import jobs for a specific vendor."""
try:
query = db.query(MarketplaceImportJob).filter(

View File

@@ -8,29 +8,31 @@ This module provides classes and functions for:
- Inventory information integration
- CSV export functionality
"""
import csv
import logging
from datetime import datetime, timezone
from collections.abc import Generator
from datetime import UTC, datetime
from io import StringIO
from typing import Generator, List, Optional, Tuple
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm import Session
from app.exceptions import (InvalidMarketplaceProductDataException,
MarketplaceProductAlreadyExistsException,
MarketplaceProductNotFoundException,
MarketplaceProductValidationException,
ValidationException)
from app.services.marketplace_import_job_service import \
marketplace_import_job_service
from app.exceptions import (
InvalidMarketplaceProductDataException,
MarketplaceProductAlreadyExistsException,
MarketplaceProductNotFoundException,
MarketplaceProductValidationException,
ValidationException,
)
from app.utils.data_processing import GTINProcessor, PriceProcessor
from models.database.inventory import Inventory
from models.database.marketplace_product import MarketplaceProduct
from models.schema.inventory import (InventoryLocationResponse,
InventorySummaryResponse)
from models.schema.marketplace_product import (MarketplaceProductCreate,
MarketplaceProductUpdate)
from models.schema.inventory import InventoryLocationResponse, InventorySummaryResponse
from models.schema.marketplace_product import (
MarketplaceProductCreate,
MarketplaceProductUpdate,
)
logger = logging.getLogger(__name__)
@@ -109,10 +111,9 @@ class MarketplaceProductService:
raise MarketplaceProductAlreadyExistsException(
product_data.marketplace_product_id
)
else:
raise MarketplaceProductValidationException(
"Data integrity constraint violation"
)
raise MarketplaceProductValidationException(
"Data integrity constraint violation"
)
except Exception as e:
db.rollback()
logger.error(f"Error creating product: {str(e)}")
@@ -120,7 +121,7 @@ class MarketplaceProductService:
def get_product_by_id(
self, db: Session, marketplace_product_id: str
) -> Optional[MarketplaceProduct]:
) -> MarketplaceProduct | None:
"""Get a product by its ID."""
try:
return (
@@ -160,13 +161,13 @@ class MarketplaceProductService:
db: Session,
skip: int = 0,
limit: int = 100,
brand: Optional[str] = None,
category: Optional[str] = None,
availability: Optional[str] = None,
marketplace: Optional[str] = None,
vendor_name: Optional[str] = None,
search: Optional[str] = None,
) -> Tuple[List[MarketplaceProduct], int]:
brand: str | None = None,
category: str | None = None,
availability: str | None = None,
marketplace: str | None = None,
vendor_name: str | None = None,
search: str | None = None,
) -> tuple[list[MarketplaceProduct], int]:
"""
Get products with filtering and pagination.
@@ -269,7 +270,7 @@ class MarketplaceProductService:
for key, value in update_data.items():
setattr(product, key, value)
product.updated_at = datetime.now(timezone.utc)
product.updated_at = datetime.now(UTC)
db.commit()
db.refresh(product)
@@ -324,7 +325,7 @@ class MarketplaceProductService:
def get_inventory_info(
self, db: Session, gtin: str
) -> Optional[InventorySummaryResponse]:
) -> InventorySummaryResponse | None:
"""
Get inventory information for a product by GTIN.
@@ -358,15 +359,14 @@ class MarketplaceProductService:
import csv
from io import StringIO
from typing import Generator, Optional
from sqlalchemy.orm import Session
def generate_csv_export(
self,
db: Session,
marketplace: Optional[str] = None,
vendor_name: Optional[str] = None,
marketplace: str | None = None,
vendor_name: str | None = None,
) -> Generator[str, None, None]:
"""
Generate CSV export with streaming for memory efficiency and proper CSV escaping.

View File

@@ -11,15 +11,17 @@ This module provides:
import logging
import random
import string
from datetime import datetime, timezone
from typing import List, Optional, Tuple
from datetime import UTC, datetime
from sqlalchemy import and_, or_
from sqlalchemy import and_
from sqlalchemy.orm import Session
from app.exceptions import (CustomerNotFoundException,
InsufficientInventoryException,
OrderNotFoundException, ValidationException)
from app.exceptions import (
CustomerNotFoundException,
InsufficientInventoryException,
OrderNotFoundException,
ValidationException,
)
from models.database.customer import Customer, CustomerAddress
from models.database.order import Order, OrderItem
from models.database.product import Product
@@ -38,7 +40,7 @@ class OrderService:
Format: ORD-{VENDOR_ID}-{TIMESTAMP}-{RANDOM}
Example: ORD-1-20250110-A1B2C3
"""
timestamp = datetime.now(timezone.utc).strftime("%Y%m%d")
timestamp = datetime.now(UTC).strftime("%Y%m%d")
random_suffix = "".join(
random.choices(string.ascii_uppercase + string.digits, k=6)
)
@@ -266,9 +268,9 @@ class OrderService:
vendor_id: int,
skip: int = 0,
limit: int = 100,
status: Optional[str] = None,
customer_id: Optional[int] = None,
) -> Tuple[List[Order], int]:
status: str | None = None,
customer_id: int | None = None,
) -> tuple[list[Order], int]:
"""
Get orders for vendor with filtering.
@@ -306,7 +308,7 @@ class OrderService:
customer_id: int,
skip: int = 0,
limit: int = 100,
) -> Tuple[List[Order], int]:
) -> tuple[list[Order], int]:
"""Get orders for a specific customer."""
return self.get_vendor_orders(
db=db, vendor_id=vendor_id, skip=skip, limit=limit, customer_id=customer_id
@@ -335,7 +337,7 @@ class OrderService:
order.status = order_update.status
# Update timestamp based on status
now = datetime.now(timezone.utc)
now = datetime.now(UTC)
if order_update.status == "shipped" and not order.shipped_at:
order.shipped_at = now
elif order_update.status == "delivered" and not order.delivered_at:
@@ -351,7 +353,7 @@ class OrderService:
if order_update.internal_notes:
order.internal_notes = order_update.internal_notes
order.updated_at = datetime.now(timezone.utc)
order.updated_at = datetime.now(UTC)
db.commit()
db.refresh(order)

View File

@@ -9,13 +9,15 @@ This module provides:
"""
import logging
from datetime import datetime, timezone
from typing import List, Optional, Tuple
from datetime import UTC, datetime
from sqlalchemy.orm import Session
from app.exceptions import (ProductAlreadyExistsException,
ProductNotFoundException, ValidationException)
from app.exceptions import (
ProductAlreadyExistsException,
ProductNotFoundException,
ValidationException,
)
from models.database.marketplace_product import MarketplaceProduct
from models.database.product import Product
from models.schema.product import ProductCreate, ProductUpdate
@@ -106,7 +108,7 @@ class ProductService:
if existing:
raise ProductAlreadyExistsException(
f"Product already exists in catalog"
"Product already exists in catalog"
)
# Create product
@@ -167,7 +169,7 @@ class ProductService:
for key, value in update_data.items():
setattr(product, key, value)
product.updated_at = datetime.now(timezone.utc)
product.updated_at = datetime.now(UTC)
db.commit()
db.refresh(product)
@@ -216,9 +218,9 @@ class ProductService:
vendor_id: int,
skip: int = 0,
limit: int = 100,
is_active: Optional[bool] = None,
is_featured: Optional[bool] = None,
) -> Tuple[List[Product], int]:
is_active: bool | None = None,
is_featured: bool | None = None,
) -> tuple[list[Product], int]:
"""
Get products in vendor catalog with filtering.

View File

@@ -11,7 +11,7 @@ This module provides:
import logging
from datetime import datetime, timedelta
from typing import Any, Dict, List
from typing import Any
from sqlalchemy import func
from sqlalchemy.orm import Session
@@ -36,7 +36,7 @@ class StatsService:
# VENDOR-SPECIFIC STATISTICS
# ========================================================================
def get_vendor_stats(self, db: Session, vendor_id: int) -> Dict[str, Any]:
def get_vendor_stats(self, db: Session, vendor_id: int) -> dict[str, Any]:
"""
Get statistics for a specific vendor.
@@ -177,7 +177,7 @@ class StatsService:
def get_vendor_analytics(
self, db: Session, vendor_id: int, period: str = "30d"
) -> Dict[str, Any]:
) -> dict[str, Any]:
"""
Get a specific vendor analytics for a time period.
@@ -283,7 +283,7 @@ class StatsService:
# SYSTEM-WIDE STATISTICS (ADMIN)
# ========================================================================
def get_comprehensive_stats(self, db: Session) -> Dict[str, Any]:
def get_comprehensive_stats(self, db: Session) -> dict[str, Any]:
"""
Get comprehensive system statistics for admin dashboard.
@@ -333,7 +333,7 @@ class StatsService:
reason=f"Database query failed: {str(e)}",
)
def get_marketplace_breakdown_stats(self, db: Session) -> List[Dict[str, Any]]:
def get_marketplace_breakdown_stats(self, db: Session) -> list[dict[str, Any]]:
"""
Get statistics broken down by marketplace.
@@ -382,7 +382,7 @@ class StatsService:
reason=f"Database query failed: {str(e)}",
)
def get_user_statistics(self, db: Session) -> Dict[str, Any]:
def get_user_statistics(self, db: Session) -> dict[str, Any]:
"""
Get user statistics for admin dashboard.
@@ -416,7 +416,7 @@ class StatsService:
operation="get_user_statistics", reason="Database query failed"
)
def get_import_statistics(self, db: Session) -> Dict[str, Any]:
def get_import_statistics(self, db: Session) -> dict[str, Any]:
"""
Get import job statistics.
@@ -457,7 +457,7 @@ class StatsService:
"success_rate": 0,
}
def get_order_statistics(self, db: Session) -> Dict[str, Any]:
def get_order_statistics(self, db: Session) -> dict[str, Any]:
"""
Get order statistics.
@@ -472,7 +472,7 @@ class StatsService:
"""
return {"total_orders": 0, "pending_orders": 0, "completed_orders": 0}
def get_product_statistics(self, db: Session) -> Dict[str, Any]:
def get_product_statistics(self, db: Session) -> dict[str, Any]:
"""
Get product statistics.
@@ -548,7 +548,7 @@ class StatsService:
.count()
)
def _get_inventory_statistics(self, db: Session) -> Dict[str, int]:
def _get_inventory_statistics(self, db: Session) -> dict[str, int]:
"""
Get inventory-related statistics.

View File

@@ -9,13 +9,12 @@ This module provides:
"""
import logging
from datetime import datetime, timezone
from typing import Any, Dict, List
from datetime import UTC, datetime
from typing import Any
from sqlalchemy.orm import Session
from app.exceptions import (UnauthorizedVendorAccessException,
ValidationException)
from app.exceptions import ValidationException
from models.database.user import User
from models.database.vendor import Role, VendorUser
@@ -27,7 +26,7 @@ class TeamService:
def get_team_members(
self, db: Session, vendor_id: int, current_user: User
) -> List[Dict[str, Any]]:
) -> list[dict[str, Any]]:
"""
Get all team members for vendor.
@@ -69,7 +68,7 @@ class TeamService:
def invite_team_member(
self, db: Session, vendor_id: int, invitation_data: dict, current_user: User
) -> Dict[str, Any]:
) -> dict[str, Any]:
"""
Invite a new team member.
@@ -102,7 +101,7 @@ class TeamService:
user_id: int,
update_data: dict,
current_user: User,
) -> Dict[str, Any]:
) -> dict[str, Any]:
"""
Update team member role or status.
@@ -135,7 +134,7 @@ class TeamService:
if "is_active" in update_data:
vendor_user.is_active = update_data["is_active"]
vendor_user.updated_at = datetime.now(timezone.utc)
vendor_user.updated_at = datetime.now(UTC)
db.commit()
db.refresh(vendor_user)
@@ -178,7 +177,7 @@ class TeamService:
# Soft delete
vendor_user.is_active = False
vendor_user.updated_at = datetime.now(timezone.utc)
vendor_user.updated_at = datetime.now(UTC)
db.commit()
logger.info(f"Removed user {user_id} from vendor {vendor_id}")
@@ -189,7 +188,7 @@ class TeamService:
logger.error(f"Error removing team member: {str(e)}")
raise ValidationException("Failed to remove team member")
def get_vendor_roles(self, db: Session, vendor_id: int) -> List[Dict[str, Any]]:
def get_vendor_roles(self, db: Session, vendor_id: int) -> list[dict[str, Any]]:
"""
Get available roles for vendor.

View File

@@ -12,25 +12,23 @@ This module provides classes and functions for:
import logging
import secrets
from datetime import datetime, timezone
from typing import List, Optional, Tuple
from datetime import UTC, datetime
from sqlalchemy import and_
from sqlalchemy.orm import Session
from app.exceptions import (DNSVerificationException,
DomainAlreadyVerifiedException,
DomainNotVerifiedException,
DomainVerificationFailedException,
InvalidDomainFormatException,
MaxDomainsReachedException,
MultiplePrimaryDomainsException,
ReservedDomainException,
UnauthorizedDomainAccessException,
ValidationException,
VendorDomainAlreadyExistsException,
VendorDomainNotFoundException,
VendorNotFoundException)
from app.exceptions import (
DNSVerificationException,
DomainAlreadyVerifiedException,
DomainNotVerifiedException,
DomainVerificationFailedException,
InvalidDomainFormatException,
MaxDomainsReachedException,
ReservedDomainException,
ValidationException,
VendorDomainAlreadyExistsException,
VendorDomainNotFoundException,
VendorNotFoundException,
)
from models.database.vendor import Vendor
from models.database.vendor_domain import VendorDomain
from models.schema.vendor_domain import VendorDomainCreate, VendorDomainUpdate
@@ -135,7 +133,7 @@ class VendorDomainService:
logger.error(f"Error adding domain: {str(e)}")
raise ValidationException("Failed to add domain")
def get_vendor_domains(self, db: Session, vendor_id: int) -> List[VendorDomain]:
def get_vendor_domains(self, db: Session, vendor_id: int) -> list[VendorDomain]:
"""
Get all domains for a vendor.
@@ -272,7 +270,7 @@ class VendorDomainService:
logger.error(f"Error deleting domain: {str(e)}")
raise ValidationException("Failed to delete domain")
def verify_domain(self, db: Session, domain_id: int) -> Tuple[VendorDomain, str]:
def verify_domain(self, db: Session, domain_id: int) -> tuple[VendorDomain, str]:
"""
Verify domain ownership via DNS TXT record.
@@ -313,7 +311,7 @@ class VendorDomainService:
if txt_value == domain.verification_token:
# Verification successful
domain.is_verified = True
domain.verified_at = datetime.now(timezone.utc)
domain.verified_at = datetime.now(UTC)
db.commit()
db.refresh(domain)
@@ -419,7 +417,7 @@ class VendorDomainService:
raise ReservedDomainException(domain, first_part)
def _unset_primary_domains(
self, db: Session, vendor_id: int, exclude_domain_id: Optional[int] = None
self, db: Session, vendor_id: int, exclude_domain_id: int | None = None
) -> None:
"""Unset all primary domains for vendor."""
query = db.query(VendorDomain).filter(

View File

@@ -10,18 +10,20 @@ This module provides classes and functions for:
"""
import logging
from typing import List, Optional, Tuple
from sqlalchemy import func
from sqlalchemy.orm import Session
from app.exceptions import (InvalidVendorDataException,
MarketplaceProductNotFoundException,
MaxVendorsReachedException,
ProductAlreadyExistsException,
UnauthorizedVendorAccessException,
ValidationException, VendorAlreadyExistsException,
VendorNotFoundException)
from app.exceptions import (
InvalidVendorDataException,
MarketplaceProductNotFoundException,
MaxVendorsReachedException,
ProductAlreadyExistsException,
UnauthorizedVendorAccessException,
ValidationException,
VendorAlreadyExistsException,
VendorNotFoundException,
)
from models.database.marketplace_product import MarketplaceProduct
from models.database.product import Product
from models.database.user import User
@@ -108,7 +110,7 @@ class VendorService:
limit: int = 100,
active_only: bool = True,
verified_only: bool = False,
) -> Tuple[List[Vendor], int]:
) -> tuple[list[Vendor], int]:
"""
Get vendors with filtering.
@@ -257,7 +259,7 @@ class VendorService:
limit: int = 100,
active_only: bool = True,
featured_only: bool = False,
) -> Tuple[List[Product], int]:
) -> tuple[list[Product], int]:
"""
Get products in vendor catalog with filtering.

View File

@@ -8,20 +8,23 @@ Handles:
- Role assignment
- Permission management
"""
import logging
import secrets
from datetime import datetime, timedelta
from typing import Any, Dict, List, Optional
from typing import Any
from sqlalchemy.orm import Session
from app.core.permissions import get_preset_permissions
from app.exceptions import (CannotRemoveOwnerException,
InvalidInvitationTokenException,
MaxTeamMembersReachedException,
TeamInvitationAlreadyAcceptedException,
TeamMemberAlreadyExistsException,
UserNotFoundException, VendorNotFoundException)
from app.exceptions import (
CannotRemoveOwnerException,
InvalidInvitationTokenException,
MaxTeamMembersReachedException,
TeamInvitationAlreadyAcceptedException,
TeamMemberAlreadyExistsException,
UserNotFoundException,
)
from middleware.auth import AuthManager
from models.database.user import User
from models.database.vendor import Role, Vendor, VendorUser, VendorUserType
@@ -43,8 +46,8 @@ class VendorTeamService:
inviter: User,
email: str,
role_name: str,
custom_permissions: Optional[List[str]] = None,
) -> Dict[str, Any]:
custom_permissions: list[str] | None = None,
) -> dict[str, Any]:
"""
Invite a new team member to a vendor.
@@ -196,9 +199,9 @@ class VendorTeamService:
db: Session,
invitation_token: str,
password: str,
first_name: Optional[str] = None,
last_name: Optional[str] = None,
) -> Dict[str, Any]:
first_name: str | None = None,
last_name: str | None = None,
) -> dict[str, Any]:
"""
Accept a team invitation and activate account.
@@ -330,7 +333,7 @@ class VendorTeamService:
vendor: Vendor,
user_id: int,
new_role_name: str,
custom_permissions: Optional[List[str]] = None,
custom_permissions: list[str] | None = None,
) -> VendorUser:
"""
Update a team member's role.
@@ -392,7 +395,7 @@ class VendorTeamService:
db: Session,
vendor: Vendor,
include_inactive: bool = False,
) -> List[Dict[str, Any]]:
) -> list[dict[str, Any]]:
"""
Get all team members for a vendor.
@@ -445,7 +448,7 @@ class VendorTeamService:
db: Session,
vendor: Vendor,
role_name: str,
custom_permissions: Optional[List[str]] = None,
custom_permissions: list[str] | None = None,
) -> Role:
"""Get existing role or create new one with preset/custom permissions."""
# Try to find existing role with same name
@@ -492,7 +495,6 @@ class VendorTeamService:
# - Vendor name
# - Inviter name
# - Expiry date
pass
# Create service instance

View File

@@ -8,21 +8,24 @@ Handles theme CRUD operations, preset application, and validation.
import logging
import re
from typing import Dict, List, Optional
from sqlalchemy.orm import Session
from app.core.theme_presets import (THEME_PRESETS, apply_preset,
get_available_presets, get_preset_preview)
from app.core.theme_presets import (
THEME_PRESETS,
apply_preset,
get_available_presets,
get_preset_preview,
)
from app.exceptions.vendor import VendorNotFoundException
from app.exceptions.vendor_theme import (InvalidColorFormatException,
InvalidFontFamilyException,
InvalidThemeDataException,
ThemeOperationException,
ThemePresetAlreadyAppliedException,
ThemePresetNotFoundException,
ThemeValidationException,
VendorThemeNotFoundException)
from app.exceptions.vendor_theme import (
InvalidColorFormatException,
InvalidFontFamilyException,
ThemeOperationException,
ThemePresetNotFoundException,
ThemeValidationException,
VendorThemeNotFoundException,
)
from models.database.vendor import Vendor
from models.database.vendor_theme import VendorTheme
from models.schema.vendor_theme import ThemePresetPreview, VendorThemeUpdate
@@ -77,7 +80,7 @@ class VendorThemeService:
# THEME RETRIEVAL
# ============================================================================
def get_theme(self, db: Session, vendor_code: str) -> Dict:
def get_theme(self, db: Session, vendor_code: str) -> dict:
"""
Get theme for vendor. Returns default if no custom theme exists.
@@ -107,7 +110,7 @@ class VendorThemeService:
return theme.to_dict()
def _get_default_theme(self) -> Dict:
def _get_default_theme(self) -> dict:
"""
Get default theme configuration.
@@ -329,7 +332,7 @@ class VendorThemeService:
operation="apply_preset", vendor_code=vendor_code, reason=str(e)
)
def get_available_presets(self) -> List[ThemePresetPreview]:
def get_available_presets(self) -> list[ThemePresetPreview]:
"""
Get list of available theme presets.
@@ -351,7 +354,7 @@ class VendorThemeService:
# THEME DELETION
# ============================================================================
def delete_theme(self, db: Session, vendor_code: str) -> Dict:
def delete_theme(self, db: Session, vendor_code: str) -> dict:
"""
Delete custom theme for vendor (reverts to default).

View File

@@ -1,6 +1,6 @@
# app/tasks/background_tasks.py
import logging
from datetime import datetime, timezone
from datetime import UTC, datetime
from app.core.database import SessionLocal
from app.utils.csv_processor import CSVProcessor
@@ -39,13 +39,13 @@ async def process_marketplace_import(
logger.error(f"Vendor {vendor_id} not found for import job {job_id}")
job.status = "failed"
job.error_message = f"Vendor {vendor_id} not found"
job.completed_at = datetime.now(timezone.utc)
job.completed_at = datetime.now(UTC)
db.commit()
return
# Update job status
job.status = "processing"
job.started_at = datetime.now(timezone.utc)
job.started_at = datetime.now(UTC)
db.commit()
logger.info(
@@ -64,7 +64,7 @@ async def process_marketplace_import(
# Update job with results
job.status = "completed"
job.completed_at = datetime.now(timezone.utc)
job.completed_at = datetime.now(UTC)
job.imported_count = result["imported"]
job.updated_count = result["updated"]
job.error_count = result.get("errors", 0)
@@ -87,13 +87,13 @@ async def process_marketplace_import(
try:
job.status = "failed"
job.error_message = str(e)
job.completed_at = datetime.now(timezone.utc)
job.completed_at = datetime.now(UTC)
db.commit()
except Exception as commit_error:
logger.error(f"Failed to update job status: {commit_error}")
db.rollback()
finally:
if hasattr(db, "close") and callable(getattr(db, "close")):
if hasattr(db, "close") and callable(db.close):
try:
db.close()
except Exception as close_error:

View File

@@ -8,9 +8,9 @@ This module provides classes and functions for:
"""
import logging
from datetime import datetime, timezone
from datetime import UTC, datetime
from io import StringIO
from typing import Any, Dict
from typing import Any
import pandas as pd
import requests
@@ -145,7 +145,7 @@ class CSVProcessor:
logger.info(f"Normalized columns: {list(df.columns)}")
return df
def _clean_row_data(self, row_data: Dict[str, Any]) -> Dict[str, Any]:
def _clean_row_data(self, row_data: dict[str, Any]) -> dict[str, Any]:
"""Process a single row with data normalization."""
# Handle NaN values
processed_data = {k: (v if pd.notna(v) else None) for k, v in row_data.items()}
@@ -188,7 +188,7 @@ class CSVProcessor:
async def process_marketplace_csv_from_url(
self, url: str, marketplace: str, vendor_name: str, batch_size: int, db: Session
) -> Dict[str, Any]:
) -> dict[str, Any]:
"""
Process CSV from URL with marketplace and vendor information.
@@ -245,7 +245,7 @@ class CSVProcessor:
vendor_name: str,
db: Session,
batch_num: int,
) -> Dict[str, int]:
) -> dict[str, int]:
"""Process a batch of CSV rows with marketplace information."""
imported = 0
updated = 0
@@ -295,7 +295,7 @@ class CSVProcessor:
existing_product, key
):
setattr(existing_product, key, value)
existing_product.updated_at = datetime.now(timezone.utc)
existing_product.updated_at = datetime.now(UTC)
updated += 1
logger.debug(
f"Updated product {product_data['marketplace_product_id']} for "

View File

@@ -9,7 +9,6 @@ This module provides classes and functions for:
import logging
import re
from typing import Optional, Tuple
import pandas as pd
@@ -21,7 +20,7 @@ class GTINProcessor:
VALID_LENGTHS = [8, 12, 13, 14] # List of valid GTIN lengths
def normalize(self, gtin_value: any) -> Optional[str]:
def normalize(self, gtin_value: any) -> str | None:
"""
Normalize GTIN to proper format.
@@ -55,11 +54,11 @@ class GTINProcessor:
# Standard lengths - pad appropriately
if length == 8:
return gtin_clean.zfill(8) # EAN-8
elif length == 12:
if length == 12:
return gtin_clean.zfill(12) # UPC-A
elif length == 13:
if length == 13:
return gtin_clean.zfill(13) # EAN-13
elif length == 14:
if length == 14:
return gtin_clean.zfill(14) # GTIN-14
elif length > 14:
@@ -111,7 +110,7 @@ class PriceProcessor:
def parse_price_currency(
self, price_str: any
) -> Tuple[Optional[str], Optional[str]]:
) -> tuple[str | None, str | None]:
"""
Parse a price string to extract the numeric value and currency.

View File

@@ -40,7 +40,7 @@ def get_db_engine(database_url: str):
echo=False,
)
logger.info(f"Database engine created for: " f"{database_url.split('@')[0]}@...")
logger.info(f"Database engine created for: {database_url.split('@')[0]}@...")
return engine