diff --git a/README.md b/README.md index 6176c1f..ce3db0c 100644 --- a/README.md +++ b/README.md @@ -12,9 +12,9 @@ Small sync helpers shared across projects. Base is stdlib only — **no dependen ## Install ``` -commons @ git+ssh://git@git.rethinkstudios.io/rethink-public/commons.git@v0.2.2 +commons @ git+ssh://git@git.rethinkstudios.io/rethink-public/commons.git@v0.2.3 # async address/geo lookups (fetch_ip / ip_location / fetch_location) need the extra: -commons[addr] @ git+ssh://git@git.rethinkstudios.io/rethink-public/commons.git@v0.2.2 +commons[addr] @ git+ssh://git@git.rethinkstudios.io/rethink-public/commons.git@v0.2.3 ``` The base install pulls **nothing** (stdlib). Only `commons[addr]` adds `aiohttp`, and diff --git a/pyproject.toml b/pyproject.toml index 3fde378..cc3de55 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "commons" -version = "0.2.2" +version = "0.2.3" description = "small stdlib-only sync helpers: time/timezone deltas, dotted-path dict access, display masking, ip/address tooling, and retry/backoff" requires-python = ">=3.10" dependencies = [] diff --git a/src/commons/__init__.py b/src/commons/__init__.py index fc43732..d4c8e05 100644 --- a/src/commons/__init__.py +++ b/src/commons/__init__.py @@ -63,4 +63,4 @@ __all__ = [ "aretry", ] -__version__ = "0.2.2" +__version__ = "0.2.3" diff --git a/src/commons/addr/geo.py b/src/commons/addr/geo.py index 810d67b..4d11443 100644 --- a/src/commons/addr/geo.py +++ b/src/commons/addr/geo.py @@ -52,18 +52,22 @@ def _reverse_url(lat: float, lon: float) -> str: def _parse_ipify(data: dict) -> Optional[str]: """pull the ip string out of an ipify response""" + if not isinstance(data, dict): + return None ip = data.get("ip") return ip or None def _state_slug(state: str) -> str: - """lowercase ascii-folded state slug with underscores, matching the live proxy region- contract""" + """lowercase ascii-folded state slug with underscores (e.g. 'New York' -> 'new_york')""" folded = unicodedata.normalize("NFKD", state).encode("ascii", "ignore").decode("ascii") return folded.lower().replace(" ", "_") def _parse_reverse(data: dict) -> Optional[dict]: """parse a nominatim reverse response into {country: iso2 lower, state: slug|None}""" + if not isinstance(data, dict): + return None address = data.get("address") if not isinstance(address, dict): return None @@ -84,10 +88,10 @@ async def _get_json( if not _HAVE_AIOHTTP: raise RuntimeError(_MISSING) owns = session is None + request_timeout = aiohttp.ClientTimeout(total=timeout) if owns: - session = aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=timeout)) + session = aiohttp.ClientSession(timeout=request_timeout) try: - request_timeout = aiohttp.ClientTimeout(total=timeout) async with session.get(url, headers=headers, timeout=request_timeout) as resp: if resp.status != 200: log.warning("address lookup %s -> %s", url, resp.status)