From 25528988493ae62c22bfd9ed80e2cfb55177aa4b Mon Sep 17 00:00:00 2001 From: jordan inskeep Date: Wed, 23 Sep 2026 08:46:22 -0400 Subject: [PATCH 1/2] Add fix for GEO_ID in variable request --- CHANGELOG.md | 4 ++++ morpc_census/api.py | 4 +++- pyproject.toml | 1 + reference/dev_notes.md | 17 +++++++++++++++++ tests/test_api.py | 28 ++++++++++++++++++++++++++++ 5 files changed, 53 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ded9cd7..d87c539 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,10 @@ This project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Fixed + +- **`CensusAPI` with both `group` and `variables`** no longer raises `KeyError: ['GEO_ID']` in `melt()`. The request now goes through the variable-list fetch path, which requests `GEO_ID`/`NAME` explicitly. + ## [0.6.0] — 2026-06-16 ### Changed diff --git a/morpc_census/api.py b/morpc_census/api.py index c13f9e5..644a626 100644 --- a/morpc_census/api.py +++ b/morpc_census/api.py @@ -899,7 +899,9 @@ def _fetch(self) -> pd.DataFrame: if key: params['key'] = key url = self.request['url'] - if self.group is not None: + # group() returns GEO_ID/NAME itself; an explicit variable list needs + # _fetch_variables to request them, even when a group is also given. + if self.group is not None and self.variables is None: return self._fetch_group(url, params) return self._fetch_variables(url, params) diff --git a/pyproject.toml b/pyproject.toml index c720adf..517dd2d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,6 +22,7 @@ dependencies = [ "requests>=2.28", "shapely>=2.0", "python-dotenv>=1.0", + "enlighten" ] [build-system] diff --git a/reference/dev_notes.md b/reference/dev_notes.md index 7d6a0a8..b61b3af 100644 --- a/reference/dev_notes.md +++ b/reference/dev_notes.md @@ -1,3 +1,20 @@ +## Fix KeyError 'GEO_ID' when CensusAPI gets both group and variables (fix/group-with-variables-fetch) + +2026-09-23. `CensusAPI(ep, scope, group='P1', variables=['P1_001N'])` raised +`KeyError: ... not present in the DataFrame: ['GEO_ID']` in `melt()`, for both +ACS and decennial endpoints. + +**Root cause**: `_build_request()` sets `get=` whenever variables are +given, but `_fetch()` dispatched to `_fetch_group()` whenever a group was given. +Only the `group(...)` query form makes the API return `GEO_ID`/`NAME` +implicitly; `_fetch_variables()` is the path that adds them to `get`. The +combination sent a bare variable list through the group path, so the response +had no `GEO_ID`. + +**Fix**: `_fetch()` uses `_fetch_group()` only when `variables is None`. +`self.group` is unchanged, so group validation and group-derived +concept/universe still apply. 3 new tests in `TestFetchDispatch`. + ## Recover CensusAPI.load() metadata from name + long data (drop _morpc block) 2026-06-16. `load()` no longer depends on the `_morpc` descriptor that #114 added diff --git a/tests/test_api.py b/tests/test_api.py index 193f2e6..c9b5ead 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -1085,6 +1085,34 @@ def test_single_row_result(self): assert len(result) == 1 +class TestFetchDispatch: + """Tests for _fetch choosing between the group() and variable-list paths.""" + + _fake_endpoints = {'acs/acs5': [2023]} + + def _dispatch(self, **kwargs): + """Construct a CensusAPI and return the name of the fetch path _fetch takes.""" + with patch('morpc_census.api.get_all_avail_endpoints', return_value=self._fake_endpoints), \ + patch.object(Group, 'variables', new={'B01001_001E': {}, 'B01001_002E': {}}), \ + patch('morpc_census.api._get_api_key', return_value=None), \ + patch.object(CensusAPI, '_fetch_group', return_value='group'), \ + patch.object(CensusAPI, '_fetch_variables', return_value='variables'): + api = CensusAPI(Endpoint('acs/acs5', 2023), 'franklin', _skip_fetch=True, **kwargs) + api.request = {'url': 'u', 'params': {'get': 'x'}} + return api._fetch() + + def test_group_only_uses_group_path(self): + assert self._dispatch(group='B01001') == 'group' + + def test_variables_only_uses_variables_path(self): + assert self._dispatch(variables=['B01001_001E']) == 'variables' + + def test_group_with_variables_uses_variables_path(self): + # The group() path relies on the API adding GEO_ID/NAME; a plain variable + # list does not, so it must go through _fetch_variables, which requests them. + assert self._dispatch(group='B01001', variables=['B01001_001E']) == 'variables' + + class TestCensusAPIGroupOptional: """Test CensusAPI behavior when group is None.""" From 61fe7b9765ebdb4293cdd05b807a2e58ed70eda1 Mon Sep 17 00:00:00 2001 From: jordan inskeep Date: Wed, 23 Sep 2026 08:49:08 -0400 Subject: [PATCH 2/2] Prepare 0.6.2 changelog Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d87c539..b38d16f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ This project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +## [0.6.2] — 2026-09-23 + ### Fixed - **`CensusAPI` with both `group` and `variables`** no longer raises `KeyError: ['GEO_ID']` in `melt()`. The request now goes through the variable-list fetch path, which requests `GEO_ID`/`NAME` explicitly.