python-us: a small library for United States state metadata
A package for easily working with US and state metadata
At a glance
- What is it?
- The us package gives you every state and territory as an object with its postal abbreviation, FIPS code, capital, admission year, time zone and more, plus lookup that matches on names, codes, abbreviations and phonetic spelling, and helpers for shapefile URLs and attribute mappings. Version 4 moved the data to a separate package and added a fallback matching hook.
- Who is it for?
- Adopt python-us if your application has to reason about US states, since it removes the hand-maintained mapping tables that every project ends up writing and getting wrong. Do not adopt it if you need a canonical, complete list of the fifty states, because the package deliberately includes territories, obsolete territories and associated states alongside them.
- Can I use it commercially?
- Yes. BSD-3-Clause is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 24 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What the package gives you
The us package is for easily working with US and state metadata, and the readme opens with the list of what that covers.
All US states and territories. Postal abbreviations, and Associated Press style abbreviations. FIPS codes. Capitals. Years of statehood. Time zones. Phonetic state name lookup. Whether a place is contiguous or continental. And URLs to shapefiles for state, census, congressional districts, counties, and census tracts.
That is a short list of data types, but each one is the kind of thing that ends up as a hardcoded dict in someone's codebase. The value of a package like this is less the data than the fact that the data is one place, tested, and consistent with a published source.
The object model is straightforward. Every state is a State object, and the attributes are properties on it:
>>> import us
>>> us.states.MD
<State:Maryland>
>>> us.states.MD.fips
'24'
>>> us.states.MD.name
'Maryland'
>>> us.states.MD.is_contiguous
TrueThe territory distinction is a property too, and it is a different check for the same object rather than a separate type:
>>> us.states.VI.name
'Virgin Islands'
>>> us.states.VI.is_territory
True
>>> us.states.MD.is_territory
FalseThe three collections are the thing to get right when you use this. STATES is the list of actual states, TERRITORIES is the territories, and STATES_AND_TERRITORIES is the whole shebang. And the readme notes that for convenience all three can be accessed directly from the us module as well as from us.states:
>>> us.states.STATES
[<State:Alabama>, <State:Alaska>, <State:Arizona>, <State:Arkansas>, ...
>>> us.STATES
[<State:Alabama>, <State:Alaska>, <State:Arizona>, <State:Arkansas>, ...Then there are the two lists that most people do not expect. COMMONWEALTHS is the four states that call themselves commonwealths, which are Kentucky, Massachusetts, Pennsylvania and Virginia, a distinction that matters when you are generating legal or formal text and the name has to be right.
ASSOCIATED_STATES is the list of associated states, which are sovereign nations in free association with the United States under the Compact of Free Association. The readme is careful here: they are not US states or territories, and they are not included in STATES_AND_TERRITORIES or returned by lookup.
OBSOLETE is the list of obsolete territories, which is Dakota, Orleans and the Philippine Islands.
That last list is the one that shows the package is built from historical data rather than a hand-typed current list.
Lookup, and the phonetic trick
The lookup method accepts a FIPS code, an abbreviation or a name, and it is case insensitive:
>>> us.states.lookup('24')
<State:Maryland>
>>> us.states.lookup('MD')
<State:Maryland>
>>> us.states.lookup('md')
<State:Maryland>
>>> us.states.lookup('maryland')
<State:Maryland>The interesting part is the phonetic matching, and the readme introduces it with the reason a developer will recognise immediately: for those days that you just can't remember how to spell Mississippi, we've got phonetic name matching too.
>>> us.states.lookup('misisipi')
<State:Mississippi>That is a genuinely useful feature rather than a novelty. Spelling a state name correctly from memory is harder than it sounds, particularly for the ones with unusual spellings, and the underlying library for it is the one dependency the package declares, jellyfish, which implements phonetic encoding algorithms for fuzzy string comparison.
There is also a cleanup helper for input that comes from users or other unpredictable sources. clean_name lowercases the input, removes punctuation, and drops the filler words the, commonwealth, state, and of:
>>> us.states.clean_name(' The state OF idaho ')
'idaho'
>>> us.states.clean_name('Commonwealth of Virginia')
'virginia'
>>> us.states.lookup(us.states.clean_name('The State of Maryland!'))
<State:Maryland>The design choice here is deliberate and the readme states it plainly: clean_name is a standalone helper and lookup does not call it automatically. Apply it yourself when you want it.
That is the right call for a library, since silently normalising input would make the lookup unpredictable for callers who do not expect it, and it keeps clean_name usable on its own for building your own matching.
The 4.0 release also added a fallback hook. lookup accepts an optional fallback_func that is called when none of the built-in matching strategies find a state. It receives the original lookup value and should return a State or None:
>>> def my_fallback(val):
... return us.states.AK if val == 'the big one' else None
>>> us.states.lookup('the big one', fallback_func=my_fallback)
<State:Alaska>For the common case of matching against the start of a state name, the included startswith_fallback helper does that, case-insensitively, so calif finds California. And the readme notes that fallback results are cached separately per fallback function, so a cached fallback match will not leak into lookups that pass a different fallback or none at all.
Shapefiles, mappings and enumerations
The shapefile support is the part of the package that reaches outside the metadata and into actual geography.
>>> urls = us.states.MD.shapefile_urls()
>>> sorted(urls.keys())
['block', 'blockgroup', 'cd', 'county', 'state', 'tract', 'zcta']
>>> urls['block']
'https://www2.census.gov/geo/tiger/TIGER2010/TABBLOCK/2010/tl_2010_24_tabblock10.zip'So the method generates Census TIGER/Line 2010 shapefile URLs for a state, and the seven region types are block, blockgroup, cd, county, state, tract and zcta, which is the ZIP Code Tabulation Area. The readme is candid about the vintage: as long as you want 2010 shapefiles, we've gotcha covered.
That qualifier matters. The 2010 TIGER release is old, and if you need current boundaries you have to go to the Census directly. But for a fixed reference set, or for reproducing an older analysis, having the URL construction handled for you saves a lot of fiddly path building.
The mapping method is for the other common need, generating a lookup between two specified fields:
>>> us.states.mapping('fips', 'abbr')
{'01': 'AL', '02': 'AK', '04': 'AZ', '05': 'AR', '06': 'CA', ...
>>> us.states.mapping('abbr', 'name')
{'AL': 'Alabama', 'AK': 'Alaska', 'AZ': 'Arizona', 'AR': 'Arkansas', ...The default list is STATES_AND_TERRITORIES, and you can override it with a states argument, so a mapping over a single jurisdiction is straightforward:
>>> us.states.mapping('fips', 'abbr', states=[us.states.DC])
{'11': 'DC'}The enumeration method is the third helper, and it is the one that reaches for a standard library type. It dynamically constructs an Enum of states, keyed by state abbreviation, with the value of each member taken from the attribute named by value_field, which defaults to name:
>>> States = us.states.enumeration()
>>> States.VA
<States.VA: 'Virginia'>
>>> States.VA.value
'Virginia'
>>> States = us.states.enumeration('fips')
>>> States.CA.value
'06'The second form is the one to reach for when you are storing a value in a database or a file and want the stable numeric code rather than the abbreviation. And like mapping, it takes a states argument to narrow the set:
>>> States = us.states.enumeration('fips', states=[us.states.DC, us.states.MD])
>>> list(States)
[<States.DC: '11'>, <States.MD: '24'>]Constructing an Enum at run time from data rather than defining one in code is a neat trick, and it is the kind of thing the package exists to make easy.
The version 4 packaging change and the DC question
The most consequential change in this package is not visible in the API at all, and it is the reason the version jumped from 3.2.0 to 4.0.0.
The state data now lives in a separate distribution, python-us, so that the data can be updated independently of the code. The same pattern is used by other projects where the reference data changes on its own schedule, and the effect is that a data correction does not require a code release, and a code release does not require you to accept new data.
The repository reflects that split in its dependencies. The pyproject declares the data package as a dependency alongside jellyfish, the one library used for the phonetic matching, and the package itself is small enough that the version bump was safe.
The second item worth knowing is the DC question, and the readme is refreshingly direct about it. Washington, DC does not appear in us.STATES or any of the related state lists, but is often treated as a state in practice and should be granted statehood anyway.
So there is a switch. DC can be automatically included in these lists by setting a DC_STATEHOOD environment variable to any truthy value before importing this package:
DC_STATEHOOD=1The timing constraint is the detail that matters. It has to be set before the import, because the lists are built at import time, so setting it afterwards in the same process does nothing.
That is a reasonable design for a political question that software has to answer one way or the other. The alternative, including DC silently in the state list, would be wrong for the many applications where the distinction is legally meaningful, and excluding it by default is the conservative choice. The environment variable gives the applications that treat DC as a state a way to say so, and the readme links the statehood campaign so the reasoning is discoverable.
The package also has a command line entry point. The scripts section declares states = "us.cli.states:main", so the same data is reachable from a terminal, and the readme starts the CLI section with When you need to know state information RIGH, which is where the excerpt ends.
The development setup is uv based, with uv sync to install, uv run pytest for the tests, and ruff for both the check and the format check. The line length for ruff is 120 and the target version is py38, matching the requires-python of 3.8 or newer, and the classifiers run from 3.8 through 3.14.
Why a small package is worth depending on
It is fair to ask why a project that stores state metadata needs a package at all, and the answer is in what the data actually is.
A list of fifty states with their FIPS codes and abbreviations looks trivial until you notice how many of the values are not what a newcomer expects. The FIPS code for Maryland is 24, not 20, because FIPS codes are assigned alphabetically and the earlier numbering included states that no longer exist. The postal abbreviation for North Carolina is NC while its Associated Press style abbreviation is also NC, but for several states the two differ, and the data reflects the difference rather than assuming they are the same. The capital of a territory is sometimes a district rather than a city, and the time zone of a territory can be a different offset from its neighbours.
So the package is not a list of strings. It is a set of relationships between several naming systems, and the relationships are the part that is easy to get wrong and hard to notice when you have got it wrong.
The data itself comes from a public source with a version, and version 4 formalised that. The state data now lives in a separate distribution, python-us, and the pyproject declares it as a dependency next to jellyfish, the phonetic matching library. The effect is that a data correction, say a boundary or an attribute change, ships without a code release, and the data can be pinned on its own when you need a result to stay reproducible.
That is a bigger change than it looks. Version 3.2.0 was released in July 2024 and 4.0.0 in September 2026, and the jump in the major version is there because the dependency structure changed, not because the API broke. The readme also mentions a release with a changelog, and the repository carries a CHANGELOG.md, so the migration notes are in the repository rather than in a blog post.
The package is BSD-3-Clause licensed, requires Python 3.8 or newer, and the classifiers run up to 3.14. The build is setuptools rather than a Rust-flavoured build backend, and there is a .python-version file, a uv.lock and a pyproject that also configures ruff with a line length of 120 and a py38 target.
And there is a small command line interface, with a states script declared in the project scripts, which the readme begins to describe when it needs to know state information.
Editorial conclusion
Adopt python-us if your application has to reason about US states, since it removes the hand-maintained mapping tables that every project ends up writing and getting wrong. Do not adopt it if you need a canonical, complete list of the fifty states, because the package deliberately includes territories, obsolete territories and associated states alongside them. Before you commit, check which collection you want, since STATES, TERRITORIES and STATES_AND_TERRITORIES differ, and note that the state data now lives in a separate distribution you can pin independently.
Frequently asked questions
What does the us Python package provide?
It provides metadata for all US states and territories, including postal and Associated Press style abbreviations, FIPS codes, capitals, years of statehood, time zones, phonetic name matching, whether a place is contiguous or continental, and URLs to shapefiles for states, census tracts, counties and congressional districts.
How do I look up a state in the us package?
Use us.states.lookup, which matches on FIPS code, postal abbreviation or name, case-insensitively, so '24', 'MD', 'md' and 'maryland' all return Maryland. It also does phonetic matching, so a misspelling such as 'misisipi' still finds Mississippi, and it accepts an optional fallback_func for anything the built-in strategies miss.
What is the difference between STATES, TERRITORIES and STATES_AND_TERRITORIES?
STATES is the list of the actual states, TERRITORIES is the territories, and STATES_AND_TERRITORIES is the two combined. There are also lists for the four commonwealths, the associated states that are not US states or territories, and the obsolete territories. All three main lists can be accessed from us.states or directly from us.
How do I include Washington DC in the state lists?
Set the DC_STATEHOOD environment variable to any truthy value before importing the package, for example DC_STATEHOOD=1. The variable has to be set before the import because the lists are built at import time, and it is off by default because the distinction between a state and the district is legally meaningful in many contexts.
What changed in version 4 of the us package?
The state data moved into a separate distribution, python-us, so a data update no longer requires a code release and the data can be pinned independently. The release before it was 3.2.0 from 2024-07-22, and 4.0.0 is dated 2026-09-29, and the 4.x line also added the fallback_func hook and the startswith_fallback helper.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/unitedstates-python-us)Was this useful?