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

@@ -2,8 +2,16 @@
"""API models package - Pydantic models for request/response validation."""
# Import API model modules
from . import (auth, base, inventory, marketplace_import_job,
marketplace_product, stats, vendor)
from . import (
auth,
base,
inventory,
marketplace_import_job,
marketplace_product,
stats,
vendor,
)
# Common imports for convenience
from .base import * # Base Pydantic models

View File

@@ -12,7 +12,7 @@ This module provides schemas for:
"""
from datetime import datetime
from typing import Any, Dict, List, Optional
from typing import Any
from pydantic import BaseModel, Field, field_validator
@@ -26,14 +26,14 @@ class AdminAuditLogResponse(BaseModel):
id: int
admin_user_id: int
admin_username: Optional[str] = None
admin_username: str | None = None
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
created_at: datetime
model_config = {"from_attributes": True}
@@ -42,11 +42,11 @@ class AdminAuditLogResponse(BaseModel):
class AdminAuditLogFilters(BaseModel):
"""Filters for querying audit logs."""
admin_user_id: Optional[int] = None
action: Optional[str] = None
target_type: Optional[str] = None
date_from: Optional[datetime] = None
date_to: Optional[datetime] = None
admin_user_id: int | None = None
action: str | None = None
target_type: str | None = None
date_from: datetime | None = None
date_to: datetime | None = None
skip: int = Field(0, ge=0)
limit: int = Field(100, ge=1, le=1000)
@@ -54,7 +54,7 @@ class AdminAuditLogFilters(BaseModel):
class AdminAuditLogListResponse(BaseModel):
"""Paginated list of audit logs."""
logs: List[AdminAuditLogResponse]
logs: list[AdminAuditLogResponse]
total: int
skip: int
limit: int
@@ -73,8 +73,8 @@ class AdminNotificationCreate(BaseModel):
title: str = Field(..., max_length=200)
message: str = Field(..., description="Notification message")
action_required: bool = Field(default=False)
action_url: Optional[str] = Field(None, max_length=500)
metadata: Optional[Dict[str, Any]] = None
action_url: str | None = Field(None, max_length=500)
metadata: dict[str, Any] | None = None
@field_validator("priority")
@classmethod
@@ -94,11 +94,11 @@ class AdminNotificationResponse(BaseModel):
title: str
message: str
is_read: bool
read_at: Optional[datetime] = None
read_by_user_id: Optional[int] = None
read_at: datetime | None = None
read_by_user_id: int | None = None
action_required: bool
action_url: Optional[str] = None
metadata: Optional[Dict[str, Any]] = None
action_url: str | None = None
metadata: dict[str, Any] | None = None
created_at: datetime
model_config = {"from_attributes": True}
@@ -113,7 +113,7 @@ class AdminNotificationUpdate(BaseModel):
class AdminNotificationListResponse(BaseModel):
"""Paginated list of notifications."""
notifications: List[AdminNotificationResponse]
notifications: list[AdminNotificationResponse]
total: int
unread_count: int
skip: int
@@ -131,8 +131,8 @@ class AdminSettingCreate(BaseModel):
key: str = Field(..., max_length=100, description="Unique setting key")
value: str = Field(..., description="Setting value")
value_type: str = Field(default="string", description="Data type")
category: Optional[str] = Field(None, max_length=50)
description: Optional[str] = None
category: str | None = Field(None, max_length=50)
description: str | None = None
is_encrypted: bool = Field(default=False)
is_public: bool = Field(default=False, description="Can be exposed to frontend")
@@ -162,11 +162,11 @@ class AdminSettingResponse(BaseModel):
key: str
value: str
value_type: str
category: Optional[str] = None
description: Optional[str] = None
category: str | None = None
description: str | None = None
is_encrypted: bool
is_public: bool
last_modified_by_user_id: Optional[int] = None
last_modified_by_user_id: int | None = None
updated_at: datetime
model_config = {"from_attributes": True}
@@ -176,15 +176,15 @@ class AdminSettingUpdate(BaseModel):
"""Update admin setting value."""
value: str
description: Optional[str] = None
description: str | None = None
class AdminSettingListResponse(BaseModel):
"""List of settings by category."""
settings: List[AdminSettingResponse]
settings: list[AdminSettingResponse]
total: int
category: Optional[str] = None
category: str | None = None
# ============================================================================
@@ -198,9 +198,9 @@ class PlatformAlertCreate(BaseModel):
alert_type: str = Field(..., max_length=50)
severity: str = Field(..., description="Alert severity")
title: str = Field(..., max_length=200)
description: Optional[str] = None
affected_vendors: Optional[List[int]] = None
affected_systems: Optional[List[str]] = None
description: str | None = None
affected_vendors: list[int] | None = None
affected_systems: list[str] | None = None
auto_generated: bool = Field(default=True)
@field_validator("severity")
@@ -234,13 +234,13 @@ class PlatformAlertResponse(BaseModel):
alert_type: str
severity: str
title: str
description: Optional[str] = None
affected_vendors: Optional[List[int]] = None
affected_systems: Optional[List[str]] = None
description: str | None = None
affected_vendors: list[int] | None = None
affected_systems: list[str] | None = None
is_resolved: bool
resolved_at: Optional[datetime] = None
resolved_by_user_id: Optional[int] = None
resolution_notes: Optional[str] = None
resolved_at: datetime | None = None
resolved_by_user_id: int | None = None
resolution_notes: str | None = None
auto_generated: bool
occurrence_count: int
first_occurred_at: datetime
@@ -254,13 +254,13 @@ class PlatformAlertResolve(BaseModel):
"""Resolve platform alert."""
is_resolved: bool = True
resolution_notes: Optional[str] = None
resolution_notes: str | None = None
class PlatformAlertListResponse(BaseModel):
"""Paginated list of platform alerts."""
alerts: List[PlatformAlertResponse]
alerts: list[PlatformAlertResponse]
total: int
active_count: int
critical_count: int
@@ -276,10 +276,10 @@ class PlatformAlertListResponse(BaseModel):
class BulkVendorAction(BaseModel):
"""Bulk actions on vendors."""
vendor_ids: List[int] = Field(..., min_length=1, max_length=100)
vendor_ids: list[int] = Field(..., min_length=1, max_length=100)
action: str = Field(..., description="Action to perform")
confirm: bool = Field(default=False, description="Required for destructive actions")
reason: Optional[str] = Field(None, description="Reason for bulk action")
reason: str | None = Field(None, description="Reason for bulk action")
@field_validator("action")
@classmethod
@@ -293,8 +293,8 @@ class BulkVendorAction(BaseModel):
class BulkVendorActionResponse(BaseModel):
"""Response for bulk vendor actions."""
successful: List[int]
failed: Dict[int, str] # vendor_id -> error_message
successful: list[int]
failed: dict[int, str] # vendor_id -> error_message
total_processed: int
action_performed: str
message: str
@@ -303,10 +303,10 @@ class BulkVendorActionResponse(BaseModel):
class BulkUserAction(BaseModel):
"""Bulk actions on users."""
user_ids: List[int] = Field(..., min_length=1, max_length=100)
user_ids: list[int] = Field(..., min_length=1, max_length=100)
action: str = Field(..., description="Action to perform")
confirm: bool = Field(default=False)
reason: Optional[str] = None
reason: str | None = None
@field_validator("action")
@classmethod
@@ -320,8 +320,8 @@ class BulkUserAction(BaseModel):
class BulkUserActionResponse(BaseModel):
"""Response for bulk user actions."""
successful: List[int]
failed: Dict[int, str]
successful: list[int]
failed: dict[int, str]
total_processed: int
action_performed: str
message: str
@@ -335,14 +335,14 @@ class BulkUserActionResponse(BaseModel):
class AdminDashboardStats(BaseModel):
"""Comprehensive admin dashboard statistics."""
platform: Dict[str, Any]
users: Dict[str, Any]
vendors: Dict[str, Any]
products: Dict[str, Any]
orders: Dict[str, Any]
imports: Dict[str, Any]
recent_vendors: List[Dict[str, Any]]
recent_imports: List[Dict[str, Any]]
platform: dict[str, Any]
users: dict[str, Any]
vendors: dict[str, Any]
products: dict[str, Any]
orders: dict[str, Any]
imports: dict[str, Any]
recent_vendors: list[dict[str, Any]]
recent_imports: list[dict[str, Any]]
unread_notifications: int
active_alerts: int
critical_alerts: int
@@ -357,10 +357,10 @@ class ComponentHealthStatus(BaseModel):
"""Health status for a system component."""
status: str # healthy, degraded, unhealthy
response_time_ms: Optional[float] = None
error_message: Optional[str] = None
response_time_ms: float | None = None
error_message: str | None = None
last_checked: datetime
details: Optional[Dict[str, Any]] = None
details: dict[str, Any] | None = None
class SystemHealthResponse(BaseModel):
@@ -386,14 +386,14 @@ class AdminSessionResponse(BaseModel):
id: int
admin_user_id: int
admin_username: Optional[str] = None
admin_username: str | None = None
ip_address: str
user_agent: Optional[str] = None
user_agent: str | None = None
login_at: datetime
last_activity_at: datetime
logout_at: Optional[datetime] = None
logout_at: datetime | None = None
is_active: bool
logout_reason: Optional[str] = None
logout_reason: str | None = None
model_config = {"from_attributes": True}
@@ -401,6 +401,6 @@ class AdminSessionResponse(BaseModel):
class AdminSessionListResponse(BaseModel):
"""List of admin sessions."""
sessions: List[AdminSessionResponse]
sessions: list[AdminSessionResponse]
total: int
active_count: int

View File

@@ -1,7 +1,6 @@
# auth.py - Keep security-critical validation
import re
from datetime import datetime
from typing import Optional
from pydantic import BaseModel, ConfigDict, EmailStr, Field, field_validator
@@ -33,7 +32,7 @@ class UserRegister(BaseModel):
class UserLogin(BaseModel):
email_or_username: str = Field(..., description="Username or email address")
password: str = Field(..., description="Password")
vendor_code: Optional[str] = Field(
vendor_code: str | None = Field(
None, description="Optional vendor code for context"
)
@@ -50,7 +49,7 @@ class UserResponse(BaseModel):
username: str
role: str
is_active: bool
last_login: Optional[datetime] = None
last_login: datetime | None = None
created_at: datetime
updated_at: datetime

View File

@@ -1,4 +1,4 @@
from typing import Generic, List, TypeVar
from typing import Generic, TypeVar
from pydantic import BaseModel
@@ -8,7 +8,7 @@ T = TypeVar("T")
class ListResponse(BaseModel, Generic[T]):
"""Generic list response model"""
items: List[T]
items: list[T]
total: int
skip: int
limit: int

View File

@@ -3,8 +3,6 @@
Pydantic schemas for shopping cart operations.
"""
from datetime import datetime
from typing import List, Optional
from pydantic import BaseModel, ConfigDict, Field
@@ -43,7 +41,7 @@ class CartItemResponse(BaseModel):
line_total: float = Field(
..., description="Total price for this line (price * quantity)"
)
image_url: Optional[str] = Field(None, description="Product image URL")
image_url: str | None = Field(None, description="Product image URL")
class CartResponse(BaseModel):
@@ -51,7 +49,7 @@ class CartResponse(BaseModel):
vendor_id: int = Field(..., description="Vendor ID")
session_id: str = Field(..., description="Shopping session ID")
items: List[CartItemResponse] = Field(
items: list[CartItemResponse] = Field(
default_factory=list, description="Cart items"
)
subtotal: float = Field(..., description="Subtotal of all items")
@@ -82,7 +80,7 @@ class CartOperationResponse(BaseModel):
message: str = Field(..., description="Operation result message")
product_id: int = Field(..., description="Product ID affected")
quantity: Optional[int] = Field(
quantity: int | None = Field(
None, description="New quantity (for add/update operations)"
)

View File

@@ -5,7 +5,6 @@ Pydantic schema for customer-related operations.
from datetime import datetime
from decimal import Decimal
from typing import Any, Dict, List, Optional
from pydantic import BaseModel, EmailStr, Field, field_validator
@@ -23,7 +22,7 @@ class CustomerRegister(BaseModel):
)
first_name: str = Field(..., min_length=1, max_length=100)
last_name: str = Field(..., min_length=1, max_length=100)
phone: Optional[str] = Field(None, max_length=50)
phone: str | None = Field(None, max_length=50)
marketing_consent: bool = Field(default=False)
@field_validator("email")
@@ -48,15 +47,15 @@ class CustomerRegister(BaseModel):
class CustomerUpdate(BaseModel):
"""Schema for updating customer profile."""
email: Optional[EmailStr] = None
first_name: Optional[str] = Field(None, min_length=1, max_length=100)
last_name: Optional[str] = Field(None, min_length=1, max_length=100)
phone: Optional[str] = Field(None, max_length=50)
marketing_consent: Optional[bool] = None
email: EmailStr | None = None
first_name: str | None = Field(None, min_length=1, max_length=100)
last_name: str | None = Field(None, min_length=1, max_length=100)
phone: str | None = Field(None, max_length=50)
marketing_consent: bool | None = None
@field_validator("email")
@classmethod
def email_lowercase(cls, v: Optional[str]) -> Optional[str]:
def email_lowercase(cls, v: str | None) -> str | None:
"""Convert email to lowercase."""
return v.lower() if v else None
@@ -72,12 +71,12 @@ class CustomerResponse(BaseModel):
id: int
vendor_id: int
email: str
first_name: Optional[str]
last_name: Optional[str]
phone: Optional[str]
first_name: str | None
last_name: str | None
phone: str | None
customer_number: str
marketing_consent: bool
last_order_date: Optional[datetime]
last_order_date: datetime | None
total_orders: int
total_spent: Decimal
is_active: bool
@@ -97,7 +96,7 @@ class CustomerResponse(BaseModel):
class CustomerListResponse(BaseModel):
"""Schema for paginated customer list."""
customers: List[CustomerResponse]
customers: list[CustomerResponse]
total: int
page: int
per_page: int
@@ -115,9 +114,9 @@ class CustomerAddressCreate(BaseModel):
address_type: str = Field(..., pattern="^(billing|shipping)$")
first_name: str = Field(..., min_length=1, max_length=100)
last_name: str = Field(..., min_length=1, max_length=100)
company: Optional[str] = Field(None, max_length=200)
company: str | None = Field(None, max_length=200)
address_line_1: str = Field(..., min_length=1, max_length=255)
address_line_2: Optional[str] = Field(None, max_length=255)
address_line_2: str | None = Field(None, max_length=255)
city: str = Field(..., min_length=1, max_length=100)
postal_code: str = Field(..., min_length=1, max_length=20)
country: str = Field(..., min_length=2, max_length=100)
@@ -127,16 +126,16 @@ class CustomerAddressCreate(BaseModel):
class CustomerAddressUpdate(BaseModel):
"""Schema for updating customer address."""
address_type: Optional[str] = Field(None, pattern="^(billing|shipping)$")
first_name: Optional[str] = Field(None, min_length=1, max_length=100)
last_name: Optional[str] = Field(None, min_length=1, max_length=100)
company: Optional[str] = Field(None, max_length=200)
address_line_1: Optional[str] = Field(None, min_length=1, max_length=255)
address_line_2: Optional[str] = Field(None, max_length=255)
city: Optional[str] = Field(None, min_length=1, max_length=100)
postal_code: Optional[str] = Field(None, min_length=1, max_length=20)
country: Optional[str] = Field(None, min_length=2, max_length=100)
is_default: Optional[bool] = None
address_type: str | None = Field(None, pattern="^(billing|shipping)$")
first_name: str | None = Field(None, min_length=1, max_length=100)
last_name: str | None = Field(None, min_length=1, max_length=100)
company: str | None = Field(None, max_length=200)
address_line_1: str | None = Field(None, min_length=1, max_length=255)
address_line_2: str | None = Field(None, max_length=255)
city: str | None = Field(None, min_length=1, max_length=100)
postal_code: str | None = Field(None, min_length=1, max_length=20)
country: str | None = Field(None, min_length=2, max_length=100)
is_default: bool | None = None
class CustomerAddressResponse(BaseModel):
@@ -148,9 +147,9 @@ class CustomerAddressResponse(BaseModel):
address_type: str
first_name: str
last_name: str
company: Optional[str]
company: str | None
address_line_1: str
address_line_2: Optional[str]
address_line_2: str | None
city: str
postal_code: str
country: str
@@ -169,7 +168,7 @@ class CustomerAddressResponse(BaseModel):
class CustomerPreferencesUpdate(BaseModel):
"""Schema for updating customer preferences."""
marketing_consent: Optional[bool] = None
language: Optional[str] = Field(None, max_length=10)
currency: Optional[str] = Field(None, max_length=3)
notification_preferences: Optional[Dict[str, bool]] = None
marketing_consent: bool | None = None
language: str | None = Field(None, max_length=10)
currency: str | None = Field(None, max_length=3)
notification_preferences: dict[str, bool] | None = None

View File

@@ -1,6 +1,5 @@
# models/schema/inventory.py
from datetime import datetime
from typing import List, Optional
from pydantic import BaseModel, ConfigDict, Field
@@ -27,9 +26,9 @@ class InventoryAdjust(InventoryBase):
class InventoryUpdate(BaseModel):
"""Update inventory fields."""
quantity: Optional[int] = Field(None, ge=0)
reserved_quantity: Optional[int] = Field(None, ge=0)
location: Optional[str] = None
quantity: int | None = Field(None, ge=0)
reserved_quantity: int | None = Field(None, ge=0)
location: str | None = None
class InventoryReserve(BaseModel):
@@ -49,7 +48,7 @@ class InventoryResponse(BaseModel):
location: str
quantity: int
reserved_quantity: int
gtin: Optional[str]
gtin: str | None
created_at: datetime
updated_at: datetime
@@ -70,16 +69,16 @@ class ProductInventorySummary(BaseModel):
product_id: int
vendor_id: int
product_sku: Optional[str]
product_sku: str | None
product_title: str
total_quantity: int
total_reserved: int
total_available: int
locations: List[InventoryLocationResponse]
locations: list[InventoryLocationResponse]
class InventoryListResponse(BaseModel):
inventories: List[InventoryResponse]
inventories: list[InventoryResponse]
total: int
skip: int
limit: int

View File

@@ -1,5 +1,4 @@
from datetime import datetime
from typing import Optional
from pydantic import BaseModel, ConfigDict, Field, field_validator
@@ -12,7 +11,7 @@ class MarketplaceImportJobRequest(BaseModel):
source_url: str = Field(..., description="URL to CSV file from marketplace")
marketplace: str = Field(default="Letzshop", description="Marketplace name")
batch_size: Optional[int] = Field(
batch_size: int | None = Field(
1000, description="Processing batch size", ge=100, le=10000
)
@@ -50,12 +49,12 @@ class MarketplaceImportJobResponse(BaseModel):
error_count: int = 0
# Error details
error_message: Optional[str] = None
error_message: str | None = None
# Timestamps
created_at: datetime
started_at: Optional[datetime] = None
completed_at: Optional[datetime] = None
started_at: datetime | None = None
completed_at: datetime | None = None
class MarketplaceImportJobListResponse(BaseModel):
@@ -71,8 +70,8 @@ class MarketplaceImportJobStatusUpdate(BaseModel):
"""Schema for updating import job status (internal use)."""
status: str
imported_count: Optional[int] = None
updated_count: Optional[int] = None
error_count: Optional[int] = None
total_processed: Optional[int] = None
error_message: Optional[str] = None
imported_count: int | None = None
updated_count: int | None = None
error_count: int | None = None
total_processed: int | None = None
error_message: str | None = None

View File

@@ -1,6 +1,5 @@
# models/schema/marketplace_products.py - Simplified validation
from datetime import datetime
from typing import List, Optional
from pydantic import BaseModel, ConfigDict, Field
@@ -8,45 +7,45 @@ from models.schema.inventory import ProductInventorySummary
class MarketplaceProductBase(BaseModel):
marketplace_product_id: Optional[str] = None
title: Optional[str] = None
description: Optional[str] = None
link: Optional[str] = None
image_link: Optional[str] = None
availability: Optional[str] = None
price: Optional[str] = None
brand: Optional[str] = None
gtin: Optional[str] = None
mpn: Optional[str] = None
condition: Optional[str] = None
adult: Optional[str] = None
multipack: Optional[int] = None
is_bundle: Optional[str] = None
age_group: Optional[str] = None
color: Optional[str] = None
gender: Optional[str] = None
material: Optional[str] = None
pattern: Optional[str] = None
size: Optional[str] = None
size_type: Optional[str] = None
size_system: Optional[str] = None
item_group_id: Optional[str] = None
google_product_category: Optional[str] = None
product_type: Optional[str] = None
custom_label_0: Optional[str] = None
custom_label_1: Optional[str] = None
custom_label_2: Optional[str] = None
custom_label_3: Optional[str] = None
custom_label_4: Optional[str] = None
additional_image_link: Optional[str] = None
sale_price: Optional[str] = None
unit_pricing_measure: Optional[str] = None
unit_pricing_base_measure: Optional[str] = None
identifier_exists: Optional[str] = None
shipping: Optional[str] = None
currency: Optional[str] = None
marketplace: Optional[str] = None
vendor_name: Optional[str] = None
marketplace_product_id: str | None = None
title: str | None = None
description: str | None = None
link: str | None = None
image_link: str | None = None
availability: str | None = None
price: str | None = None
brand: str | None = None
gtin: str | None = None
mpn: str | None = None
condition: str | None = None
adult: str | None = None
multipack: int | None = None
is_bundle: str | None = None
age_group: str | None = None
color: str | None = None
gender: str | None = None
material: str | None = None
pattern: str | None = None
size: str | None = None
size_type: str | None = None
size_system: str | None = None
item_group_id: str | None = None
google_product_category: str | None = None
product_type: str | None = None
custom_label_0: str | None = None
custom_label_1: str | None = None
custom_label_2: str | None = None
custom_label_3: str | None = None
custom_label_4: str | None = None
additional_image_link: str | None = None
sale_price: str | None = None
unit_pricing_measure: str | None = None
unit_pricing_base_measure: str | None = None
identifier_exists: str | None = None
shipping: str | None = None
currency: str | None = None
marketplace: str | None = None
vendor_name: str | None = None
class MarketplaceProductCreate(MarketplaceProductBase):
@@ -70,7 +69,7 @@ class MarketplaceProductResponse(MarketplaceProductBase):
class MarketplaceProductListResponse(BaseModel):
products: List[MarketplaceProductResponse]
products: list[MarketplaceProductResponse]
total: int
skip: int
limit: int
@@ -78,4 +77,4 @@ class MarketplaceProductListResponse(BaseModel):
class MarketplaceProductDetailResponse(BaseModel):
product: MarketplaceProductResponse
inventory_info: Optional[ProductInventorySummary] = None
inventory_info: ProductInventorySummary | None = None

View File

@@ -4,8 +4,6 @@ Pydantic schema for order operations.
"""
from datetime import datetime
from decimal import Decimal
from typing import List, Optional
from pydantic import BaseModel, ConfigDict, Field
@@ -30,7 +28,7 @@ class OrderItemResponse(BaseModel):
order_id: int
product_id: int
product_name: str
product_sku: Optional[str]
product_sku: str | None
quantity: int
unit_price: float
total_price: float
@@ -50,9 +48,9 @@ class OrderAddressCreate(BaseModel):
first_name: str = Field(..., min_length=1, max_length=100)
last_name: str = Field(..., min_length=1, max_length=100)
company: Optional[str] = Field(None, max_length=200)
company: str | None = Field(None, max_length=200)
address_line_1: str = Field(..., min_length=1, max_length=255)
address_line_2: Optional[str] = Field(None, max_length=255)
address_line_2: str | None = Field(None, max_length=255)
city: str = Field(..., min_length=1, max_length=100)
postal_code: str = Field(..., min_length=1, max_length=20)
country: str = Field(..., min_length=2, max_length=100)
@@ -67,9 +65,9 @@ class OrderAddressResponse(BaseModel):
address_type: str
first_name: str
last_name: str
company: Optional[str]
company: str | None
address_line_1: str
address_line_2: Optional[str]
address_line_2: str | None
city: str
postal_code: str
country: str
@@ -83,29 +81,29 @@ class OrderAddressResponse(BaseModel):
class OrderCreate(BaseModel):
"""Schema for creating an order."""
customer_id: Optional[int] = None # Optional for guest checkout
items: List[OrderItemCreate] = Field(..., min_length=1)
customer_id: int | None = None # Optional for guest checkout
items: list[OrderItemCreate] = Field(..., min_length=1)
# Addresses
shipping_address: OrderAddressCreate
billing_address: Optional[OrderAddressCreate] = None # Use shipping if not provided
billing_address: OrderAddressCreate | None = None # Use shipping if not provided
# Optional fields
shipping_method: Optional[str] = None
customer_notes: Optional[str] = Field(None, max_length=1000)
shipping_method: str | None = None
customer_notes: str | None = Field(None, max_length=1000)
# Cart/session info
session_id: Optional[str] = None
session_id: str | None = None
class OrderUpdate(BaseModel):
"""Schema for updating order status."""
status: Optional[str] = Field(
status: str | None = Field(
None, pattern="^(pending|processing|shipped|delivered|cancelled|refunded)$"
)
tracking_number: Optional[str] = None
internal_notes: Optional[str] = None
tracking_number: str | None = None
internal_notes: str | None = None
# ============================================================================
@@ -133,26 +131,26 @@ class OrderResponse(BaseModel):
currency: str
# Shipping
shipping_method: Optional[str]
tracking_number: Optional[str]
shipping_method: str | None
tracking_number: str | None
# Notes
customer_notes: Optional[str]
internal_notes: Optional[str]
customer_notes: str | None
internal_notes: str | None
# Timestamps
created_at: datetime
updated_at: datetime
paid_at: Optional[datetime]
shipped_at: Optional[datetime]
delivered_at: Optional[datetime]
cancelled_at: Optional[datetime]
paid_at: datetime | None
shipped_at: datetime | None
delivered_at: datetime | None
cancelled_at: datetime | None
class OrderDetailResponse(OrderResponse):
"""Schema for detailed order response with items and addresses."""
items: List[OrderItemResponse]
items: list[OrderItemResponse]
shipping_address: OrderAddressResponse
billing_address: OrderAddressResponse
@@ -160,7 +158,7 @@ class OrderDetailResponse(OrderResponse):
class OrderListResponse(BaseModel):
"""Schema for paginated order list."""
orders: List[OrderResponse]
orders: list[OrderResponse]
total: int
skip: int
limit: int

View File

@@ -1,6 +1,5 @@
# models/schema/product.py
from datetime import datetime
from typing import List, Optional
from pydantic import BaseModel, ConfigDict, Field
@@ -12,30 +11,30 @@ class ProductCreate(BaseModel):
marketplace_product_id: int = Field(
..., description="MarketplaceProduct ID to add to vendor catalog"
)
product_id: Optional[str] = Field(
product_id: str | None = Field(
None, description="Vendor's internal SKU/product ID"
)
price: Optional[float] = Field(None, ge=0)
sale_price: Optional[float] = Field(None, ge=0)
currency: Optional[str] = None
availability: Optional[str] = None
condition: Optional[str] = None
price: float | None = Field(None, ge=0)
sale_price: float | None = Field(None, ge=0)
currency: str | None = None
availability: str | None = None
condition: str | None = None
is_featured: bool = False
min_quantity: int = Field(1, ge=1)
max_quantity: Optional[int] = Field(None, ge=1)
max_quantity: int | None = Field(None, ge=1)
class ProductUpdate(BaseModel):
product_id: Optional[str] = None
price: Optional[float] = Field(None, ge=0)
sale_price: Optional[float] = Field(None, ge=0)
currency: Optional[str] = None
availability: Optional[str] = None
condition: Optional[str] = None
is_featured: Optional[bool] = None
is_active: Optional[bool] = None
min_quantity: Optional[int] = Field(None, ge=1)
max_quantity: Optional[int] = Field(None, ge=1)
product_id: str | None = None
price: float | None = Field(None, ge=0)
sale_price: float | None = Field(None, ge=0)
currency: str | None = None
availability: str | None = None
condition: str | None = None
is_featured: bool | None = None
is_active: bool | None = None
min_quantity: int | None = Field(None, ge=1)
max_quantity: int | None = Field(None, ge=1)
class ProductResponse(BaseModel):
@@ -44,33 +43,33 @@ class ProductResponse(BaseModel):
id: int
vendor_id: int
marketplace_product: MarketplaceProductResponse
product_id: Optional[str]
price: Optional[float]
sale_price: Optional[float]
currency: Optional[str]
availability: Optional[str]
condition: Optional[str]
product_id: str | None
price: float | None
sale_price: float | None
currency: str | None
availability: str | None
condition: str | None
is_featured: bool
is_active: bool
display_order: int
min_quantity: int
max_quantity: Optional[int]
max_quantity: int | None
created_at: datetime
updated_at: datetime
# Include inventory summary
total_inventory: Optional[int] = None
available_inventory: Optional[int] = None
total_inventory: int | None = None
available_inventory: int | None = None
class ProductDetailResponse(ProductResponse):
"""Product with full inventory details."""
inventory_locations: List[InventoryLocationResponse] = []
inventory_locations: list[InventoryLocationResponse] = []
class ProductListResponse(BaseModel):
products: List[ProductResponse]
products: list[ProductResponse]
total: int
skip: int
limit: int

View File

@@ -1,9 +1,7 @@
import re
from datetime import datetime
from decimal import Decimal
from typing import List, Optional
from pydantic import BaseModel, ConfigDict, EmailStr, Field, field_validator
from pydantic import BaseModel, Field
class StatsResponse(BaseModel):
@@ -35,8 +33,8 @@ class CustomerStatsResponse(BaseModel):
total_orders: int
total_spent: Decimal
average_order_value: Decimal
last_order_date: Optional[datetime]
first_order_date: Optional[datetime]
last_order_date: datetime | None
first_order_date: datetime | None
lifetime_value: Decimal

View File

@@ -10,7 +10,6 @@ This module defines request/response schemas for:
"""
from datetime import datetime
from typing import List, Optional
from pydantic import BaseModel, EmailStr, Field, field_validator
@@ -23,7 +22,7 @@ class RoleBase(BaseModel):
"""Base role schema."""
name: str = Field(..., min_length=1, max_length=100, description="Role name")
permissions: List[str] = Field(
permissions: list[str] = Field(
default_factory=list, description="List of permission strings"
)
@@ -31,14 +30,13 @@ class RoleBase(BaseModel):
class RoleCreate(RoleBase):
"""Schema for creating a role."""
pass
class RoleUpdate(BaseModel):
"""Schema for updating a role."""
name: Optional[str] = Field(None, min_length=1, max_length=100)
permissions: Optional[List[str]] = None
name: str | None = Field(None, min_length=1, max_length=100)
permissions: list[str] | None = None
class RoleResponse(RoleBase):
@@ -56,7 +54,7 @@ class RoleResponse(RoleBase):
class RoleListResponse(BaseModel):
"""Schema for role list response."""
roles: List[RoleResponse]
roles: list[RoleResponse]
total: int
@@ -69,20 +67,20 @@ class TeamMemberBase(BaseModel):
"""Base team member schema."""
email: EmailStr = Field(..., description="Team member email address")
first_name: Optional[str] = Field(None, max_length=100)
last_name: Optional[str] = Field(None, max_length=100)
first_name: str | None = Field(None, max_length=100)
last_name: str | None = Field(None, max_length=100)
class TeamMemberInvite(TeamMemberBase):
"""Schema for inviting a team member."""
role_id: Optional[int] = Field(
role_id: int | None = Field(
None, description="Role ID to assign (for preset roles)"
)
role_name: Optional[str] = Field(
role_name: str | None = Field(
None, description="Role name (manager, staff, support, etc.)"
)
custom_permissions: Optional[List[str]] = Field(
custom_permissions: list[str] | None = Field(
None, description="Custom permissions (overrides role preset)"
)
@@ -112,8 +110,8 @@ class TeamMemberInvite(TeamMemberBase):
class TeamMemberUpdate(BaseModel):
"""Schema for updating a team member."""
role_id: Optional[int] = Field(None, description="New role ID")
is_active: Optional[bool] = Field(None, description="Active status")
role_id: int | None = Field(None, description="New role ID")
is_active: bool | None = Field(None, description="Active status")
class TeamMemberResponse(BaseModel):
@@ -122,13 +120,13 @@ class TeamMemberResponse(BaseModel):
id: int = Field(..., description="User ID")
email: EmailStr
username: str
first_name: Optional[str]
last_name: Optional[str]
first_name: str | None
last_name: str | None
full_name: str
user_type: str = Field(..., description="'owner' or 'member'")
role_name: str = Field(..., description="Role name")
role_id: Optional[int]
permissions: List[str] = Field(
role_id: int | None
permissions: list[str] = Field(
default_factory=list, description="User's permissions"
)
is_active: bool
@@ -136,8 +134,8 @@ class TeamMemberResponse(BaseModel):
invitation_pending: bool = Field(
default=False, description="True if invitation not yet accepted"
)
invited_at: Optional[datetime] = Field(None, description="When invitation was sent")
accepted_at: Optional[datetime] = Field(
invited_at: datetime | None = Field(None, description="When invitation was sent")
accepted_at: datetime | None = Field(
None, description="When invitation was accepted"
)
joined_at: datetime = Field(..., description="When user joined vendor")
@@ -149,7 +147,7 @@ class TeamMemberResponse(BaseModel):
class TeamMemberListResponse(BaseModel):
"""Schema for team member list response."""
members: List[TeamMemberResponse]
members: list[TeamMemberResponse]
total: int
active_count: int
pending_invitations: int
@@ -197,7 +195,7 @@ class InvitationResponse(BaseModel):
message: str
email: EmailStr
role: str
invitation_token: Optional[str] = Field(
invitation_token: str | None = Field(
None, description="Token (only returned in dev/test environments)"
)
invitation_sent: bool = Field(default=True)
@@ -239,7 +237,7 @@ class TeamStatistics(BaseModel):
class BulkRemoveRequest(BaseModel):
"""Schema for bulk removing team members."""
user_ids: List[int] = Field(
user_ids: list[int] = Field(
..., min_items=1, description="List of user IDs to remove"
)
@@ -249,7 +247,7 @@ class BulkRemoveResponse(BaseModel):
success_count: int
failed_count: int
errors: List[dict] = Field(default_factory=list)
errors: list[dict] = Field(default_factory=list)
# ============================================================================
@@ -260,7 +258,7 @@ class BulkRemoveResponse(BaseModel):
class PermissionCheckRequest(BaseModel):
"""Schema for checking permissions."""
permissions: List[str] = Field(..., min_items=1, description="Permissions to check")
permissions: list[str] = Field(..., min_items=1, description="Permissions to check")
class PermissionCheckResponse(BaseModel):
@@ -268,8 +266,8 @@ class PermissionCheckResponse(BaseModel):
has_all: bool = Field(..., description="True if user has all permissions")
has_any: bool = Field(..., description="True if user has any permission")
granted: List[str] = Field(default_factory=list, description="Permissions user has")
denied: List[str] = Field(
granted: list[str] = Field(default_factory=list, description="Permissions user has")
denied: list[str] = Field(
default_factory=list, description="Permissions user lacks"
)
@@ -277,10 +275,10 @@ class PermissionCheckResponse(BaseModel):
class UserPermissionsResponse(BaseModel):
"""Schema for user's permissions response."""
permissions: List[str] = Field(default_factory=list)
permissions: list[str] = Field(default_factory=list)
permission_count: int
is_owner: bool
role_name: Optional[str] = None
role_name: str | None = None
# ============================================================================
@@ -293,4 +291,4 @@ class TeamErrorResponse(BaseModel):
error_code: str
message: str
details: Optional[dict] = None
details: dict | None = None

View File

@@ -15,7 +15,7 @@ Schemas include:
import re
from datetime import datetime
from typing import Any, Dict, List, Optional
from typing import Any
from pydantic import BaseModel, ConfigDict, Field, field_validator
@@ -36,7 +36,7 @@ class VendorCreate(BaseModel):
name: str = Field(
..., description="Display name of the vendor", min_length=2, max_length=255
)
description: Optional[str] = Field(None, description="Vendor description")
description: str | None = Field(None, description="Vendor description")
# Owner Information (Creates User Account)
owner_email: str = Field(
@@ -45,21 +45,21 @@ class VendorCreate(BaseModel):
)
# Business Contact Information (Vendor Fields)
contact_email: Optional[str] = Field(
contact_email: str | None = Field(
None,
description="Public business contact email (defaults to owner_email if not provided)",
)
contact_phone: Optional[str] = Field(None, description="Contact phone number")
website: Optional[str] = Field(None, description="Website URL")
contact_phone: str | None = Field(None, description="Contact phone number")
website: str | None = Field(None, description="Website URL")
# Business Details
business_address: Optional[str] = Field(None, description="Business address")
tax_number: Optional[str] = Field(None, description="Tax/VAT number")
business_address: str | None = Field(None, description="Business address")
tax_number: str | None = Field(None, description="Tax/VAT number")
# Marketplace URLs (multi-language support)
letzshop_csv_url_fr: Optional[str] = Field(None, description="French CSV URL")
letzshop_csv_url_en: Optional[str] = Field(None, description="English CSV URL")
letzshop_csv_url_de: Optional[str] = Field(None, description="German CSV URL")
letzshop_csv_url_fr: str | None = Field(None, description="French CSV URL")
letzshop_csv_url_en: str | None = Field(None, description="English CSV URL")
letzshop_csv_url_de: str | None = Field(None, description="German CSV URL")
@field_validator("owner_email", "contact_email")
@classmethod
@@ -95,29 +95,29 @@ class VendorUpdate(BaseModel):
"""
# Basic Information
name: Optional[str] = Field(None, min_length=2, max_length=255)
description: Optional[str] = None
subdomain: Optional[str] = Field(None, min_length=2, max_length=100)
name: str | None = Field(None, min_length=2, max_length=255)
description: str | None = None
subdomain: str | None = Field(None, min_length=2, max_length=100)
# Business Contact Information (Vendor Fields)
contact_email: Optional[str] = Field(
contact_email: str | None = Field(
None, description="Public business contact email"
)
contact_phone: Optional[str] = None
website: Optional[str] = None
contact_phone: str | None = None
website: str | None = None
# Business Details
business_address: Optional[str] = None
tax_number: Optional[str] = None
business_address: str | None = None
tax_number: str | None = None
# Marketplace URLs
letzshop_csv_url_fr: Optional[str] = None
letzshop_csv_url_en: Optional[str] = None
letzshop_csv_url_de: Optional[str] = None
letzshop_csv_url_fr: str | None = None
letzshop_csv_url_en: str | None = None
letzshop_csv_url_de: str | None = None
# Status (Admin only)
is_active: Optional[bool] = None
is_verified: Optional[bool] = None
is_active: bool | None = None
is_verified: bool | None = None
@field_validator("subdomain")
@classmethod
@@ -145,22 +145,22 @@ class VendorResponse(BaseModel):
vendor_code: str
subdomain: str
name: str
description: Optional[str]
description: str | None
owner_user_id: int
# Contact Information (Business)
contact_email: Optional[str]
contact_phone: Optional[str]
website: Optional[str]
contact_email: str | None
contact_phone: str | None
website: str | None
# Business Information
business_address: Optional[str]
tax_number: Optional[str]
business_address: str | None
tax_number: str | None
# Marketplace URLs
letzshop_csv_url_fr: Optional[str]
letzshop_csv_url_en: Optional[str]
letzshop_csv_url_de: Optional[str]
letzshop_csv_url_fr: str | None
letzshop_csv_url_en: str | None
letzshop_csv_url_de: str | None
# Status Flags
is_active: bool
@@ -196,13 +196,13 @@ class VendorCreateResponse(VendorDetailResponse):
temporary_password: str = Field(
..., description="Temporary password for owner (SHOWN ONLY ONCE)"
)
login_url: Optional[str] = Field(None, description="URL for vendor owner to login")
login_url: str | None = Field(None, description="URL for vendor owner to login")
class VendorListResponse(BaseModel):
"""Schema for paginated vendor list."""
vendors: List[VendorResponse]
vendors: list[VendorResponse]
total: int
skip: int
limit: int
@@ -237,7 +237,7 @@ class VendorTransferOwnership(BaseModel):
..., description="Must be true to confirm ownership transfer"
)
transfer_reason: Optional[str] = Field(
transfer_reason: str | None = Field(
None,
max_length=500,
description="Reason for ownership transfer (for audit logs)",
@@ -260,12 +260,12 @@ class VendorTransferOwnershipResponse(BaseModel):
vendor_code: str
vendor_name: str
old_owner: Dict[str, Any] = Field(
old_owner: dict[str, Any] = Field(
..., description="Information about the previous owner"
)
new_owner: Dict[str, Any] = Field(
new_owner: dict[str, Any] = Field(
..., description="Information about the new owner"
)
transferred_at: datetime
transfer_reason: Optional[str]
transfer_reason: str | None

View File

@@ -12,7 +12,6 @@ Schemas include:
import re
from datetime import datetime
from typing import Dict, List, Optional
from pydantic import BaseModel, ConfigDict, Field, field_validator
@@ -69,8 +68,8 @@ class VendorDomainCreate(BaseModel):
class VendorDomainUpdate(BaseModel):
"""Schema for updating vendor domain settings."""
is_primary: Optional[bool] = Field(None, description="Set as primary domain")
is_active: Optional[bool] = Field(None, description="Activate or deactivate domain")
is_primary: bool | None = Field(None, description="Set as primary domain")
is_active: bool | None = Field(None, description="Activate or deactivate domain")
model_config = ConfigDict(from_attributes=True)
@@ -87,9 +86,9 @@ class VendorDomainResponse(BaseModel):
is_active: bool
is_verified: bool
ssl_status: str
verification_token: Optional[str] = None
verified_at: Optional[datetime] = None
ssl_verified_at: Optional[datetime] = None
verification_token: str | None = None
verified_at: datetime | None = None
ssl_verified_at: datetime | None = None
created_at: datetime
updated_at: datetime
@@ -97,7 +96,7 @@ class VendorDomainResponse(BaseModel):
class VendorDomainListResponse(BaseModel):
"""Schema for paginated vendor domain list."""
domains: List[VendorDomainResponse]
domains: list[VendorDomainResponse]
total: int
@@ -106,9 +105,9 @@ class DomainVerificationInstructions(BaseModel):
domain: str
verification_token: str
instructions: Dict[str, str]
txt_record: Dict[str, str]
common_registrars: Dict[str, str]
instructions: dict[str, str]
txt_record: dict[str, str]
common_registrars: dict[str, str]
model_config = ConfigDict(from_attributes=True)

View File

@@ -3,7 +3,6 @@
Pydantic schemas for vendor theme operations.
"""
from typing import Dict, List, Optional
from pydantic import BaseModel, Field
@@ -11,40 +10,40 @@ from pydantic import BaseModel, Field
class VendorThemeColors(BaseModel):
"""Color scheme for vendor theme."""
primary: Optional[str] = Field(None, description="Primary brand color")
secondary: Optional[str] = Field(None, description="Secondary color")
accent: Optional[str] = Field(None, description="Accent/CTA color")
background: Optional[str] = Field(None, description="Background color")
text: Optional[str] = Field(None, description="Text color")
border: Optional[str] = Field(None, description="Border color")
primary: str | None = Field(None, description="Primary brand color")
secondary: str | None = Field(None, description="Secondary color")
accent: str | None = Field(None, description="Accent/CTA color")
background: str | None = Field(None, description="Background color")
text: str | None = Field(None, description="Text color")
border: str | None = Field(None, description="Border color")
class VendorThemeFonts(BaseModel):
"""Typography settings for vendor theme."""
heading: Optional[str] = Field(None, description="Font for headings")
body: Optional[str] = Field(None, description="Font for body text")
heading: str | None = Field(None, description="Font for headings")
body: str | None = Field(None, description="Font for body text")
class VendorThemeBranding(BaseModel):
"""Branding assets for vendor theme."""
logo: Optional[str] = Field(None, description="Logo URL")
logo_dark: Optional[str] = Field(None, description="Dark mode logo URL")
favicon: Optional[str] = Field(None, description="Favicon URL")
banner: Optional[str] = Field(None, description="Banner image URL")
logo: str | None = Field(None, description="Logo URL")
logo_dark: str | None = Field(None, description="Dark mode logo URL")
favicon: str | None = Field(None, description="Favicon URL")
banner: str | None = Field(None, description="Banner image URL")
class VendorThemeLayout(BaseModel):
"""Layout settings for vendor theme."""
style: Optional[str] = Field(
style: str | None = Field(
None, description="Product layout style (grid, list, masonry)"
)
header: Optional[str] = Field(
header: str | None = Field(
None, description="Header style (fixed, static, transparent)"
)
product_card: Optional[str] = Field(
product_card: str | None = Field(
None, description="Product card style (modern, classic, minimal)"
)
@@ -52,15 +51,15 @@ class VendorThemeLayout(BaseModel):
class VendorThemeUpdate(BaseModel):
"""Schema for updating vendor theme (partial updates allowed)."""
theme_name: Optional[str] = Field(None, description="Theme preset name")
colors: Optional[Dict[str, str]] = Field(None, description="Color scheme")
fonts: Optional[Dict[str, str]] = Field(None, description="Font settings")
branding: Optional[Dict[str, Optional[str]]] = Field(
theme_name: str | None = Field(None, description="Theme preset name")
colors: dict[str, str] | None = Field(None, description="Color scheme")
fonts: dict[str, str] | None = Field(None, description="Font settings")
branding: dict[str, str | None] | None = Field(
None, description="Branding assets"
)
layout: Optional[Dict[str, str]] = Field(None, description="Layout settings")
custom_css: Optional[str] = Field(None, description="Custom CSS rules")
social_links: Optional[Dict[str, str]] = Field(
layout: dict[str, str] | None = Field(None, description="Layout settings")
custom_css: str | None = Field(None, description="Custom CSS rules")
social_links: dict[str, str] | None = Field(
None, description="Social media links"
)
@@ -69,15 +68,15 @@ class VendorThemeResponse(BaseModel):
"""Schema for vendor theme response."""
theme_name: str = Field(..., description="Theme name")
colors: Dict[str, str] = Field(..., description="Color scheme")
fonts: Dict[str, str] = Field(..., description="Font settings")
branding: Dict[str, Optional[str]] = Field(..., description="Branding assets")
layout: Dict[str, str] = Field(..., description="Layout settings")
social_links: Optional[Dict[str, str]] = Field(
colors: dict[str, str] = Field(..., description="Color scheme")
fonts: dict[str, str] = Field(..., description="Font settings")
branding: dict[str, str | None] = Field(..., description="Branding assets")
layout: dict[str, str] = Field(..., description="Layout settings")
social_links: dict[str, str] | None = Field(
default_factory=dict, description="Social links"
)
custom_css: Optional[str] = Field(None, description="Custom CSS")
css_variables: Optional[Dict[str, str]] = Field(
custom_css: str | None = Field(None, description="Custom CSS")
css_variables: dict[str, str] | None = Field(
None, description="CSS custom properties"
)
@@ -105,4 +104,4 @@ class ThemePresetResponse(BaseModel):
class ThemePresetListResponse(BaseModel):
"""List of available theme presets."""
presets: List[ThemePresetPreview] = Field(..., description="Available presets")
presets: list[ThemePresetPreview] = Field(..., description="Available presets")