from __future__ import annotations import json import os from functools import lru_cache from typing import List, Tuple, Dict, Optional from django.conf import settings def _json_path(filename: str) -> str: return os.path.join(settings.BASE_DIR, 'static', 'json', filename) @lru_cache(maxsize=1) def load_countries() -> List[dict]: with open(_json_path('countries.json'), 'r', encoding='utf-8') as f: return json.load(f) @lru_cache(maxsize=1) def load_states() -> List[dict]: with open(_json_path('states.json'), 'r', encoding='utf-8') as f: return json.load(f) @lru_cache(maxsize=1) def load_cities() -> List[dict]: with open(_json_path('cities.json'), 'r', encoding='utf-8') as f: return json.load(f) @lru_cache(maxsize=1) def _index_states_by_country() -> Dict[str, List[Tuple[str, str]]]: idx: Dict[str, List[Tuple[str, str]]] = {} for s in load_states(): cc = s.get('country_code') code = s.get('iso2') or s.get('state_code') name = s.get('name') if not (cc and code and name): continue idx.setdefault(cc, []).append((str(code), name)) return idx @lru_cache(maxsize=1) def _index_cities_by_country_state() -> Dict[Tuple[str, str], List[Tuple[str, str]]]: idx: Dict[Tuple[str, str], List[Tuple[str, str]]] = {} for c in load_cities(): cc = c.get('country_code') sc = (c.get('state_code') or '').upper() name = c.get('name') if not (cc and sc and name): continue key = (cc, sc) idx.setdefault(key, []).append((name, name)) return idx def country_label(code: str) -> Optional[str]: code = (code or '').upper() for c in load_countries(): if c.get('iso2') == code: return c.get('name') return None def state_label(country_code: str, state_code: str) -> Optional[str]: cc = (country_code or '').upper() sc = (state_code or '').upper() for s in load_states(): if s.get('country_code') == cc and s.get('iso2', '').upper() == sc: return s.get('name') if s.get('country_code') == cc and s.get('state_code', '').upper() == sc: return s.get('name') return None def list_countries(q: str | None = None) -> List[Tuple[str, str]]: items: List[Tuple[str, str]] = [] ql = (q or '').strip().lower() for c in load_countries(): code = c.get('iso2') name = c.get('name') if not code or not name: continue if ql and ql not in name.lower() and ql not in code.lower(): continue items.append((code, name)) return items def list_states(country_code: str | None, q: str | None = None) -> List[Tuple[str, str]]: items: List[Tuple[str, str]] = [] if not country_code: return items cc = country_code.upper() ql = (q or '').strip().lower() pool = _index_states_by_country().get(cc, []) if not ql: return list(pool) for code, name in pool: if ql and ql not in name.lower() and ql not in (code or '').lower(): continue items.append((code, name)) return items def list_cities(country_code: str | None, state_code: str | None, q: str | None = None) -> List[Tuple[str, str]]: items: List[Tuple[str, str]] = [] if not (country_code and state_code): return items cc = country_code.upper() sc = state_code.upper() ql = (q or '').strip().lower() pool = _index_cities_by_country_state().get((cc, sc), []) if not ql: # Return first 50 to avoid huge payloads without query return pool[:50] for code, name in pool: if ql in name.lower(): items.append((code, name)) return items