Skip to content

Commit acb01c7

Browse files
author
Nathaniel Henry
committed
Return tabular output by default via a three-mode output setting.
1 parent 2bf63e5 commit acb01c7

10 files changed

Lines changed: 673 additions & 172 deletions

File tree

.claude/CLAUDE.md

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
# closecity (Python SDK)
22

33
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:
4+
returns tabular data by default (a `geopandas.GeoDataFrame` where geometry applies, a
5+
plain `pandas.DataFrame` otherwise). Sphinx + Furo docs deploy to GitHub Pages; PyPI
6+
(via OIDC trusted publishing) is the eventual target. Public repo:
67
`henryspatialanalysis/closecity-python`. The R sibling is
78
`henryspatialanalysis/closecity-r` -- keep the two SDKs behaviourally in step.
89

@@ -15,19 +16,31 @@ OIDC trusted publishing) is the eventual target. Public repo:
1516
- **Attribution:** copyright and funder is "Henry Spatial Analysis", never "Close".
1617
Author `Nathaniel Henry <nat@henryspatialanalysis.com>`.
1718
- **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.
19+
linters on this: `E251`/`E252` are ignored). `ruff check` clean with
20+
`select = ["E","F","W","I","N","UP","B","C4","SIM"]`. **Never run `ruff format`** --
21+
it strips the kwarg `=` spacing; lint only.
22+
- **API shape:** `Client` with one method per route; `Reply` / `Paginator`. Routes
23+
with geometry convert via `spatial.py` (`to_geopandas()`), everything else via
24+
`tabular.py` (`to_pandas()`); both are public. The `Client._deliver()` chokepoint
25+
routes by the resolved output mode.
26+
- **Output modes (`output`, default `"spatial"`):** `"spatial"` returns a
27+
`GeoDataFrame` where geometry applies (points, isochrone and block polygons) and a
28+
plain `DataFrame` for catalog and summary routes; `"tabular"` returns a plain
29+
`DataFrame` everywhere and never downloads block boundaries; `"raw"` returns the
30+
`Reply` / `Paginator`. Set it on the client (`close.output = "raw"`) or per call
31+
(`..., output = "tabular"`). `health`/`last_updated`/`isochrone_meta` are always raw;
32+
a 304 is always raw. Metering + envelope ride on `df.attrs`. Keep the R SDK's
33+
identical `output` vocabulary in step.
34+
- **Dependencies:** `pandas>=1.5` and `geopandas>=0.14` are hard dependencies. `pygris`
35+
is the `[tiger]` extra, used only to fetch block boundaries for the GEOID-only block
36+
routes in `spatial` mode. Keep the runtime footprint minimal otherwise.
2737
- **Tutorials (notebooks):** dead-simple and linear, **no helper functions**. Pull
2838
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`.
39+
hardcode numeric codes. Draw a **map at each stage**. State the tutorial's **measured
40+
token usage** in one italic line near the top ("Running this tutorial uses about N
41+
tokens."); keep every tutorial **under 5,000 tokens** and do not publish a cheaper
42+
variant on the same page. No other token-cost talk in the body except the dedicated
43+
`docs/token_use.md` page. After a placeholder key, inline `# use your own key here`.
3144
- **Example cities:** Somerville MA (home search), Richmond VA (amenity basket),
3245
Providence RI (competitor walksheds). **No Seattle anywhere.**
3346
- **Docs execute live** at build time (see below), guarded on `CLOSECITY_KEY` so a

CHANGELOG.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,24 @@
11
# Changelog
22

3+
## 1.1.0
4+
5+
Tabular results by default, across every route.
6+
7+
- New `output` mode replaces the `spatial` flag: `Client(output = "spatial")` (the
8+
default) returns a `GeoDataFrame` where geometry applies and a plain
9+
`pandas.DataFrame` for catalog and summary routes; `output = "tabular"` returns a
10+
plain `DataFrame` everywhere and never downloads block boundaries (the cheap path
11+
when you only want the numbers); `output = "raw"` returns the underlying `Reply` /
12+
`Paginator`. Set it on the client or per call.
13+
- Catalog routes (`modes()`, `destination_types()`, `vintage()`, `places()`) and
14+
the block and point summaries now return data frames instead of nested replies.
15+
`block_summary()` / `point_summary()` broadcast the origin GEOID to a `geoid`
16+
column. `isochrone(format = "blocks")` now converts too.
17+
- Metering and envelope metadata (`tokens_charged`, `tokens_remaining`, `etag`,
18+
`block_geoid`, `assumptions`, ...) ride on `df.attrs`.
19+
- New `to_pandas()` module function and `Reply.to_pandas()` / `Paginator.to_pandas()`
20+
methods, beside the existing geopandas ones. `pandas` is now a direct dependency.
21+
322
## 1.0.0
423

524
First public release of the `closecity` Python client for the Close API

pyproject.toml

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "closecity"
7-
version = "1.0.0"
7+
version = "1.1.0"
88
description = "Python client for the Close API (api.close.city). Travel times to points of interest for every US census block."
99
readme = "README.md"
1010
requires-python = ">=3.9"
@@ -19,7 +19,7 @@ classifiers = [
1919
"Programming Language :: Python :: 3",
2020
"Topic :: Scientific/Engineering :: GIS",
2121
]
22-
dependencies = ["httpx>=0.27", "geopandas>=0.14"]
22+
dependencies = ["httpx>=0.27", "pandas>=1.5", "geopandas>=0.14"]
2323

2424
[project.urls]
2525
Homepage = "https://close.city"
@@ -42,7 +42,18 @@ where = ["src"]
4242

4343
[tool.ruff]
4444
line-length = 90
45+
target-version = "py39"
4546

4647
[tool.ruff.lint]
47-
# House style: spaces around '=' in keyword args and defaults.
48+
select = ["E", "F", "W", "I", "N", "UP", "B", "C4", "SIM"]
49+
# House style: spaces around '=' in keyword args and defaults (E251/E252). Do NOT
50+
# run `ruff format` here; it strips those spaces. Lint only with `ruff check`.
4851
ignore = ["E251", "E252"]
52+
53+
[tool.ruff.lint.per-file-ignores]
54+
# `Point` is the shapely class passed around and called as a constructor; the
55+
# uppercase name is deliberate (like sklearn's X), so allow it here.
56+
"src/closecity/spatial.py" = ["N803", "N806"]
57+
58+
[tool.ruff.format]
59+
quote-style = "double"

src/closecity/__init__.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
"""
99

1010
from .client import Client, Paginator, Reply
11-
from .spatial import to_geopandas
1211
from .errors import (
1312
AuthenticationError,
1413
BadRequestError,
@@ -20,14 +19,17 @@
2019
ServiceUnavailableError,
2120
TokensExhaustedError,
2221
)
22+
from .spatial import to_geopandas
23+
from .tabular import to_pandas
2324

24-
__version__ = "1.0.0"
25+
__version__ = "1.1.0"
2526

2627
__all__ = [
2728
"Client",
2829
"Paginator",
2930
"Reply",
3031
"to_geopandas",
32+
"to_pandas",
3133
"CloseError",
3234
"CloseAPIError",
3335
"BadRequestError",

0 commit comments

Comments
 (0)