Began work on overhauling the deck_builder

This commit is contained in:
mwisnowski 2025-01-14 12:07:49 -08:00
parent e0dd09adee
commit 319f7848d3
10 changed files with 2589 additions and 761 deletions

View file

@ -1,64 +1,237 @@
"""Custom exceptions for MTG Python Deckbuilder setup operations."""
"""Custom exceptions for the MTG Python Deckbuilder application."""
class MTGSetupError(Exception):
"""Base exception class for MTG setup-related errors."""
pass
class DeckBuilderError(Exception):
"""Base exception class for deck builder errors.
Attributes:
code (str): Error code for identifying the error type
message (str): Descriptive error message
details (dict): Additional error context and details
"""
def __init__(self, message: str, code: str = "DECK_ERR", details: dict | None = None):
"""Initialize the base deck builder error.
Args:
message: Human-readable error description
code: Error code for identification and handling
details: Additional context about the error
"""
self.code = code
self.message = message
self.details = details or {}
super().__init__(self.message)
def __str__(self) -> str:
"""Format the error message with code and details."""
error_msg = f"[{self.code}] {self.message}"
if self.details:
error_msg += f"\nDetails: {self.details}"
return error_msg
class MTGSetupError(DeckBuilderError):
"""Base exception class for MTG setup-related errors.
This exception serves as the base for all setup-related errors in the deck builder,
including file operations, data processing, and validation during setup.
"""
def __init__(self, message: str, code: str = "SETUP_ERR", details: dict | None = None):
"""Initialize the base setup error.
Args:
message: Human-readable error description
code: Error code for identification and handling
details: Additional context about the error
"""
super().__init__(message, code=code, details=details)
class CSVFileNotFoundError(MTGSetupError):
"""Exception raised when a required CSV file is not found.
This exception is raised when attempting to access or process a CSV file
that does not exist in the expected location.
Args:
message: Explanation of the error
filename: Name of the missing CSV file
"""
def __init__(self, message: str, filename: str) -> None:
self.filename = filename
super().__init__(f"{message}: {filename}")
def __init__(self, filename: str, details: dict | None = None):
"""Initialize CSV file not found error.
Args:
filename: Name of the missing CSV file
details: Additional context about the missing file
"""
message = f"Required CSV file not found: '{filename}'"
super().__init__(message, code="CSV_MISSING", details=details)
class MTGJSONDownloadError(MTGSetupError):
"""Exception raised when downloading data from MTGJSON fails.
This exception is raised when there are issues downloading card data
from the MTGJSON API, such as network errors or API failures.
Args:
message: Explanation of the error
url: The URL that failed to download
status_code: HTTP status code if available
"""
def __init__(self, message: str, url: str, status_code: int = None) -> None:
self.url = url
self.status_code = status_code
def __init__(self, url: str, status_code: int | None = None, details: dict | None = None):
"""Initialize MTGJSON download error.
Args:
url: The URL that failed to download
status_code: HTTP status code if available
details: Additional context about the download failure
"""
status_info = f" (Status: {status_code})" if status_code else ""
super().__init__(f"{message}: {url}{status_info}")
message = f"Failed to download from MTGJSON: {url}{status_info}"
super().__init__(message, code="MTGJSON_ERR", details=details)
class DataFrameProcessingError(MTGSetupError):
"""Exception raised when DataFrame operations fail during setup.
# Input Handler Exceptions
class EmptyInputError(DeckBuilderError):
"""Raised when text input validation fails due to empty or whitespace-only input.
This exception is raised when there are issues processing card data
in pandas DataFrames, such as filtering, sorting, or transformation errors.
Args:
message: Explanation of the error
operation: The DataFrame operation that failed (e.g., 'color_filtering', 'commander_processing')
details: Additional error details
Examples:
>>> raise DataFrameProcessingError(
... "Invalid color identity",
... "color_filtering",
... "Color 'P' is not a valid MTG color"
... )
This exception is used by the validate_text method when checking user input.
"""
def __init__(self, message: str, operation: str, details: str = None) -> None:
self.operation = operation
self.details = details
error_info = f" - {details}" if details else ""
super().__init__(f"{message} during {operation}{error_info}")
def __init__(self, field_name: str = "input", details: dict | None = None):
"""Initialize empty input error.
Args:
field_name: Name of the input field that was empty
details: Additional context about the validation failure
"""
message = f"Empty or whitespace-only {field_name} is not allowed"
super().__init__(message, code="EMPTY_INPUT", details=details)
class InvalidNumberError(DeckBuilderError):
"""Raised when number input validation fails.
This exception is used by the validate_number method when checking numeric input.
"""
def __init__(self, value: str, details: dict | None = None):
"""Initialize invalid number error.
Args:
value: The invalid input value
details: Additional context about the validation failure
"""
message = f"Invalid number format: '{value}'"
super().__init__(message, code="INVALID_NUM", details=details)
class InvalidQuestionTypeError(DeckBuilderError):
"""Raised when an unsupported question type is used in the questionnaire method.
This exception is raised when the questionnaire method receives an unknown question type.
"""
def __init__(self, question_type: str, details: dict | None = None):
"""Initialize invalid question type error.
Args:
question_type: The unsupported question type
details: Additional context about the error
"""
message = f"Unsupported question type: '{question_type}'"
super().__init__(message, code="INVALID_QTYPE", details=details)
class MaxAttemptsError(DeckBuilderError):
"""Raised when maximum input attempts are exceeded.
This exception is used when user input validation fails multiple times.
"""
def __init__(self, max_attempts: int, input_type: str = "input", details: dict | None = None):
"""Initialize maximum attempts error.
Args:
max_attempts: Maximum number of attempts allowed
input_type: Type of input that failed validation
details: Additional context about the attempts
"""
message = f"Maximum {input_type} attempts ({max_attempts}) exceeded"
super().__init__(message, code="MAX_ATTEMPTS", details=details)
# CSV Exceptions
class CSVError(DeckBuilderError):
"""Base exception class for CSV-related errors.
This exception serves as the base for all CSV-related errors in the deck builder,
including file reading, processing, validation, and timeout issues.
Attributes:
code (str): Error code for identifying the error type
message (str): Descriptive error message
details (dict): Additional error context and details
"""
def __init__(self, message: str, code: str = "CSV_ERR", details: dict | None = None):
"""Initialize the base CSV error.
Args:
message: Human-readable error description
code: Error code for identification and handling
details: Additional context about the error
"""
super().__init__(message, code=code, details=details)
class CSVReadError(CSVError):
"""Raised when there are issues reading CSV files.
This exception is used when CSV files cannot be opened, read, or parsed.
"""
def __init__(self, filename: str, details: dict | None = None):
"""Initialize CSV read error.
Args:
filename: Name of the CSV file that failed to read
details: Additional context about the read failure
"""
message = f"Failed to read CSV file: '{filename}'"
super().__init__(message, code="CSV_READ", details=details)
class CSVProcessingError(CSVError):
"""Base class for CSV and DataFrame processing errors.
This exception is used when operations fail during data processing,
including batch operations and transformations.
"""
def __init__(self, message: str, operation_context: dict | None = None, details: dict | None = None):
"""Initialize processing error with context.
Args:
message: Descriptive error message
operation_context: Details about the failed operation
details: Additional error context
"""
if operation_context:
details = details or {}
details['operation_context'] = operation_context
super().__init__(message, code="CSV_PROC", details=details)
class DataFrameProcessingError(CSVProcessingError):
"""Raised when DataFrame batch operations fail.
This exception provides detailed context about batch processing failures
including operation state and progress information.
"""
def __init__(self, operation: str, batch_state: dict, processed_count: int, total_count: int, details: dict | None = None):
"""Initialize DataFrame processing error.
Args:
operation: Name of the operation that failed
batch_state: Current state of batch processing
processed_count: Number of items processed
total_count: Total number of items to process
details: Additional error context
"""
message = f"DataFrame batch operation '{operation}' failed after processing {processed_count}/{total_count} items"
operation_context = {
'operation': operation,
'batch_state': batch_state,
'processed_count': processed_count,
'total_count': total_count
}
super().__init__(message, operation_context, details)
class ColorFilterError(MTGSetupError):
"""Exception raised when color-specific filtering operations fail.
@ -84,113 +257,330 @@ class ColorFilterError(MTGSetupError):
error_info = f" - {details}" if details else ""
super().__init__(f"{message} for color '{color}'{error_info}")
class CommanderValidationError(MTGSetupError):
"""Exception raised when commander validation fails.
class CSVValidationError(CSVError):
"""Base class for CSV and DataFrame validation errors.
This exception is raised when there are issues validating commander cards,
such as non-legendary creatures, color identity mismatches, or banned cards.
Args:
message: Explanation of the error
validation_type: Type of validation that failed (e.g., 'legendary_check', 'color_identity', 'banned_set')
details: Additional error details
Examples:
>>> raise CommanderValidationError(
... "Card must be legendary",
... "legendary_check",
... "Lightning Bolt is not a legendary creature"
... )
>>> raise CommanderValidationError(
... "Commander color identity mismatch",
... "color_identity",
... "Omnath, Locus of Creation cannot be used in Golgari deck"
... )
>>> raise CommanderValidationError(
... "Commander banned in format",
... "banned_set",
... "Golos, Tireless Pilgrim is banned in Commander"
... )
This exception is used when data fails validation checks, including field validation,
data type validation, and data consistency validation.
"""
def __init__(self, message: str, validation_type: str, details: str = None) -> None:
self.validation_type = validation_type
self.details = details
error_info = f" - {details}" if details else ""
super().__init__(f"{message} [{validation_type}]{error_info}")
class InputValidationError(MTGSetupError):
"""Exception raised when input validation fails.
This exception is raised when there are issues validating user input,
such as invalid text formats, number ranges, or confirmation responses.
Args:
message: Explanation of the error
input_type: Type of input validation that failed (e.g., 'text', 'number', 'confirm')
details: Additional error details
Examples:
>>> raise InputValidationError(
... "Invalid number input",
... "number",
... "Value must be between 1 and 100"
... )
def __init__(self, message: str, validation_context: dict | None = None, details: dict | None = None):
"""Initialize validation error with context.
>>> raise InputValidationError(
... "Invalid confirmation response",
... "confirm",
... "Please enter 'y' or 'n'"
... )
>>> raise InputValidationError(
... "Invalid text format",
... "text",
... "Input contains invalid characters"
... )
Args:
message: Descriptive error message
validation_context: Specific validation failure details
details: Additional error context
"""
if validation_context:
details = details or {}
details['validation_context'] = validation_context
super().__init__(message, code="CSV_VALID", details=details)
class DataFrameValidationError(CSVValidationError):
"""Raised when DataFrame validation fails.
This exception provides detailed context about validation failures including
rule violations, invalid values, and data type mismatches.
"""
def __init__(self, message: str, input_type: str, details: str = None) -> None:
self.input_type = input_type
self.details = details
error_info = f" - {details}" if details else ""
super().__init__(f"{message} [{input_type}]{error_info}")
class PriceCheckError(MTGSetupError):
"""Exception raised when price checking operations fail.
This exception is raised when there are issues retrieving or processing
card prices, such as API failures, invalid responses, or parsing errors.
Args:
message: Explanation of the error
card_name: Name of the card that caused the error
details: Additional error details
Examples:
>>> raise PriceCheckError(
... "Failed to retrieve price",
... "Black Lotus",
... "API request timeout"
... )
def __init__(self, field: str, validation_rules: dict, invalid_data: dict | None = None, details: dict | None = None):
"""Initialize DataFrame validation error.
>>> raise PriceCheckError(
... "Invalid price data format",
... "Lightning Bolt",
... "Unexpected response structure"
... )
>>> raise PriceCheckError(
... "Price data unavailable",
... "Underground Sea",
... "No price information found"
... )
Args:
field: Name of the field that failed validation
validation_rules: Rules that were violated
invalid_data: The invalid data that caused the failure
details: Additional error context
"""
message = f"DataFrame validation failed for field '{field}'"
validation_context = {
'field': field,
'rules': validation_rules,
'invalid_data': invalid_data or {}
}
super().__init__(message, validation_context, details)
class EmptyDataFrameError(CSVError):
"""Raised when a DataFrame is unexpectedly empty.
This exception is used when a DataFrame operation requires non-empty data
but receives an empty DataFrame.
"""
def __init__(self, message: str, card_name: str, details: str = None) -> None:
self.card_name = card_name
self.details = details
error_info = f" - {details}" if details else ""
super().__init__(f"{message} for card '{card_name}'{error_info}")
def __init__(self, operation: str, details: dict | None = None):
"""Initialize empty DataFrame error.
Args:
operation: Name of the operation that requires non-empty data
details: Additional context about the empty DataFrame
"""
message = f"Empty DataFrame encountered during: '{operation}'"
super().__init__(message, code="CSV_EMPTY", details=details)
class CSVTimeoutError(CSVError):
"""Base class for CSV and DataFrame timeout errors.
This exception is used when operations exceed their timeout thresholds.
"""
def __init__(self, message: str, timeout_context: dict | None = None, details: dict | None = None):
"""Initialize timeout error with context.
Args:
message: Descriptive error message
timeout_context: Details about the timeout
details: Additional error context
"""
if timeout_context:
details = details or {}
details['timeout_context'] = timeout_context
super().__init__(message, code="CSV_TIMEOUT", details=details)
class DataFrameTimeoutError(CSVTimeoutError):
"""Raised when DataFrame operations timeout.
This exception provides detailed context about operation timeouts
including operation type and duration information.
"""
def __init__(self, operation: str, timeout: float, elapsed: float, operation_state: dict | None = None, details: dict | None = None):
"""Initialize DataFrame timeout error.
Args:
operation: Name of the operation that timed out
timeout: Timeout threshold in seconds
elapsed: Actual time elapsed in seconds
operation_state: State of the operation when timeout occurred
details: Additional error context
"""
message = f"DataFrame operation '{operation}' timed out after {elapsed:.1f}s (threshold: {timeout}s)"
timeout_context = {
'operation': operation,
'timeout_threshold': timeout,
'elapsed_time': elapsed,
'operation_state': operation_state or {}
}
super().__init__(message, timeout_context, details)
# For PriceCheck/Scrython functions
class PriceError(DeckBuilderError):
"""Base exception class for price-related errors.
This exception serves as the base for all price-related errors in the deck builder,
including API issues, validation errors, and price limit violations.
Attributes:
code (str): Error code for identifying the error type
message (str): Descriptive error message
details (dict): Additional error context and details
"""
def __init__(self, message: str, code: str = "PRICE_ERR", details: dict | None = None):
"""Initialize the base price error.
Args:
message: Human-readable error description
code: Error code for identification and handling
details: Additional context about the error
"""
super().__init__(message, code=code, details=details)
class PriceAPIError(PriceError):
"""Raised when there are issues with the Scryfall API price lookup.
This exception is used when the price API request fails, returns invalid data,
or encounters other API-related issues.
"""
def __init__(self, card_name: str, details: dict | None = None):
"""Initialize price API error.
Args:
card_name: Name of the card that failed price lookup
details: Additional context about the API failure
"""
message = f"Failed to fetch price data for '{card_name}' from Scryfall API"
super().__init__(message, code="PRICE_API", details=details)
class PriceLimitError(PriceError):
"""Raised when a card or deck price exceeds the specified limit.
This exception is used when price thresholds are violated during deck building.
"""
def __init__(self, card_name: str, price: float, limit: float, details: dict | None = None):
"""Initialize price limit error.
Args:
card_name: Name of the card exceeding the price limit
price: Actual price of the card
limit: Maximum allowed price
details: Additional context about the price limit violation
"""
message = f"Price of '{card_name}' (${price:.2f}) exceeds limit of ${limit:.2f}"
super().__init__(message, code="PRICE_LIMIT", details=details)
class PriceTimeoutError(PriceError):
"""Raised when a price lookup request times out.
This exception is used when the Scryfall API request exceeds the timeout threshold.
"""
def __init__(self, card_name: str, timeout: float, details: dict | None = None):
"""Initialize price timeout error.
Args:
card_name: Name of the card that timed out
timeout: Timeout threshold in seconds
details: Additional context about the timeout
"""
message = f"Price lookup for '{card_name}' timed out after {timeout} seconds"
super().__init__(message, code="PRICE_TIMEOUT", details=details)
class PriceValidationError(PriceError):
"""Raised when price data fails validation.
This exception is used when received price data is invalid, malformed,
or cannot be properly parsed.
"""
def __init__(self, card_name: str, price_data: str, details: dict | None = None):
"""Initialize price validation error.
Args:
card_name: Name of the card with invalid price data
price_data: The invalid price data received
details: Additional context about the validation failure
"""
message = f"Invalid price data for '{card_name}': {price_data}"
super().__init__(message, code="PRICE_INVALID", details=details)
# Commander Exceptions
class CommanderLoadError(DeckBuilderError):
"""Raised when there are issues loading commander data from CSV.
This exception is used when the commander CSV file cannot be loaded,
is missing required columns, or contains invalid data.
"""
def __init__(self, message: str, details: dict | None = None):
"""Initialize commander load error.
Args:
message: Description of the loading failure
details: Additional context about the error
"""
super().__init__(message, code="CMD_LOAD", details=details)
class CommanderSelectionError(DeckBuilderError):
"""Raised when there are issues with the commander selection process.
This exception is used when the commander selection process fails,
such as no matches found or ambiguous matches.
"""
def __init__(self, message: str, details: dict | None = None):
"""Initialize commander selection error.
Args:
message: Description of the selection failure
details: Additional context about the error
"""
super().__init__(message, code="CMD_SELECT", details=details)
class CommanderValidationError(DeckBuilderError):
"""Raised when commander data fails validation.
This exception is used when the selected commander's data is invalid,
missing required fields, or contains inconsistent information.
"""
def __init__(self, message: str, details: dict | None = None):
"""Initialize commander validation error.
Args:
message: Description of the validation failure
details: Additional context about the error
"""
super().__init__(message, code="CMD_VALID", details=details)
class CommanderTypeError(CommanderValidationError):
"""Raised when commander type validation fails.
This exception is used when a commander fails the legendary creature requirement
or has an invalid creature type.
"""
def __init__(self, message: str, details: dict | None = None):
"""Initialize commander type error.
Args:
message: Description of the type validation failure
details: Additional context about the error
"""
super().__init__(message, code="CMD_TYPE_ERR", details=details)
class CommanderStatsError(CommanderValidationError):
"""Raised when commander stats validation fails.
This exception is used when a commander's power, toughness, or mana value
fails validation requirements.
"""
def __init__(self, message: str, details: dict | None = None):
"""Initialize commander stats error.
Args:
message: Description of the stats validation failure
details: Additional context about the error
"""
super().__init__(message, code="CMD_STATS_ERR", details=details)
class CommanderColorError(CommanderValidationError):
"""Raised when commander color identity validation fails.
This exception is used when a commander's color identity is invalid
or incompatible with deck requirements.
"""
def __init__(self, message: str, details: dict | None = None):
"""Initialize commander color error.
Args:
message: Description of the color validation failure
details: Additional context about the error
"""
super().__init__(message, code="CMD_COLOR_ERR", details=details)
class CommanderTagError(CommanderValidationError):
"""Raised when commander theme tag validation fails.
This exception is used when a commander's theme tags are invalid
or incompatible with deck requirements.
"""
def __init__(self, message: str, details: dict | None = None):
"""Initialize commander tag error.
Args:
message: Description of the tag validation failure
details: Additional context about the error
"""
super().__init__(message, code="CMD_TAG_ERR", details=details)
class CommanderThemeError(CommanderValidationError):
"""Raised when commander theme validation fails.
This exception is used when a commander's themes are invalid
or incompatible with deck requirements.
"""
def __init__(self, message: str, details: dict | None = None):
"""Initialize commander theme error.
Args:
message: Description of the theme validation failure
details: Additional context about the error
"""
super().__init__(message, code="CMD_THEME_ERR", details=details)