From 3287d05a69ccbd850538c537cf19dee70141350b Mon Sep 17 00:00:00 2001 From: Jeremy Carbaugh <39159+jcarbaugh@users.noreply.github.com> Date: Wed, 20 May 2026 18:01:36 -0400 Subject: [PATCH] Add us.states.enumeration() for dynamic Enum construction Adds a sibling to `mapping()` that builds an `Enum` keyed by `State.abbr` with member values pulled from a caller-chosen `State` attribute. Default state list matches `mapping()` (`STATES_AND_TERRITORIES`); `value_field` defaults to `name`. Closes APR-32. Co-Authored-By: Claude Opus 4.7 (1M context) --- CHANGELOG.md | 1 + README.md | 28 ++++++++++++++++++++++++++++ us/states.py | 9 ++++++++- us/tests/test_us.py | 28 ++++++++++++++++++++++++++++ 4 files changed, 65 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 60c22c0..c6357ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ * add counties, thanks to [Ray Kiddy](https://github.com/rkiddy) * add `clean_name()` method and `fallback_func` parameter to `lookup()` to provide customizable matching, thanks to [Max Filenko](https://github.com/mfilenko) and [Charlie Tonneslan](https://github.com/c-tonneslan) * DC has returned to `STATES_AND_TERRITORIES`, thanks to [Kavi Gupta](https://github.com/kavigupta) +* add `us.states.enumeration()` for dynamic `Enum` construction from `State` attributes * add `us.__version__` and deprecate `us.version` * fix `py.typed` location, thanks to [johnw-bluemark](https://github.com/johnw-bluemark) * add support for Python 3.13 and 3.14 diff --git a/README.md b/README.md index bd8ffc7..fba5d56 100644 --- a/README.md +++ b/README.md @@ -234,6 +234,34 @@ additional states argument: ``` +### Enumerations + +The `enumeration()` method dynamically constructs an `Enum` of states, keyed +by state abbreviation. The value of each member is taken from the attribute +named by `value_field`, which defaults to `name`. + +```python +>>> States = us.states.enumeration() +>>> States.VA + +>>> States.VA.value +'Virginia' + +>>> States = us.states.enumeration('fips') +>>> States.CA.value +'06' +``` + +Like `mapping()`, this method uses `us.STATES_AND_TERRITORIES` as the +default state list, which can be overridden by passing a `states` argument: + +```python +>>> States = us.states.enumeration('fips', states=[us.states.DC, us.states.MD]) +>>> list(States) +[, ] +``` + + ### DC should be granted statehood Washington, DC does not appear in `us.STATES` or any of the diff --git a/us/states.py b/us/states.py index 9ef8c5a..fd06388 100644 --- a/us/states.py +++ b/us/states.py @@ -1,6 +1,7 @@ import os import re -from typing import Any, Callable, Dict, Iterable, List, Optional +from enum import Enum +from typing import Any, Callable, Dict, Iterable, List, Optional, Type from urllib.parse import urljoin import jellyfish # type: ignore @@ -203,6 +204,12 @@ def mapping(from_field: str, to_field: str, states: Optional[Iterable[State]] = return {getattr(s, from_field): getattr(s, to_field) for s in states} +def enumeration(value_field: str = "name", states: Optional[Iterable[State]] = None) -> Type[Enum]: + if states is None: + states = STATES_AND_TERRITORIES + return Enum("States", {s.abbr: getattr(s, value_field) for s in states}) + + AL = State( **{ "fips": "01", diff --git a/us/tests/test_us.py b/us/tests/test_us.py index 2f31e34..7942562 100644 --- a/us/tests/test_us.py +++ b/us/tests/test_us.py @@ -201,6 +201,34 @@ def test_custom_mapping(): assert "MD" in mapping +# enumerations + + +def test_enumeration(): + states = us.STATES[:5] + enum = us.states.enumeration("name", states=states) + for state in states: + assert enum[state.abbr].value == state.name + + +def test_enumeration_default_states(): + enum = us.states.enumeration("name") + assert enum["VA"].value == "Virginia" + assert enum["DC"].value == "District of Columbia" + + +def test_enumeration_default_value_field(): + enum = us.states.enumeration() + assert enum["VA"].value == "Virginia" + + +def test_custom_enumeration(): + enum = us.states.enumeration("fips", states=[us.states.DC, us.states.MD]) + assert len(enum) == 2 + assert enum["DC"].value == us.states.DC.fips + assert enum["MD"].value == us.states.MD.fips + + # known bugs