Source code for fastrag.serve.geolocalization.resolver

"""IP → country resolution against an operator-supplied GeoLite2 database.

Country resolution is an optional concern layered on top of client IP
resolution (``fastrag.serve._net.resolve_client_ip``): it needs nothing from
the request except that IP, reads an operator-supplied MaxMind GeoLite2
database, and degrades to ``None`` whenever anything about that is
unavailable — no database configured, one that can't be opened, the
``geoip2`` extra not installed, or an address that was never going to be
found in it (private/loopback).
"""

from __future__ import annotations

import ipaddress
import logging
from pathlib import Path

logger = logging.getLogger("fastrag.serve")


[docs] class CountryResolver: """Resolves an IP to an ISO-3166 alpha-2 country code, or ``None``. Built once per app (see ``app.py``) from ``ServeConfig.geoip_database_path`` and closed by the app's lifespan on shutdown. Every failure mode — no path configured, the ``geoip2`` extra not installed, the database file missing or unreadable, the address not present in it, or a private/loopback address that was never going to be — degrades silently to ``None`` rather than failing the request. Only the "a path was given but couldn't be opened" case is worth an operator's attention, and it is logged at most once per resolver, not once per request. """ def __init__(self, database_path: Path | str | None) -> None: """Open ``database_path``, or stay disabled when it is ``None``.""" self._reader = None self._warned = False if database_path is None: return try: import geoip2.database except ImportError: self._warn( f"ServeConfig.geoip_database_path is set ({database_path}) but " "the `geoip2` package is not installed; install fastrag's " "`geoip` extra to enable country resolution." ) return try: self._reader = geoip2.database.Reader(str(database_path)) except OSError as exc: self._warn(f"Could not open GeoIP database at {database_path}: {exc}") def _warn(self, message: str) -> None: if not self._warned: logger.warning(message) self._warned = True
[docs] def lookup(self, ip_address: str | None) -> str | None: """Return an ISO-3166 alpha-2 country code for ``ip_address``, or ``None``.""" if self._reader is None or ip_address is None: return None try: parsed = ipaddress.ip_address(ip_address) except ValueError: return None if parsed.is_private or parsed.is_loopback: return None from geoip2.errors import AddressNotFoundError try: response = self._reader.country(ip_address) except AddressNotFoundError: return None except Exception as exc: # noqa: BLE001 - any lookup failure degrades to None self._warn(f"GeoIP lookup failed for {ip_address}: {exc}") return None return response.country.iso_code
[docs] def close(self) -> None: """Release the database reader. Idempotent; later lookups return ``None``.""" if self._reader is not None: self._reader.close() self._reader = None