1 Commits
Author SHA1 Message Date
dsql a5b91bed0d fix: state slug uses underscore + ascii-fold to match the proxy region- contract
addr.geo.fetch_location produced a hyphen slug with no unicode fold (new-york / québec), but the live proxy region- token the original utils.py fed expects underscore + ascii-fold (new_york / quebec). a multi-word state silently routed to an unrecognized region with no error. added _state_slug (NFKD ascii-fold, lowercase, spaces->underscore) and routed _parse_reverse through it. bump to v0.2.2.

Signed-off-by: disqualifier <dev@disqualifier.me>
2026-06-29 03:25:14 -04:00
4 changed files with 12 additions and 5 deletions
+2 -2
View File
@@ -12,9 +12,9 @@ Small sync helpers shared across projects. Base is stdlib only — **no dependen
## Install ## Install
``` ```
commons @ git+ssh://git@git.rethinkstudios.io/rethink-public/commons.git@v0.2.1 commons @ git+ssh://git@git.rethinkstudios.io/rethink-public/commons.git@v0.2.2
# async address/geo lookups (fetch_ip / ip_location / fetch_location) need the extra: # 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.1 commons[addr] @ git+ssh://git@git.rethinkstudios.io/rethink-public/commons.git@v0.2.2
``` ```
The base install pulls **nothing** (stdlib). Only `commons[addr]` adds `aiohttp`, and The base install pulls **nothing** (stdlib). Only `commons[addr]` adds `aiohttp`, and
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project] [project]
name = "commons" name = "commons"
version = "0.2.1" version = "0.2.2"
description = "small stdlib-only sync helpers: time/timezone deltas, dotted-path dict access, display masking, ip/address tooling, and retry/backoff" 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" requires-python = ">=3.10"
dependencies = [] dependencies = []
+1 -1
View File
@@ -63,4 +63,4 @@ __all__ = [
"aretry", "aretry",
] ]
__version__ = "0.2.1" __version__ = "0.2.2"
+8 -1
View File
@@ -12,6 +12,7 @@ security: the only secret is geo.ipify's `api_key`, which is a REQUIRED keyword
`ip_location` — the caller injects it. nothing is hardcoded here. `ip_location` — the caller injects it. nothing is hardcoded here.
""" """
import logging import logging
import unicodedata
from typing import Optional from typing import Optional
from urllib.parse import urlencode from urllib.parse import urlencode
@@ -55,6 +56,12 @@ def _parse_ipify(data: dict) -> Optional[str]:
return ip or None return ip or None
def _state_slug(state: str) -> str:
"""lowercase ascii-folded state slug with underscores, matching the live proxy region- contract"""
folded = unicodedata.normalize("NFKD", state).encode("ascii", "ignore").decode("ascii")
return folded.lower().replace(" ", "_")
def _parse_reverse(data: dict) -> Optional[dict]: def _parse_reverse(data: dict) -> Optional[dict]:
"""parse a nominatim reverse response into {country: iso2 lower, state: slug|None}""" """parse a nominatim reverse response into {country: iso2 lower, state: slug|None}"""
address = data.get("address") address = data.get("address")
@@ -66,7 +73,7 @@ def _parse_reverse(data: dict) -> Optional[dict]:
state = address.get("state") state = address.get("state")
return { return {
"country": str(country).lower(), "country": str(country).lower(),
"state": state.lower().replace(" ", "-") if state else None, "state": _state_slug(state) if state else None,
} }