What
iter_features gained return_geometry and out_sr in #75, which closed most of the consuming project's gap 2. It hard-codes the output format:
query: dict[str, Any] = {
...
"f": "json",
}
This asks for an output-format argument alongside them, defaulting to "json" so the request this project makes by default stays byte-for-byte what it has always been and the pinned retrievals in sources.py stay reproducible.
Why it matters
wildfire-service-territory-overlap consumes this package under a pinned commit, and its docs/UPSTREAM.md gap 2 asked for "geometry, output format and output spatial reference as arguments to fetch_layer, or the offset loop factored out". #75 delivered the offset loop and two of the three arguments. Re-audited at the pin 3a6aa47 on 2026-09-07, the gap is still open on the third, and it is the one that keeps the duplication alive.
That project reads three polygon layers, the two CEC service-territory layers and a county boundary layer, as f=geojson and writes them as GeoJSON feature collections, which is what its geometry.py consumes. iter_features yields GeoServices features, {"attributes": ..., "geometry": ...} with Esri rings. Routing those layers through it would mean converting Esri geometry to GeoJSON in the consumer, which is more re-implementation than the offset loop it would retire, and it would be conversion of the geometry every measurement in that project runs on.
So its fetch_feature_pages stays, and with it a second copy of the offset rule this package exists to stop being copied:
step the offset by the rows received, never by the page size asked for
which iter_features's own comment explains at length, and which was wrong once here. There are two implementations of it in that project's dependency graph today, and only one of them gets fixed when the pin moves.
It also keeps a second copy of the refusals alive. That project's local _get duplicates this one's HTTPS-only check, its stop on 401, 403 and 429, its refusal of a non-JSON challenge page and its refusal of an error payload, because fetch_feature_pages needs a fetch and _get is private. Those refusals are this project's promises about somebody else's server, and a fix to them currently reaches the consumer for one of its four layers.
Shape
One keyword argument, out_format: str = "json" (or the narrower geojson: bool = False), threaded into the query dict beside out_sr, which is already sent only when given for exactly this reason.
fetch_layer should probably not take it, since it reads feature["attributes"] and a GeoJSON page has none. That is an argument for putting it on iter_features only and saying so in the docstring.
Out of scope
- Changing what this project acquires by default. The default request must not move:
sources.py pins hashes of files fetched with it.
- Any GeoJSON parsing, validation or conversion here. The caller asks for a format and gets what the service sends, which is what
iter_features already promises when it says a feature is yielded whole.
Done when
- A caller can request
geojson and receives the service's own feature objects unaltered.
- A test asserts the default query is byte-for-byte the one sent today, in the shape of
test_the_default_request_is_the_one_this_project_has_always_made.
- The paging rule is exercised under the non-default format too, so the offset stepping is known to be shared rather than assumed to be.
Pointers
Raised from the consuming project under its roadmap item 4.4, which says an acquisition gap found there is contributed here rather than worked around locally.
Prepared with AI assistance; reviewed before submission.
What
iter_featuresgainedreturn_geometryandout_srin #75, which closed most of the consuming project's gap 2. It hard-codes the output format:This asks for an output-format argument alongside them, defaulting to
"json"so the request this project makes by default stays byte-for-byte what it has always been and the pinned retrievals insources.pystay reproducible.Why it matters
wildfire-service-territory-overlapconsumes this package under a pinned commit, and itsdocs/UPSTREAM.mdgap 2 asked for "geometry, output format and output spatial reference as arguments tofetch_layer, or the offset loop factored out". #75 delivered the offset loop and two of the three arguments. Re-audited at the pin3a6aa47on 2026-09-07, the gap is still open on the third, and it is the one that keeps the duplication alive.That project reads three polygon layers, the two CEC service-territory layers and a county boundary layer, as
f=geojsonand writes them as GeoJSON feature collections, which is what itsgeometry.pyconsumes.iter_featuresyields GeoServices features,{"attributes": ..., "geometry": ...}with Esri rings. Routing those layers through it would mean converting Esri geometry to GeoJSON in the consumer, which is more re-implementation than the offset loop it would retire, and it would be conversion of the geometry every measurement in that project runs on.So its
fetch_feature_pagesstays, and with it a second copy of the offset rule this package exists to stop being copied:which
iter_features's own comment explains at length, and which was wrong once here. There are two implementations of it in that project's dependency graph today, and only one of them gets fixed when the pin moves.It also keeps a second copy of the refusals alive. That project's local
_getduplicates this one's HTTPS-only check, its stop on 401, 403 and 429, its refusal of a non-JSON challenge page and its refusal of an error payload, becausefetch_feature_pagesneeds a fetch and_getis private. Those refusals are this project's promises about somebody else's server, and a fix to them currently reaches the consumer for one of its four layers.Shape
One keyword argument,
out_format: str = "json"(or the narrowergeojson: bool = False), threaded into the query dict besideout_sr, which is already sent only when given for exactly this reason.fetch_layershould probably not take it, since it readsfeature["attributes"]and a GeoJSON page has none. That is an argument for putting it oniter_featuresonly and saying so in the docstring.Out of scope
sources.pypins hashes of files fetched with it.iter_featuresalready promises when it says a feature is yielded whole.Done when
geojsonand receives the service's own feature objects unaltered.test_the_default_request_is_the_one_this_project_has_always_made.Pointers
src/perimeter/acquire.py:iter_features(the query dict and theout_srprecedent),fetch_layerwildfire-service-territory-overlap:src/wildfire_service_territory_overlap/acquire.py(fetch_feature_pages,_get),docs/UPSTREAM.mdgap 2 and the re-audit of 2026-09-07, and its issue Add an artifact diff command so a deliberate refresh is compared value by value #58Raised from the consuming project under its roadmap item 4.4, which says an acquisition gap found there is contributed here rather than worked around locally.
Prepared with AI assistance; reviewed before submission.