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>
This commit is contained in:
parent
83a156fd31
commit
912c37f99f
@ -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
|
||||||
|
|||||||
28
ledger.md
Normal file
28
ledger.md
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
# commons — ledger
|
||||||
|
|
||||||
|
## v0.2.2
|
||||||
|
|
||||||
|
- **fidelity fix (HIGH):** `addr.geo.fetch_location` state slug now uses an underscore
|
||||||
|
separator and an NFKD ascii-fold, matching the original `utils.py` contract the live
|
||||||
|
proxy `region-` token depends on (`New York` → `new_york`, `Québec` → `quebec`). The
|
||||||
|
rewrite had produced a hyphen slug with no fold (`new-york`), which silently routed
|
||||||
|
multi-word states to an unrecognized region. Added `_state_slug`; routed
|
||||||
|
`_parse_reverse` through it.
|
||||||
|
|
||||||
|
## v0.2.1
|
||||||
|
|
||||||
|
- floor `retry`/`aretry` `attempts` at 1 (was: `attempts=0` silently returned None).
|
||||||
|
- retry log uses total `attempts` as the denominator (was `attempts-1` → `retry 1/2`).
|
||||||
|
- geo lookups forward the per-request `timeout` to `session.get` on the injected-session
|
||||||
|
path (was honored only on the owned-session path).
|
||||||
|
|
||||||
|
## v0.2.0
|
||||||
|
|
||||||
|
- added the `retry` module: `retry` (sync) + `aretry` (async), call-form or decorator,
|
||||||
|
exponential backoff with optional full jitter, `on=` narrowing, `give_up` early-stop,
|
||||||
|
fail-loud re-raise of the last exception.
|
||||||
|
|
||||||
|
## v0.1.0
|
||||||
|
|
||||||
|
- initial: timing (unix deltas + tz Clock), paths (dotted deep_get/set), masking
|
||||||
|
(card/cvv/token display), addr (ip utils + geo behind the `[addr]` extra).
|
||||||
@ -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 = []
|
||||||
|
|||||||
@ -63,4 +63,4 @@ __all__ = [
|
|||||||
"aretry",
|
"aretry",
|
||||||
]
|
]
|
||||||
|
|
||||||
__version__ = "0.2.1"
|
__version__ = "0.2.2"
|
||||||
|
|||||||
@ -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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user