|
| 1 | +# closecity (Python SDK) |
| 2 | + |
| 3 | +Python client for the Close travel-time API (https://api.close.city). httpx-based, |
| 4 | +returns `geopandas` by default. Sphinx + Furo docs deploy to GitHub Pages; PyPI (via |
| 5 | +OIDC trusted publishing) is the eventual target. Public repo: |
| 6 | +`henryspatialanalysis/closecity-python`. The R sibling is |
| 7 | +`henryspatialanalysis/closecity-r` -- keep the two SDKs behaviourally in step. |
| 8 | + |
| 9 | +## Conventions (these are binding -- do not re-litigate) |
| 10 | + |
| 11 | +- **Prose:** follow the humanizer STYLE (no em dashes, no hype, short active |
| 12 | + sentences, Oxford comma). Beginner audience: keep the getting-started glossary |
| 13 | + current. Explain, do not sell. |
| 14 | +- **ASCII only** in all code and docs (no smart quotes, arrows, or non-ASCII glyphs). |
| 15 | +- **Attribution:** copyright and funder is "Henry Spatial Analysis", never "Close". |
| 16 | + Author `Nathaniel Henry <nat@henryspatialanalysis.com>`. |
| 17 | +- **Code style:** line length 90, spaces around `=` even inside calls (Nat overrides |
| 18 | + linters on this). `ruff` clean. |
| 19 | +- **API shape:** `Client` with one method per route; `Reply` / `Paginator`. Feature |
| 20 | + methods convert to geopandas via `spatial.py` (`to_geopandas()` is also public). |
| 21 | +- **Spatial by default:** feature methods (POIs, catchments, areal blocks, isochrones) |
| 22 | + return a `GeoDataFrame`. `Client(spatial=True)` is the default; `spatial=False` (on |
| 23 | + the client or per call) returns the raw `Reply`. Catalog/places/summaries stay raw. |
| 24 | +- **Dependencies:** `geopandas>=0.14` is a hard dependency. `pygris` is the `[tiger]` |
| 25 | + extra, used only to fetch block boundaries for the GEOID-only block routes |
| 26 | + (`fetch=True`). Keep the runtime footprint minimal otherwise. |
| 27 | +- **Tutorials (notebooks):** dead-simple and linear, **no helper functions**. Pull |
| 28 | + destination-type ids from the **free catalog** (`client.destination_types()`), never |
| 29 | + hardcode numeric codes. Draw a **map at each stage**. **No token-cost talk.** After a |
| 30 | + placeholder key, inline `# use your own key here`. |
| 31 | +- **Example cities:** Somerville MA (home search), Richmond VA (amenity basket), |
| 32 | + Providence RI (competitor walksheds). **No Seattle anywhere.** |
| 33 | +- **Docs execute live** at build time (see below), guarded on `CLOSECITY_KEY` so a |
| 34 | + keyless / PyPI build stays green (`conf.py` sets `nb_execution_mode` from the key). |
| 35 | + |
| 36 | +## Gotchas (hard-won -- do not re-hit) |
| 37 | + |
| 38 | +- **`blocks_query` needs JSON arrays:** the POST `/v1/blocks/query` body requires list |
| 39 | + fields, so scalar `mode`/`type` are normalised with `_as_list()`. A scalar mode |
| 40 | + returns 400. |
| 41 | +- **CRS:** TIGER blocks arrive in NAD83 (EPSG:4269); `_blocks_gdf` reprojects to 4326 |
| 42 | + so they match POI/isochrone geometry. geopandas warns / errors on mismatched CRS. |
| 43 | +- **Blocks TIGER lacks** (e.g. water blocks) get NaN geometry from the left join; |
| 44 | + `_blocks_gdf` drops them (`geometry.notna()`) so plotting and spatial joins work. The |
| 45 | + block join renames the key to `geoid` before merging (the R SDK had a bug here; do |
| 46 | + not regress). |
| 47 | +- **`place_blocks()` (`/v1/places/{geoid}/blocks`) is BROKEN server-side:** it times |
| 48 | + out (15s Lambda) for every real place. Tutorials use `blocks_query(center, radius_m)` |
| 49 | + instead. Leave the wrapper, but do not build examples on it until the API is fixed. |
| 50 | + |
| 51 | +## Local live doc build (this EFS host) |
| 52 | + |
| 53 | +```bash |
| 54 | +python -m pip install -e '.[docs]' |
| 55 | +export CLOSECITY_KEY=$(aws ssm get-parameter --name /wtm-api/internal-test-key \ |
| 56 | + --with-decryption --region us-west-2 --query Parameter.Value --output text) # do not print it |
| 57 | +python -m sphinx -b html docs docs/_build/html -W --keep-going |
| 58 | +``` |
| 59 | + |
| 60 | +Notebooks (`docs/getting_started.md` + `docs/tutorials/*.md`) are myst-nb `.md` |
| 61 | +notebooks; they execute when the key is set (else `nb_execution_mode` is off). A hidden |
| 62 | +`:tags: [remove-cell]` auth cell builds the real client; a display-only block shows the |
| 63 | +placeholder key. `docs.yml` passes the `CLOSECITY_KEY` secret to the build. Tests: |
| 64 | +`python -m pytest` (offline, no key). |
| 65 | + |
| 66 | +## Git |
| 67 | + |
| 68 | +Read-only unless Nat says otherwise; Nat runs commit sessions (modular, single-sentence, |
| 69 | +present-tense messages, no AI-attribution trailers). Push is a separate gate. |
0 commit comments