Phase 3: Schema Layer - Pydantic v2 migration, deduplication, and missing schemas

- Migrated all schemas to Pydantic v2 syntax (model_config, ConfigDict)
- Fixed mutable default in ProtoMessageBase using Field(default_factory=datetime.now)
- Consolidated CardCollection and Wishlist schemas in user_card_collection.py
- Created game_schemas.py with GameCreate, GameResponse, GameJoinRequest, etc.
- Created mtg_card_schemas.py with MtgCardResponse, MtgCardSearchRequest, etc.
- Added CardImportBatchCreate, CardImportBatchResponse, UserCardImportCreate/Response schemas
- Fixed duplicate UserCardImportRecord class between card_import_batch.py and user_card_import_record.py
- Updated __init__.py with comprehensive schema exports
- Created verify_schemas.py for schema-model matching verification
This commit is contained in:
2026-08-16 05:22:17 +00:00
parent 5f324cf8a9
commit bea91db64d
20 changed files with 2733 additions and 342 deletions
+133 -1
View File
@@ -1 +1,133 @@
# Schemas package
"""Schemas package initialization."""
from app.schemas.schemas import (
LoginRequest, LoginResponse, RefreshTokenRequest, TokenResponse,
UserBase, UserCreate, UserUpdate, UserResponse,
DeckCreate, DeckUpdate, DeckResponse,
FolderCreate, FolderResponse,
GameCreate, GameResponse,
RoomResponse,
BanCreate, BanResponse,
ErrorResponse, ValidationErrorResponse,
PaginationParams, PaginatedResponse,
CardMirrorResponse, DeckCardLinkResponse, DeckWithCardsResponse,
)
from app.schemas.user_data_schemas import (
DeckVersionStatus, GameReplayStatus, GameOutcomeType,
GroupMemberRole, NetworkMemberRole, UserPreferenceTheme, ActivityType,
SessionResponse, SessionCleanupResponse,
DeckVersionCreate, DeckVersionUpdate, DeckVersionResponse, DeckVersionListResponse,
GameReplayCreate, GameReplayUpdate, GameReplayResponse, GameReplayListResponse,
GameOutcomeCreate, GameOutcomeResponse, GameOutcomeListResponse,
UserStatisticsResponse, StatisticsUpdateResponse,
CardCollectionCreate, CardCollectionUpdate, CardCollectionResponse, CardCollectionListResponse,
WishlistCreate, WishlistUpdate, WishlistResponse, WishlistListResponse,
GroupCreate, GroupUpdate, GroupMemberCreate, GroupMemberUpdate, GroupMemberRemove,
GroupResponse, GroupListResponse, GroupChatMessageCreate, GroupChatMessageResponse, GroupChatMessageListResponse,
NetworkCreate, NetworkUpdate, NetworkMemberCreate,
NetworkResponse, NetworkListResponse,
UserPreferenceUpdate, UserPreferenceResponse,
ActivityLogEntry, ActivityLogListResponse,
MessageResponse, CountResponse, ErrorDetail,
)
from app.schemas.user_deck_schemas import (
DeckStatus, DeckZone, SuggestionType,
UserDeckCreate, UserDeckUpdate, UserDeckResponse, UserDeckListResponse,
DeckCardCreate, DeckCardUpdate, DeckCardResponse, DeckCardWithDetailsResponse, DeckCardListResponse,
PrecedentCreate, PrecedentUpdate, PrecedentResponse, PrecedentListResponse,
SuggestionCreate, SuggestionResponse, SuggestionListResponse,
DeckFinalizeRequest, DeckFinalizeResponse, DeckDeleteResponse,
CardSearchRequest, CardSearchResponse,
)
from app.schemas.user_card_collection import (
CardCondition, AcquisitionMethod,
CollectionStatistics, CollectionSummaryResponse,
)
from app.schemas.card_import_schemas import (
CardImportRequest, CardImportResponse, CardImportStatusResponse,
CardMatchResult, CardImportSummary,
CardImportBatchCreate, CardImportBatchResponse,
UserCardImportCreate, UserCardImportResponse, UserCardImportRecordResponse,
)
from app.schemas.card_search_schemas import (
CardResponse, SetResponse, CardTypeResponse,
)
from app.schemas.proto_messages import (
ProtoMessageBase, SessionCommand, GameCommand, GameEvent, Response,
ServerInfoUser, ServerInfoDeckStorageFile, ServerInfoDeckStorageFolder,
ServerInfoDeckStorageTreeItem, ServerInfoCard, ServerInfoZone, ServerInfoGame,
)
from app.schemas.protocol_constants import (
SessionCommandType, GameCommandType, GameEventType, ResponseCode,
ZoneType, UserLevelFlag,
)
from app.schemas.game_schemas import (
GameCreate, GameResponse, GameJoinRequest, GameLeaveRequest,
GameListResponse, GamePlayerResponse, GameStateResponse,
)
from app.schemas.mtg_card_schemas import (
MtgCardResponse, MtgCardSearchRequest, MtgCardSearchResponse,
MtgSetResponse, MtgCardMirrorResponse, DeckCardLinkResponse, DeckWithCardsResponse,
)
__all__ = [
# Auth
"LoginRequest", "LoginResponse", "RefreshTokenRequest", "TokenResponse",
# User
"UserBase", "UserCreate", "UserUpdate", "UserResponse",
# Deck
"DeckCreate", "DeckUpdate", "DeckResponse",
"FolderCreate", "FolderResponse",
# Game
"GameCreate", "GameResponse", "RoomResponse",
"GameJoinRequest", "GameLeaveRequest", "GameListResponse", "GamePlayerResponse", "GameStateResponse",
# Ban
"BanCreate", "BanResponse",
# Error
"ErrorResponse", "ValidationErrorResponse",
# Pagination
"PaginationParams", "PaginatedResponse",
# Card Mirror
"CardMirrorResponse", "DeckCardLinkResponse", "DeckWithCardsResponse",
# User Data
"DeckVersionStatus", "GameReplayStatus", "GameOutcomeType",
"GroupMemberRole", "NetworkMemberRole", "UserPreferenceTheme", "ActivityType",
"SessionResponse", "SessionCleanupResponse",
"DeckVersionCreate", "DeckVersionUpdate", "DeckVersionResponse", "DeckVersionListResponse",
"GameReplayCreate", "GameReplayUpdate", "GameReplayResponse", "GameReplayListResponse",
"GameOutcomeCreate", "GameOutcomeResponse", "GameOutcomeListResponse",
"UserStatisticsResponse", "StatisticsUpdateResponse",
"CardCollectionCreate", "CardCollectionUpdate", "CardCollectionResponse", "CardCollectionListResponse",
"WishlistCreate", "WishlistUpdate", "WishlistResponse", "WishlistListResponse",
"GroupCreate", "GroupUpdate", "GroupMemberCreate", "GroupMemberUpdate", "GroupMemberRemove",
"GroupResponse", "GroupListResponse", "GroupChatMessageCreate", "GroupChatMessageResponse", "GroupChatMessageListResponse",
"NetworkCreate", "NetworkUpdate", "NetworkMemberCreate",
"NetworkResponse", "NetworkListResponse",
"UserPreferenceUpdate", "UserPreferenceResponse",
"ActivityLogEntry", "ActivityLogListResponse",
"MessageResponse", "CountResponse", "ErrorDetail",
# User Deck
"DeckStatus", "DeckZone", "SuggestionType",
"UserDeckCreate", "UserDeckUpdate", "UserDeckResponse", "UserDeckListResponse",
"DeckCardCreate", "DeckCardUpdate", "DeckCardResponse", "DeckCardWithDetailsResponse", "DeckCardListResponse",
"PrecedentCreate", "PrecedentUpdate", "PrecedentResponse", "PrecedentListResponse",
"SuggestionCreate", "SuggestionResponse", "SuggestionListResponse",
"DeckFinalizeRequest", "DeckFinalizeResponse", "DeckDeleteResponse",
"CardSearchRequest", "CardSearchResponse",
# Card Collection
"CardCondition", "AcquisitionMethod",
"CollectionStatistics", "CollectionSummaryResponse",
# Card Import
"CardImportRequest", "CardImportResponse", "CardImportStatusResponse",
"CardMatchResult", "CardImportSummary",
"CardImportBatchCreate", "CardImportBatchResponse",
"UserCardImportCreate", "UserCardImportResponse", "UserCardImportRecordResponse",
# Card Search
"CardResponse", "SetResponse", "CardTypeResponse",
# Proto Messages
"ProtoMessageBase", "SessionCommand", "GameCommand", "GameEvent", "Response",
"ServerInfoUser", "ServerInfoDeckStorageFile", "ServerInfoDeckStorageFolder",
"ServerInfoDeckStorageTreeItem", "ServerInfoCard", "ServerInfoZone", "ServerInfoGame",
# Protocol Constants
"SessionCommandType", "GameCommandType", "GameEventType", "ResponseCode",
"ZoneType", "UserLevelFlag",
]
+60 -1
View File
@@ -4,7 +4,7 @@ Pydantic schemas for card import feature.
Provides request/response models for importing card collections
and using them for deckbuilding.
"""
from pydantic import BaseModel, Field
from pydantic import BaseModel, Field, ConfigDict
from typing import List, Optional, Dict, Any
from datetime import datetime
@@ -53,6 +53,65 @@ class CardImportSummary(BaseModel):
import_id: Optional[int] = None
# ===== Card Import Batch Schemas =====
class CardImportBatchCreate(BaseModel):
"""Card import batch creation request."""
user_id: int
card_names: List[str] = Field(..., min_length=1, max_length=10000)
source: Optional[str] = None # 'manual', 'mtgjson', 'deck_text'
class CardImportBatchResponse(BaseModel):
"""Card import batch response."""
id: int
user_id: int
card_names: List[str]
source: Optional[str]
status: str # 'pending', 'processing', 'completed', 'failed'
total_cards: int
matched_cards: int
unmatched_cards: int
created_at: datetime
updated_at: datetime
model_config = ConfigDict(from_attributes=True)
class UserCardImportCreate(BaseModel):
"""User card import request."""
batch_id: int
card_id: int
quantity: int = Field(1, ge=1)
match_confidence: Optional[float] = None
class UserCardImportResponse(BaseModel):
"""User card import response."""
id: int
user_id: int
batch_id: int
card_id: int
quantity: int
match_confidence: Optional[float]
created_at: datetime
model_config = ConfigDict(from_attributes=True)
class UserCardImportRecordResponse(BaseModel):
"""User card import record response."""
id: int
user_id: int
card_id: int
batch_id: int
quantity: int
source: Optional[str]
created_at: datetime
model_config = ConfigDict(from_attributes=True)
# Generic response models
class MessageResponse(BaseModel):
"""Generic message response."""
+64
View File
@@ -0,0 +1,64 @@
"""Pydantic schemas for game features."""
from pydantic import BaseModel, Field, ConfigDict
from typing import Optional, List, Dict, Any
from datetime import datetime
class GameCreate(BaseModel):
"""Game creation request."""
room_id: int
game_type: Optional[str] = None
description: Optional[str] = None
password: Optional[str] = None
max_players: int = Field(4, ge=2, le=8)
class GameResponse(BaseModel):
"""Game response payload."""
id: int
room_id: int
game_type: Optional[str]
description: Optional[str]
with_password: bool
max_players: int
player_count: int
started: bool
creation_date: datetime
model_config = ConfigDict(from_attributes=True)
class GameJoinRequest(BaseModel):
"""Game join request."""
game_id: int
password: Optional[str] = None
class GameLeaveRequest(BaseModel):
"""Game leave request."""
game_id: int
class GameListResponse(BaseModel):
"""List of games."""
games: List[GameResponse]
total: int
class GamePlayerResponse(BaseModel):
"""Game player response."""
user_id: int
username: str
deck_id: Optional[int] = None
is_ready: bool = False
is_host: bool = False
class GameStateResponse(BaseModel):
"""Game state response."""
game_id: int
players: List[GamePlayerResponse]
turn: int
phase: str
zones: Dict[str, Any]
updated_at: datetime
+122
View File
@@ -0,0 +1,122 @@
"""Pydantic schemas for MTG card data."""
from pydantic import BaseModel, Field, ConfigDict
from typing import Optional, List, Dict, Any
from datetime import datetime
class MtgCardResponse(BaseModel):
"""MTG card response with full details."""
id: int
source_id: Optional[int] = None
name: str
mana_cost: Optional[str] = None
type_line: Optional[str] = None
oracle_text: Optional[str] = None
power: Optional[str] = None
toughness: Optional[str] = None
rarity: Optional[str] = None
layout: Optional[str] = None
artist: Optional[str] = None
flavor_text: Optional[str] = None
numbers: Optional[str] = None
identifiers: Optional[Dict[str, Any]] = None
images: Optional[Dict[str, Any]] = None
image: Optional[str] = None
card_parts: Optional[List[str]] = None
keywords: Optional[List[str]] = None
legalities: Optional[Dict[str, str]] = None
set_code: Optional[str] = None
set_name: Optional[str] = None
synced_at: Optional[datetime] = None
created_at: datetime
model_config = ConfigDict(from_attributes=True)
class MtgCardSearchRequest(BaseModel):
"""MTG card search request."""
query: str = Field(..., min_length=1, max_length=100)
set_code: Optional[str] = None
rarity: Optional[str] = None
type_line: Optional[str] = None
limit: int = Field(50, ge=1, le=200)
offset: int = Field(0, ge=0)
class MtgCardSearchResponse(BaseModel):
"""MTG card search response."""
cards: List[MtgCardResponse]
total: int
page: int
page_size: int
total_pages: int
class MtgSetResponse(BaseModel):
"""MTG set response."""
id: int
code: str
name: str
release_date: Optional[datetime] = None
card_count: Optional[int] = None
type: Optional[str] = None
border: Optional[str] = None
mcm_id: Optional[int] = None
model_config = ConfigDict(from_attributes=True)
class MtgCardMirrorResponse(BaseModel):
"""Mirrored card data for user decks."""
id: int
source_id: Optional[int] = None
name: str
mana_cost: Optional[str] = None
type_line: Optional[str] = None
oracle_text: Optional[str] = None
power: Optional[str] = None
toughness: Optional[str] = None
rarity: Optional[str] = None
layout: Optional[str] = None
artist: Optional[str] = None
flavor_text: Optional[str] = None
numbers: Optional[str] = None
identifiers: Optional[Dict[str, Any]] = None
images: Optional[Dict[str, Any]] = None
image: Optional[str] = None
card_parts: Optional[str] = None
keywords: Optional[str] = None
legalities: Optional[Dict[str, str]] = None
set_code: Optional[str] = None
set_name: Optional[str] = None
synced_at: Optional[datetime] = None
created_at: datetime
model_config = ConfigDict(from_attributes=True)
class DeckCardLinkResponse(BaseModel):
"""Deck-card link response."""
id: int
deck_id: int
card_id: int
quantity: int
zone: str
card: MtgCardMirrorResponse
model_config = ConfigDict(from_attributes=True)
class DeckWithCardsResponse(BaseModel):
"""Deck response with card links."""
id: int
name: str
content: str
format: str
status: str
folder_id: Optional[int] = None
owner_id: int
creation_date: datetime
card_links: List[DeckCardLinkResponse] = []
model_config = ConfigDict(from_attributes=True)