mirror of
https://github.com/mwisnowski/mtg_python_deckbuilder.git
synced 2025-09-22 04:50:46 +02:00
Fleshed out docstrings, added typings, cleaned up imports, and added a requirements.txt file
Additionally, renamed utility.ty to tag_utils.py to fit the naming pattern used with setup.py and setup.utils.py
This commit is contained in:
parent
000d804ba7
commit
b8d9958564
8 changed files with 592 additions and 466 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -2,6 +2,7 @@
|
|||
*.json
|
||||
*.log
|
||||
*.txt
|
||||
!requirements.txt
|
||||
test.py
|
||||
.mypy_cache/
|
||||
__pycache__/
|
67
main.py
67
main.py
|
@ -1,15 +1,26 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import inquirer.prompt # type: ignore
|
||||
# Standard library imports
|
||||
import sys
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from typing import NoReturn, Optional
|
||||
|
||||
# Third-party imports
|
||||
import inquirer.prompt # type: ignore
|
||||
|
||||
# Local imports
|
||||
import setup
|
||||
import card_info
|
||||
import tagger
|
||||
|
||||
"""Command-line interface for the MTG Python Deckbuilder application.
|
||||
|
||||
This module provides the main menu and user interaction functionality for the
|
||||
MTG Python Deckbuilder. It handles menu display, user input processing, and
|
||||
routing to different application features like setup, deck building, card info
|
||||
lookup and CSV file tagging.
|
||||
"""
|
||||
# Configure logging
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
|
@ -31,8 +42,16 @@ MENU_CHOICES = [MENU_SETUP, MENU_BUILD_DECK, MENU_CARD_INFO, MAIN_TAG, MENU_QUIT
|
|||
def get_menu_choice() -> Optional[str]:
|
||||
"""Display the main menu and get user choice.
|
||||
|
||||
Presents a menu of options to the user using inquirer and returns their selection.
|
||||
Handles potential errors from inquirer gracefully.
|
||||
|
||||
Returns:
|
||||
Optional[str]: The selected menu option or None if cancelled
|
||||
Optional[str]: The selected menu option or None if cancelled/error occurs
|
||||
|
||||
Example:
|
||||
>>> choice = get_menu_choice()
|
||||
>>> if choice == MENU_SETUP:
|
||||
... setup.setup()
|
||||
"""
|
||||
question = [
|
||||
inquirer.List('menu',
|
||||
|
@ -40,14 +59,26 @@ def get_menu_choice() -> Optional[str]:
|
|||
carousel=True)
|
||||
]
|
||||
try:
|
||||
answer = inquirer.prompt(question)
|
||||
answer = inquirer.prompt(question) # type: ignore
|
||||
return answer['menu'] if answer else None
|
||||
except (KeyError, TypeError) as e:
|
||||
logging.error(f"Error getting menu choice: {e}")
|
||||
return None
|
||||
|
||||
def handle_card_info() -> None:
|
||||
"""Handle the card info menu option with proper error handling."""
|
||||
"""Handle the card info menu option with proper error handling.
|
||||
|
||||
Provides an interface for looking up card information repeatedly until the user
|
||||
chooses to stop. Handles potential errors from card info lookup and user input.
|
||||
|
||||
Returns:
|
||||
None
|
||||
|
||||
Example:
|
||||
>>> handle_card_info()
|
||||
Enter card name: Lightning Bolt
|
||||
[Card info displayed]
|
||||
Would you like to look up another card? [y/N]: n
|
||||
"""
|
||||
try:
|
||||
while True:
|
||||
card_info.get_card_info()
|
||||
|
@ -56,7 +87,7 @@ def handle_card_info() -> None:
|
|||
message='Would you like to look up another card?')
|
||||
]
|
||||
try:
|
||||
answer = inquirer.prompt(question)
|
||||
answer = inquirer.prompt(question) # type: ignore
|
||||
if not answer or not answer['continue']:
|
||||
break
|
||||
except (KeyError, TypeError) as e:
|
||||
|
@ -64,9 +95,28 @@ def handle_card_info() -> None:
|
|||
break
|
||||
except Exception as e:
|
||||
logging.error(f"Error in card info handling: {e}")
|
||||
|
||||
def run_menu() -> NoReturn:
|
||||
"""Main menu loop with improved error handling and logging."""
|
||||
"""Main menu loop with improved error handling and logging.
|
||||
|
||||
Provides the main application loop that displays the menu and handles user selections.
|
||||
Creates required directories, processes menu choices, and handles errors gracefully.
|
||||
Never returns normally - exits via sys.exit().
|
||||
|
||||
Returns:
|
||||
NoReturn: Function never returns normally
|
||||
|
||||
Raises:
|
||||
SystemExit: When user selects Quit option
|
||||
|
||||
Example:
|
||||
>>> run_menu()
|
||||
What would you like to do?
|
||||
1. Setup
|
||||
2. Build a Deck
|
||||
3. Get Card Info
|
||||
4. Tag CSV Files
|
||||
5. Quit
|
||||
"""
|
||||
logging.info("Starting MTG Python Deckbuilder")
|
||||
Path('csv_files').mkdir(parents=True, exist_ok=True)
|
||||
|
||||
|
@ -100,6 +150,5 @@ def run_menu() -> NoReturn:
|
|||
|
||||
except Exception as e:
|
||||
logging.error(f"Unexpected error in main menu: {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
run_menu()
|
8
requirements.txt
Normal file
8
requirements.txt
Normal file
|
@ -0,0 +1,8 @@
|
|||
pandas>=1.5.0
|
||||
inquirer>=3.1.3
|
||||
typing-extensions>=4.5.0
|
||||
|
||||
# Development dependencies
|
||||
mypy>=1.3.0
|
||||
pandas-stubs>=2.0.0
|
||||
types-inquirer>=3.1.3
|
16
settings.py
16
settings.py
|
@ -3,8 +3,14 @@
|
|||
This module contains all the constant values and configuration settings used throughout
|
||||
the application for card filtering, processing, and analysis. Constants are organized
|
||||
into logical sections with clear documentation.
|
||||
|
||||
All constants are properly typed according to PEP 484 standards to ensure type safety
|
||||
and enable static type checking with mypy.
|
||||
"""
|
||||
artifact_tokens = ['Blood', 'Clue', 'Food', 'Gold', 'Incubator',
|
||||
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
artifact_tokens: List[str] = ['Blood', 'Clue', 'Food', 'Gold', 'Incubator',
|
||||
'Junk','Map','Powerstone', 'Treasure']
|
||||
|
||||
banned_cards = [# in commander
|
||||
|
@ -33,7 +39,7 @@ basic_lands = ['Plains', 'Island', 'Swamp', 'Mountain', 'Forest']
|
|||
basic_lands = ['Plains', 'Island', 'Swamp', 'Mountain', 'Forest']
|
||||
|
||||
# Constants for lands matter functionality
|
||||
LANDS_MATTER_PATTERNS = {
|
||||
LANDS_MATTER_PATTERNS: Dict[str, List[str]] = {
|
||||
'land_play': [
|
||||
'play a land',
|
||||
'play an additional land',
|
||||
|
@ -661,7 +667,7 @@ DRAW_EXCLUSION_PATTERNS = [
|
|||
]
|
||||
|
||||
# Constants for DataFrame validation and processing
|
||||
REQUIRED_COLUMNS = [
|
||||
REQUIRED_COLUMNS: List[str] = [
|
||||
'name', 'faceName', 'edhrecRank', 'colorIdentity', 'colors',
|
||||
'manaCost', 'manaValue', 'type', 'creatureTypes', 'text',
|
||||
'power', 'toughness', 'keywords', 'themeTags', 'layout', 'side'
|
||||
|
@ -833,7 +839,7 @@ COLOR_ABRV = ['Colorless', 'W', 'U', 'B', 'G', 'R',
|
|||
'B, R, U, W', 'B, G, R, U, W']
|
||||
|
||||
# Configuration for handling null/NA values in DataFrame columns
|
||||
FILL_NA_COLUMNS = {
|
||||
FILL_NA_COLUMNS: Dict[str, Optional[str]] = {
|
||||
'colorIdentity': 'Colorless', # Default color identity for cards without one
|
||||
'faceName': None # Use card's name column value when face name is not available
|
||||
}
|
||||
|
@ -844,7 +850,7 @@ SORT_CONFIG = {
|
|||
}
|
||||
|
||||
# Configuration for DataFrame filtering operations
|
||||
FILTER_CONFIG = {
|
||||
FILTER_CONFIG: Dict[str, Dict[str, List[str]]] = {
|
||||
'layout': {
|
||||
'exclude': ['reversible_card']
|
||||
},
|
||||
|
|
39
setup.py
39
setup.py
|
@ -1,15 +1,26 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from enum import Enum
|
||||
|
||||
import pandas as pd # type: ignore
|
||||
import inquirer.prompt # type: ignore
|
||||
# Standard library imports
|
||||
import logging
|
||||
from enum import Enum
|
||||
from pathlib import Path
|
||||
from typing import Union, List, Dict, Any
|
||||
|
||||
from settings import banned_cards, csv_directory, SETUP_COLORS, COLOR_ABRV, MTGJSON_API_URL
|
||||
from setup_utils import download_cards_csv, filter_dataframe, process_legendary_cards, filter_by_color_identity
|
||||
from exceptions import CSVFileNotFoundError, MTGJSONDownloadError, DataFrameProcessingError, ColorFilterError, CommanderValidationError
|
||||
# Third-party imports
|
||||
import pandas as pd
|
||||
import inquirer
|
||||
|
||||
# Local application imports
|
||||
from settings import (
|
||||
banned_cards, csv_directory, SETUP_COLORS, COLOR_ABRV, MTGJSON_API_URL
|
||||
)
|
||||
from setup_utils import (
|
||||
download_cards_csv, filter_dataframe, process_legendary_cards, filter_by_color_identity
|
||||
)
|
||||
from exceptions import (
|
||||
CSVFileNotFoundError, MTGJSONDownloadError, DataFrameProcessingError,
|
||||
ColorFilterError, CommanderValidationError
|
||||
)
|
||||
# Configure logging
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
|
@ -18,7 +29,7 @@ logging.basicConfig(
|
|||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def check_csv_exists(file_path: str) -> bool:
|
||||
def check_csv_exists(file_path: Union[str, Path]) -> bool:
|
||||
"""Check if a CSV file exists at the specified path.
|
||||
|
||||
Args:
|
||||
|
@ -83,7 +94,7 @@ def initial_setup() -> None:
|
|||
logger.error(f'Error during initial setup: {str(e)}')
|
||||
raise
|
||||
|
||||
def filter_by_color(df: pd.DataFrame, column_name: str, value: str, new_csv_name: str) -> None:
|
||||
def filter_by_color(df: pd.DataFrame, column_name: str, value: str, new_csv_name: Union[str, Path]) -> None:
|
||||
"""Filter DataFrame by color identity and save to CSV.
|
||||
|
||||
Args:
|
||||
|
@ -248,11 +259,11 @@ def _display_setup_menu() -> SetupOption:
|
|||
Returns:
|
||||
SetupOption: The selected menu option
|
||||
"""
|
||||
question = [
|
||||
inquirer.List('menu',
|
||||
choices=[option.value for option in SetupOption],
|
||||
carousel=True)
|
||||
]
|
||||
question: List[Dict[str, Any]] = [
|
||||
inquirer.List(
|
||||
'menu',
|
||||
choices=[option.value for option in SetupOption],
|
||||
carousel=True)]
|
||||
answer = inquirer.prompt(question)
|
||||
return SetupOption(answer['menu'])
|
||||
|
||||
|
|
112
setup_utils.py
112
setup_utils.py
|
@ -1,12 +1,16 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pandas as pd
|
||||
import requests
|
||||
# Standard library imports
|
||||
import logging
|
||||
from tqdm import tqdm
|
||||
import requests
|
||||
from pathlib import Path
|
||||
from typing import List, Optional, Union, Dict, Any
|
||||
from typing import List, Optional, Union, TypedDict
|
||||
|
||||
# Third-party imports
|
||||
import pandas as pd
|
||||
from tqdm import tqdm
|
||||
|
||||
# Local application imports
|
||||
from settings import (
|
||||
CSV_PROCESSING_COLUMNS,
|
||||
CARD_TYPES_TO_EXCLUDE,
|
||||
|
@ -14,26 +18,58 @@ from settings import (
|
|||
LEGENDARY_OPTIONS,
|
||||
FILL_NA_COLUMNS,
|
||||
SORT_CONFIG,
|
||||
FILTER_CONFIG
|
||||
FILTER_CONFIG,
|
||||
)
|
||||
from exceptions import CSVFileNotFoundError, MTGJSONDownloadError, DataFrameProcessingError, ColorFilterError, CommanderValidationError
|
||||
from settings import (
|
||||
CSV_PROCESSING_COLUMNS,
|
||||
CARD_TYPES_TO_EXCLUDE,
|
||||
NON_LEGAL_SETS,
|
||||
LEGENDARY_OPTIONS
|
||||
from exceptions import (
|
||||
MTGJSONDownloadError,
|
||||
DataFrameProcessingError,
|
||||
ColorFilterError,
|
||||
CommanderValidationError
|
||||
)
|
||||
from exceptions import CSVFileNotFoundError, MTGJSONDownloadError, DataFrameProcessingError
|
||||
|
||||
"""MTG Python Deckbuilder setup utilities.
|
||||
|
||||
This module provides utility functions for setting up and managing the MTG Python Deckbuilder
|
||||
application. It handles tasks such as downloading card data, filtering cards by various criteria,
|
||||
and processing legendary creatures for commander format.
|
||||
|
||||
Key Features:
|
||||
- Card data download from MTGJSON
|
||||
- DataFrame filtering and processing
|
||||
- Color identity filtering
|
||||
- Commander validation
|
||||
- CSV file management
|
||||
|
||||
The module integrates with settings.py for configuration and exceptions.py for error handling.
|
||||
"""
|
||||
|
||||
# Type definitions
|
||||
class FilterRule(TypedDict):
|
||||
"""Type definition for filter rules configuration."""
|
||||
exclude: Optional[List[str]]
|
||||
require: Optional[List[str]]
|
||||
|
||||
class FilterConfig(TypedDict):
|
||||
"""Type definition for complete filter configuration."""
|
||||
layout: FilterRule
|
||||
availability: FilterRule
|
||||
promoTypes: FilterRule
|
||||
securityStamp: FilterRule
|
||||
def download_cards_csv(url: str, output_path: Union[str, Path]) -> None:
|
||||
"""Download cards data from MTGJSON and save to CSV.
|
||||
|
||||
Downloads card data from the specified MTGJSON URL and saves it to a local CSV file.
|
||||
Shows a progress bar during download using tqdm.
|
||||
|
||||
Args:
|
||||
url: URL to download cards data from
|
||||
output_path: Path to save the downloaded CSV file
|
||||
url: URL to download cards data from (typically MTGJSON API endpoint)
|
||||
output_path: Path where the downloaded CSV file will be saved
|
||||
|
||||
Raises:
|
||||
MTGJSONDownloadError: If download fails or response is invalid
|
||||
MTGJSONDownloadError: If download fails due to network issues or invalid response
|
||||
|
||||
Example:
|
||||
>>> download_cards_csv('https://mtgjson.com/api/v5/cards.csv', 'cards.csv')
|
||||
"""
|
||||
try:
|
||||
response = requests.get(url, stream=True)
|
||||
|
@ -47,35 +83,49 @@ def download_cards_csv(url: str, output_path: Union[str, Path]) -> None:
|
|||
pbar.update(size)
|
||||
|
||||
except requests.RequestException as e:
|
||||
logging.error(f'Failed to download cards data from {url}')
|
||||
raise MTGJSONDownloadError(
|
||||
"Failed to download cards data",
|
||||
url,
|
||||
getattr(e.response, 'status_code', None) if hasattr(e, 'response') else None
|
||||
) from e
|
||||
|
||||
def check_csv_exists(filepath: Union[str, Path]) -> bool:
|
||||
"""Check if a CSV file exists at the specified path.
|
||||
|
||||
Verifies the existence of a CSV file at the given path. This function is used
|
||||
to determine if card data needs to be downloaded or if it already exists locally.
|
||||
|
||||
Args:
|
||||
filepath: Path to check for CSV file
|
||||
filepath: Path to the CSV file to check
|
||||
|
||||
Returns:
|
||||
True if file exists, False otherwise
|
||||
bool: True if the file exists, False otherwise
|
||||
|
||||
Example:
|
||||
>>> if not check_csv_exists('cards.csv'):
|
||||
... download_cards_csv(MTGJSON_API_URL, 'cards.csv')
|
||||
"""
|
||||
return Path(filepath).is_file()
|
||||
|
||||
def filter_dataframe(df: pd.DataFrame, banned_cards: List[str]) -> pd.DataFrame:
|
||||
"""Apply standard filters to the cards DataFrame using configuration from settings.
|
||||
|
||||
Applies a series of filters to the cards DataFrame based on configuration from settings.py.
|
||||
This includes handling null values, applying basic filters, removing illegal sets and banned cards,
|
||||
and processing special card types.
|
||||
|
||||
Args:
|
||||
df: DataFrame to filter
|
||||
banned_cards: List of banned card names to exclude
|
||||
df: pandas DataFrame containing card data to filter
|
||||
banned_cards: List of card names that are banned and should be excluded
|
||||
|
||||
Returns:
|
||||
Filtered DataFrame
|
||||
pd.DataFrame: A new DataFrame containing only the cards that pass all filters
|
||||
|
||||
Raises:
|
||||
DataFrameProcessingError: If filtering operations fail
|
||||
DataFrameProcessingError: If any filtering operation fails
|
||||
|
||||
Example:
|
||||
>>> filtered_df = filter_dataframe(cards_df, ['Channel', 'Black Lotus'])
|
||||
"""
|
||||
try:
|
||||
logging.info('Starting standard DataFrame filtering')
|
||||
|
@ -89,7 +139,8 @@ def filter_dataframe(df: pd.DataFrame, banned_cards: List[str]) -> pd.DataFrame:
|
|||
|
||||
# Apply basic filters from configuration
|
||||
filtered_df = df.copy()
|
||||
for field, rules in FILTER_CONFIG.items():
|
||||
filter_config: FilterConfig = FILTER_CONFIG # Type hint for configuration
|
||||
for field, rules in filter_config.items():
|
||||
for rule_type, values in rules.items():
|
||||
if rule_type == 'exclude':
|
||||
for value in values:
|
||||
|
@ -126,12 +177,12 @@ def filter_dataframe(df: pd.DataFrame, banned_cards: List[str]) -> pd.DataFrame:
|
|||
return filtered_df
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f'Failed to filter DataFrame: {str(e)}')
|
||||
raise DataFrameProcessingError(
|
||||
"Failed to filter DataFrame",
|
||||
"standard_filtering",
|
||||
str(e)
|
||||
) from e
|
||||
|
||||
def filter_by_color_identity(df: pd.DataFrame, color_identity: str) -> pd.DataFrame:
|
||||
"""Filter DataFrame by color identity with additional color-specific processing.
|
||||
|
||||
|
@ -153,14 +204,6 @@ def filter_by_color_identity(df: pd.DataFrame, color_identity: str) -> pd.DataFr
|
|||
try:
|
||||
logging.info(f'Filtering cards for color identity: {color_identity}')
|
||||
|
||||
# Define processing steps for progress tracking
|
||||
steps = [
|
||||
'Validating color identity',
|
||||
'Applying base filtering',
|
||||
'Filtering by color identity',
|
||||
'Performing color-specific processing'
|
||||
]
|
||||
|
||||
# Validate color identity
|
||||
with tqdm(total=1, desc='Validating color identity') as pbar:
|
||||
if not isinstance(color_identity, str):
|
||||
|
@ -217,11 +260,6 @@ def process_legendary_cards(df: pd.DataFrame) -> pd.DataFrame:
|
|||
"""
|
||||
try:
|
||||
logging.info('Starting commander validation process')
|
||||
validation_steps = [
|
||||
'Checking legendary status',
|
||||
'Validating special cases',
|
||||
'Verifying set legality'
|
||||
]
|
||||
|
||||
filtered_df = df.copy()
|
||||
# Step 1: Check legendary status
|
||||
|
|
|
@ -1,12 +1,28 @@
|
|||
import pandas as pd
|
||||
"""Utility module for tag manipulation and pattern matching in card data processing.
|
||||
|
||||
This module provides a collection of functions for working with card tags, types, and text patterns
|
||||
in a card game context. It includes utilities for:
|
||||
|
||||
- Creating boolean masks for filtering cards based on various criteria
|
||||
- Manipulating and extracting card types
|
||||
- Managing theme tags and card attributes
|
||||
- Pattern matching in card text and types
|
||||
- Mass effect detection (damage, removal, etc.)
|
||||
|
||||
The module is designed to work with pandas DataFrames containing card data and provides
|
||||
vectorized operations for efficient processing of large card collections.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
# Standard library imports
|
||||
import re
|
||||
import logging
|
||||
from typing import List, Set, Union, Any
|
||||
|
||||
from typing import Dict, List, Optional, Set, Union
|
||||
from time import perf_counter
|
||||
# Third-party imports
|
||||
import pandas as pd
|
||||
|
||||
# Local application imports
|
||||
import settings
|
||||
|
||||
def pluralize(word: str) -> str:
|
||||
"""Convert a word to its plural form using basic English pluralization rules.
|
||||
|
||||
|
@ -25,7 +41,7 @@ def pluralize(word: str) -> str:
|
|||
else:
|
||||
return word + 's'
|
||||
|
||||
def sort_list(items: Union[List, pd.Series]) -> Union[List, pd.Series]:
|
||||
def sort_list(items: Union[List[Any], pd.Series]) -> Union[List[Any], pd.Series]:
|
||||
"""Sort a list or pandas Series in ascending order.
|
||||
|
||||
Args:
|
||||
|
@ -38,7 +54,7 @@ def sort_list(items: Union[List, pd.Series]) -> Union[List, pd.Series]:
|
|||
return sorted(items) if isinstance(items, list) else items.sort_values()
|
||||
return items
|
||||
|
||||
def create_type_mask(df: pd.DataFrame, type_text: Union[str, List[str]], regex: bool = True) -> pd.Series:
|
||||
def create_type_mask(df: pd.DataFrame, type_text: Union[str, List[str]], regex: bool = True) -> pd.Series[bool]:
|
||||
"""Create a boolean mask for rows where type matches one or more patterns.
|
||||
|
||||
Args:
|
||||
|
@ -68,7 +84,7 @@ def create_type_mask(df: pd.DataFrame, type_text: Union[str, List[str]], regex:
|
|||
masks = [df['type'].str.contains(p, case=False, na=False, regex=False) for p in type_text]
|
||||
return pd.concat(masks, axis=1).any(axis=1)
|
||||
|
||||
def create_text_mask(df: pd.DataFrame, type_text: Union[str, List[str]], regex: bool = True, combine_with_or: bool = True) -> pd.Series:
|
||||
def create_text_mask(df: pd.DataFrame, type_text: Union[str, List[str]], regex: bool = True, combine_with_or: bool = True) -> pd.Series[bool]:
|
||||
"""Create a boolean mask for rows where text matches one or more patterns.
|
||||
|
||||
Args:
|
||||
|
@ -102,7 +118,7 @@ def create_text_mask(df: pd.DataFrame, type_text: Union[str, List[str]], regex:
|
|||
else:
|
||||
return pd.concat(masks, axis=1).all(axis=1)
|
||||
|
||||
def create_keyword_mask(df: pd.DataFrame, type_text: Union[str, List[str]], regex: bool = True) -> pd.Series:
|
||||
def create_keyword_mask(df: pd.DataFrame, type_text: Union[str, List[str]], regex: bool = True) -> pd.Series[bool]:
|
||||
"""Create a boolean mask for rows where keyword text matches one or more patterns.
|
||||
|
||||
Args:
|
||||
|
@ -146,7 +162,8 @@ def create_keyword_mask(df: pd.DataFrame, type_text: Union[str, List[str]], rege
|
|||
else:
|
||||
masks = [keywords.str.contains(p, case=False, na=False, regex=False) for p in type_text]
|
||||
return pd.concat(masks, axis=1).any(axis=1)
|
||||
def create_name_mask(df: pd.DataFrame, type_text: Union[str, List[str]], regex: bool = True) -> pd.Series:
|
||||
|
||||
def create_name_mask(df: pd.DataFrame, type_text: Union[str, List[str]], regex: bool = True) -> pd.Series[bool]:
|
||||
"""Create a boolean mask for rows where name matches one or more patterns.
|
||||
|
||||
Args:
|
||||
|
@ -229,7 +246,7 @@ def add_outlaw_type(types: List[str], outlaw_types: List[str]) -> List[str]:
|
|||
return types + ['Outlaw']
|
||||
return types
|
||||
|
||||
def create_tag_mask(df: pd.DataFrame, tag_patterns: Union[str, List[str]], column: str = 'themeTags') -> pd.Series:
|
||||
def create_tag_mask(df: pd.DataFrame, tag_patterns: Union[str, List[str]], column: str = 'themeTags') -> pd.Series[bool]:
|
||||
"""Create a boolean mask for rows where tags match specified patterns.
|
||||
|
||||
Args:
|
||||
|
@ -272,7 +289,7 @@ def validate_dataframe_columns(df: pd.DataFrame, required_columns: Set[str]) ->
|
|||
if missing:
|
||||
raise ValueError(f"Missing required columns: {missing}")
|
||||
|
||||
def apply_tag_vectorized(df: pd.DataFrame, mask: pd.Series, tags: List[str]) -> None:
|
||||
def apply_tag_vectorized(df: pd.DataFrame, mask: pd.Series[bool], tags: Union[str, List[str]]) -> None:
|
||||
"""Apply tags to rows in a dataframe based on a boolean mask.
|
||||
|
||||
Args:
|
||||
|
@ -288,7 +305,8 @@ def apply_tag_vectorized(df: pd.DataFrame, mask: pd.Series, tags: List[str]) ->
|
|||
|
||||
# Add new tags
|
||||
df.loc[mask, 'themeTags'] = current_tags.apply(lambda x: sorted(list(set(x + tags))))
|
||||
def create_mass_effect_mask(df: pd.DataFrame, effect_type: str) -> pd.Series:
|
||||
|
||||
def create_mass_effect_mask(df: pd.DataFrame, effect_type: str) -> pd.Series[bool]:
|
||||
"""Create a boolean mask for cards with mass removal effects of a specific type.
|
||||
|
||||
Args:
|
||||
|
@ -306,6 +324,7 @@ def create_mass_effect_mask(df: pd.DataFrame, effect_type: str) -> pd.Series:
|
|||
|
||||
patterns = settings.BOARD_WIPE_TEXT_PATTERNS[effect_type]
|
||||
return create_text_mask(df, patterns)
|
||||
|
||||
def create_damage_pattern(number: Union[int, str]) -> str:
|
||||
"""Create a pattern for matching X damage effects.
|
||||
|
||||
|
@ -316,7 +335,8 @@ def create_damage_pattern(number: Union[int, str]) -> str:
|
|||
Pattern string for matching damage effects
|
||||
"""
|
||||
return f'deals {number} damage'
|
||||
def create_mass_damage_mask(df: pd.DataFrame) -> pd.Series:
|
||||
|
||||
def create_mass_damage_mask(df: pd.DataFrame) -> pd.Series[bool]:
|
||||
"""Create a boolean mask for cards with mass damage effects.
|
||||
|
||||
Args:
|
Loading…
Add table
Add a link
Reference in a new issue