"""Utility functions for UI helpers.""" import re from typing import Any import pandas as pd import pycountry import streamlit as st def init_session_state(key: str, default_value: Any) -> None: """ Initialize session state key if not exists. Args: key: Session state key name default_value: Default value to set """ if key not in st.session_state: st.session_state[key] = default_value def format_datetime(dt: pd.Timestamp | None) -> str: """ Format datetime for display. Args: dt: Datetime to format Returns: Formatted datetime string or empty string """ if pd.isna(dt) or dt is None: return "" return dt.strftime("%Y-%m-%d %H:%M:%S") def create_pagination_controls( total_rows: int, page_size: int = 100, key_prefix: str = "" ) -> tuple[int, int]: """ Create pagination controls and return limit/offset. Args: total_rows: Total number of rows page_size: Number of rows per page key_prefix: Prefix for session state keys to avoid conflicts Returns: Tuple of (limit, offset) for query """ # Initialize current page in session state page_key = f"{key_prefix}current_page" if page_key not in st.session_state: st.session_state[page_key] = 0 total_pages = max(1, (total_rows + page_size - 1) // page_size) current_page = st.session_state[page_key] # Ensure current page is valid if current_page >= total_pages: current_page = total_pages - 1 st.session_state[page_key] = current_page # Create pagination UI col1, col2, col3 = st.columns([1, 3, 1]) with col1: if st.button("← Previous", disabled=current_page == 0): st.session_state[page_key] = max(0, current_page - 1) st.rerun() with col2: st.write( f"Page {current_page + 1} of {total_pages} " f"(Showing {page_size} rows per page)" ) with col3: if st.button("Next →", disabled=current_page >= total_pages - 1): st.session_state[page_key] = min(total_pages - 1, current_page + 1) st.rerun() offset = current_page * page_size return page_size, offset def validate_country_code(code: str) -> tuple[bool, str | None]: """ Validate ISO Alpha-2 country code using pycountry. Args: code: Country code to validate (e.g., 'US', 'GB', 'JP') Returns: Tuple of (is_valid: bool, country_name: str | None) - is_valid: True if code is valid ISO Alpha-2 country code - country_name: Official country name if valid, None otherwise Examples: >>> validate_country_code("US") (True, "United States") >>> validate_country_code("GB") (True, "United Kingdom") >>> validate_country_code("XX") (False, None) """ if not code or len(code) != 2 or not code.isalpha() or not code.isupper(): return False, None try: country = pycountry.countries.get(alpha_2=code) if country: return True, country.name return False, None except (KeyError, AttributeError): return False, None def search_countries(query: str) -> list[tuple[str, str]]: """ Search for countries by name (partial match). Args: query: Search query (country name or partial name) Returns: List of tuples (country_code, country_name) matching the query Sorted by country name Examples: >>> search_countries("united") [('AE', 'United Arab Emirates'), ('GB', 'United Kingdom'), ('US', 'United States')] >>> search_countries("ukr") [('UA', 'Ukraine')] """ if not query or len(query) < 2: return [] query_lower = query.lower() results = [] for country in pycountry.countries: # Search in official name if query_lower in country.name.lower(): results.append((country.alpha_2, country.name)) # Sort by name results.sort(key=lambda x: x[1]) return results[:20] # Limit to top 20 results def parse_spotify_id(input_str: str) -> str: """ Parse Spotify Artist ID from various input formats. Supports three formats: 1. Spotify URL with query params: https://open.spotify.com/artist/7tNO3vJC9zlHy2IJOx34ga?si=... 2. Spotify URL without params: https://open.spotify.com/artist/7tNO3vJC9zlHy2IJOx34ga 3. Spotify URI: spotify:artist:4OGiMt96TFUKkKWf7Imlno 4. Plain ID: 7tNO3vJC9zlHy2IJOx34ga Args: input_str: Input string containing Spotify artist ID in any format Returns: Extracted Spotify artist ID (22-character alphanumeric string) Returns original string if parsing fails Examples: >>> parse_spotify_id("https://open.spotify.com/artist/7tNO3vJC9zlHy2IJOx34ga?si=...") "7tNO3vJC9zlHy2IJOx34ga" >>> parse_spotify_id("spotify:artist:4OGiMt96TFUKkKWf7Imlno") "4OGiMt96TFUKkKWf7Imlno" >>> parse_spotify_id("7tNO3vJC9zlHy2IJOx34ga") "7tNO3vJC9zlHy2IJOx34ga" """ if not input_str: return input_str input_str = input_str.strip() # Pattern 1: URL format (with or without query params) # https://open.spotify.com/artist/7tNO3vJC9zlHy2IJOx34ga?si=... # https://open.spotify.com/artist/7tNO3vJC9zlHy2IJOx34ga url_match = re.search(r"open\.spotify\.com/artist/([a-zA-Z0-9]{22})", input_str) if url_match: return url_match.group(1) # Pattern 2: URI format # spotify:artist:4OGiMt96TFUKkKWf7Imlno uri_match = re.search(r"spotify:artist:([a-zA-Z0-9]{22})", input_str) if uri_match: return uri_match.group(1) # Pattern 3: Already a plain ID (22 alphanumeric characters) if re.match(r"^[a-zA-Z0-9]{22}$", input_str): return input_str # If no pattern matches, return original string return input_str def format_roster_type_display(roster_type: str | None) -> str: """ Convert technical roster type to user-friendly display name. Args: roster_type: Technical roster type from database (MAIN_REP, LOCAL_REP, MAIN, LOCAL, etc.) Returns: User-friendly display name Examples: >>> format_roster_type_display("MAIN_REP") "Rep Owner" >>> format_roster_type_display("LOCAL_REP") "IRL Rep" >>> format_roster_type_display("MAIN") "Rep Owner" >>> format_roster_type_display("LOCAL") "IRL Rep" """ if not roster_type: return "" mapping = { "MAIN_REP": "Rep Owner", "LOCAL_REP": "IRL Rep", "MAIN": "Rep Owner", "LOCAL": "IRL Rep", } return mapping.get(roster_type, roster_type)