fix: harden Photon client + consistent lifespan settings (#125, #126, #127)#131
Conversation
…127) - _candidates no longer combines street+postcode when city is empty, matching the documented invariant; drops a wasted Photon round-trip (#125) - _query parses coordinates inside the try/except and catches (TypeError, IndexError), so a non-Point feature yields None instead of propagating a 500 — honouring the 'never raises' contract (#126) - lifespan reads _config.settings.* for the NUTS-polygon block, matching the adjacent Photon/token blocks so a monkeypatch can't desync PIP vs geocoder (#127) Adds no-city and non-Point-geometry regression tests.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 867e4c285d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| out.append(f"{street}, {postal_code}") | ||
| if postal_code: | ||
| out.append(postal_code) | ||
| if street and not city and not postal_code: |
There was a problem hiding this comment.
Preserve street fallback when city is missing
When /resolve is called with a street but no city, postal_code is still always present, so after removing the street+postcode candidate this condition prevents any candidate containing the street from being sent. The client now queries Photon with only the postcode for these weak postal matches, which can return no_result or a coarse/wrong postcode hit despite the caller supplying street information; add a street-only candidate for this no-city case without combining it with the postcode.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Checked this against the module's premise before acting — I don't think it's a regression, and I'd push back on adding the street-only candidate here.
The candidate this PR removed was f"{street}, {postal_code}" — street combined with the postcode. The whole point of this module (docstring) is that Photon returns 0 results for a street+postcode string. So for a (street, no-city, postcode) input that candidate was always an empty round-trip; postcode-only was already the only query that could return a hit. Removing it changes correctness by zero and just drops the wasted request — which is what #125 asked for. No working street fallback was lost.
What you're proposing is a new street-only candidate (never combined), which is a different thing. I'd hold off on it: for a NUTS region lookup, a street with no city is low-precision — street names repeat across municipalities, so a street-alone Photon hit can land in the wrong city → wrong coordinates → wrong NUTS3. This service deliberately prefers no-result over a wrong region (0.85 geocode threshold, cross-border guard). Postcode-only is the reliable region signal for these no-city records, and "coarse" (postcode centroid) is exactly the right granularity for NUTS3.
If we do want a street-only last-resort, it should be gated and appended after the postcode query, and it's a separate enhancement rather than part of this bugfix. Happy to open an issue for it if you think the coverage is worth the precision risk.
Resolves the three findings from the pre-publish whole-branch review of #123, all in the new Photon geocoding code.
Changes
PhotonClient._candidatesno longer appendsstreet, postal_codeas a candidate whencityis empty. Both docstrings state street and postcode are never combined (Photon returns nothing for that pair); the offline enrich path could pass an emptycityand trigger it, wasting the first Photon round-trip. Docstring "never" claim is now accurate.PhotonClient._querynow parses coordinates inside thetry/exceptand catches(TypeError, IndexError). A non-Point feature has nestedcoordinates, sofloat(coords[1])raisedTypeErrorand propagated a 500 out ofgeocode(), contradicting the documented "never raises → None" contract._config.settings.*(matching the adjacent Photon and token-DB blocks) instead of the module-levelsettings.*, so a monkeypatch of_config.settingswithout a module reload can't desync PIP vs. geocoder.Tests
test_never_combines_street_and_postcode_without_city(the no-city path PhotonClient combines street+postcode when city is empty (violates documented invariant) #125's existing test didn't cover).test_non_point_geometry_returns_none(PhotonClient._query: coordinate parsing outside try/except can raise (breaks 'never raises' contract) #126).tests/test_photon_client.py— 10/10 pass, ruff clean. No new failures vs.main(the shapely-gated suites are unaffected by these changes and run in CI).Closes #125
Closes #126
Closes #127