Query U.S. state zipcodes without SQLite.
Project description
Zipcodes
Zipcodes is a simple library for querying U.S. zipcodes. No SQLite, no network, no runtime data files — the full dataset is embedded in the package.
Since 2.0, the library is implemented in Rust and published from a single
codebase as both the zipcodes Python package
and the zipcodes Rust crate. The Python
API is a drop-in replacement for 1.x — same functions, same dicts, same
exceptions — just faster:
| Operation | 1.x (pure Python) | 2.0 (Rust) |
|---|---|---|
import zipcodes |
~330 ms (loads dataset) | ~5 ms (dataset loads lazily on first query, ~200 ms) |
is_real("06903") |
~4.2 ms | ~0.03 ms |
matching("77429") |
~4.2 ms | ~0.3 ms |
similar_to("1018") |
~7.2 ms | ~0.3 ms |
filter_by(state="TX") |
~9.7 ms | ~3.7 ms |
>>> import zipcodes
>>> assert zipcodes.is_real('77429')
>>> assert len(zipcodes.similar_to('7742')) != 0
>>> exact_zip = zipcodes.matching('77429')[0]
>>> filtered_zips = zipcodes.filter_by(city="Cypress", state="TX")
>>> assert exact_zip in filtered_zips
>>> pprint.pprint(exact_zip)
{'acceptable_cities': [],
'active': True,
'area_codes': ['281', '346', '713', '832'],
'city': 'Cypress',
'country': 'US',
'county': 'Harris County',
'lat': '29.9766',
'long': '-95.6358',
'state': 'TX',
'timezone': 'America/Chicago',
'unacceptable_cities': [],
'world_region': 'NA',
'zip_code': '77429',
'zip_code_type': 'STANDARD'}
The zipcode data is refreshed automatically every month — see Zipcode Data.
Installation
Python
$ python -m pip install zipcodes
Zipcodes 2.x supports Python 3.9+ and ships prebuilt wheels for Linux (x86_64, aarch64, musl), macOS, and Windows. Installing from the source distribution requires a Rust toolchain. Python 2.6+/3.2+ users are automatically served the pure-Python 1.3.0 release by pip.
Rust
$ cargo add zipcodes
New in 2.0
- Implemented in Rust; the dataset is compiled into the extension module and
decompressed lazily on first query, so
import zipcodesis effectively free. - New functions:
contains,filter_by_state,filter_by_city,filter_by_county,filter_by_timezone,filter_by_zip_code_type,filter_by_coordinates, andhaversine. is_validnow actually emits itsDeprecationWarning(in 1.x it raisedAttributeError); useis_real.- PyInstaller users no longer need
--add-dataforzips.json.bz2— there is no data file anymore. - Behavioral notes for upgraders: query results are fresh dicts (mutating a
result no longer mutates the shared database list), and
filter_by(active=1)no longer matchesactive=True(pass a bool). - Unified validation. Input validation now lives once, in the Rust core,
instead of a separate Python validator. This changes a few edge cases:
- Breaking:
is_real/matchingnow raiseValueErrorfor input shorter than five characters (after trimming), instead of returningFalse/[]. - Relaxations: surrounding whitespace (
" 06903 ") and space-separated Zip+4 ("12345 6789") are now accepted; any input of five or more characters is normalized to its first five digits, generalizing the existing#####-####handling. - Private API removal: the undocumented helpers
zipcodes._cleanandzipcodes._contains_nondigits(and the_digits/_valid_zipcode_lengthmodule attributes) no longer exist.
- Breaking:
Zipcode Data
The embedded dataset (crates/zipcodes/src/zips.json.bz2) is assembled from
three sources:
- unitedstateszipcodes.org — the
rich base data (city aliases, zip type, area codes, county, timezone),
committed in-repo as
scripts/data/zip_code_database.csv. Its bot protection prevents automated downloads, so this file is refreshed manually on occasion. - GeoNames (
download.geonames.org/export/zip/US.zip) — GPS coordinates, fetched fresh on every update. Licensed CC BY 4.0. - USPS ZIP Locale Detail (postalpro.usps.com) — the authoritative list of active delivery ZIPs, fetched fresh on every update and used to add zipcodes missing from the base CSV (#23). Public domain.
A GitHub Actions workflow (update-data.yml)
rebuilds the dataset on the 1st of every month
(#7). If anything changed,
it opens a pull request with a summary of added/removed/modified records;
merging that PR tags a patch release, which publishes to PyPI and crates.io
automatically.
To rebuild the dataset manually:
$ pip install xlrd
$ curl -fsSL https://download.geonames.org/export/zip/US.zip -o /tmp/geonames_us.zip
$ # download the .xls linked from https://postalpro.usps.com/ZIP_Locale_Detail
$ python scripts/update_zipcode_dataset.py \
--base scripts/data/zip_code_database.csv \
--gps scripts/data/zip-codes-database-FREE.csv \
--geonames-zip /tmp/geonames_us.zip \
--usps-xls /tmp/usps_zip_locale.xls \
--output-bz2 crates/zipcodes/src/zips.json.bz2 \
--summary-output /tmp/change_summary.json
Tests
The tests are defined in a declarative, table-based format that generates test cases.
$ cargo test # Rust unit tests
$ python tests/__init__.py # Python suite (or: pytest tests/)
Examples
>>> from pprint import pprint
>>> import zipcodes
>>> # Simple zip-code matching.
>>> pprint(zipcodes.matching('77429'))
[{'acceptable_cities': [],
'active': True,
'area_codes': ['281', '346', '713', '832'],
'city': 'Cypress',
'country': 'US',
'county': 'Harris County',
'lat': '29.9766',
'long': '-95.6358',
'state': 'TX',
'timezone': 'America/Chicago',
'unacceptable_cities': [],
'world_region': 'NA',
'zip_code': '77429',
'zip_code_type': 'STANDARD'}]
>>> # Handles Zip+4 zip-codes nicely. :)
>>> pprint(zipcodes.matching('77429-1145'))
[{'acceptable_cities': [],
'active': True,
'area_codes': ['281', '346', '713', '832'],
'city': 'Cypress',
'country': 'US',
'county': 'Harris County',
'lat': '29.9766',
'long': '-95.6358',
'state': 'TX',
'timezone': 'America/Chicago',
'unacceptable_cities': [],
'world_region': 'NA',
'zip_code': '77429',
'zip_code_type': 'STANDARD'}]
>>> # Will try to handle invalid zip-codes gracefully...
>>> print(zipcodes.matching('06463'))
[]
>>> # Until it cannot.
>>> zipcodes.matching('0646a')
Traceback (most recent call last):
...
ValueError: Invalid characters, zipcode may only contain digits and "-".
>>> zipcodes.matching('0646')
Traceback (most recent call last):
...
ValueError: Invalid format, zipcode must be of the format: "#####" or "#####-####"
>>> zipcodes.matching(None)
Traceback (most recent call last):
...
TypeError: Invalid type, zipcode must be a string.
>>> # Whether the zip-code exists within the database.
>>> print(zipcodes.is_real('06463'))
False
>>> # How handy!
>>> print(zipcodes.is_real('06469'))
True
>>> # Search for zipcodes that begin with a pattern.
>>> pprint(zipcodes.similar_to('1018'))
[{'acceptable_cities': [],
'active': False,
'area_codes': ['212'],
'city': 'New York',
'country': 'US',
'county': 'New York County',
'lat': '40.71',
'long': '-74',
'state': 'NY',
'timezone': 'America/New_York',
'unacceptable_cities': ['J C Penney'],
'world_region': 'NA',
'zip_code': '10184',
'zip_code_type': 'UNIQUE'},
{'acceptable_cities': [],
'active': True,
'area_codes': ['212'],
'city': 'New York',
'country': 'US',
'county': 'New York County',
'lat': '40.7808',
'long': '-73.9772',
'state': 'NY',
'timezone': 'America/New_York',
'unacceptable_cities': ['Manhattan', 'New York City', 'NY', 'Ny City', 'Nyc'],
'world_region': 'NA',
'zip_code': '10185',
'zip_code_type': 'PO BOX'}]
>>> # Use filter_by to filter a list of zip-codes by specific attribute->value pairs.
>>> pprint(zipcodes.filter_by(city="Old Saybrook"))
[{'acceptable_cities': [],
'active': True,
'area_codes': ['860', '959'],
'city': 'Old Saybrook',
'country': 'US',
'county': 'Middlesex County',
'lat': '41.2913',
'long': '-72.385',
'state': 'CT',
'timezone': 'America/New_York',
'unacceptable_cities': ['Fenwick'],
'world_region': 'NA',
'zip_code': '06475',
'zip_code_type': 'STANDARD'}]
>>> # Arbitrary nesting of similar_to and filter_by calls, allowing for great precision while filtering.
>>> pprint([z['zip_code'] for z in zipcodes.similar_to('2', zips=zipcodes.filter_by(active=True, city='Windsor'))])
['23487', '27983', '29856']
>>> # Find zipcodes within a radius (miles) of a coordinate.
>>> pprint([z['zip_code'] for z in zipcodes.filter_by_coordinates(41.3015, -72.3879, 5)])
['06371', '06409', '06426', '06442', '06475', '06498']
>>> # Have any other ideas? Make a pull request and start contributing today!
>>> # Made with love by Sean Pianka
Repository layout
This repository builds both packages from one Rust core:
crates/zipcodes— the core library, published to crates.io.crates/zipcodes-py— PyO3 bindings (not published to crates.io).python/zipcodes— the thin Python compatibility layer; together with the bindings it forms the PyPI package, built with maturin.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file zipcodes-3.0.0.tar.gz.
File metadata
- Download URL: zipcodes-3.0.0.tar.gz
- Upload date:
- Size: 734.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9a9426527f1288d334444a4de7d1960d9f6670bb26b843b274234fcf95ce4497
|
|
| MD5 |
e648b34a768ebd5cba58e6e9fae1a2a3
|
|
| BLAKE2b-256 |
fda64f0b4666c54eccffe057124b63e4385b80598dd94a967d6d5eb0a6363ae7
|
Provenance
The following attestation bundles were made for zipcodes-3.0.0.tar.gz:
Publisher:
release.yml on seanpianka/Zipcodes
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
zipcodes-3.0.0.tar.gz -
Subject digest:
9a9426527f1288d334444a4de7d1960d9f6670bb26b843b274234fcf95ce4497 - Sigstore transparency entry: 1819415211
- Sigstore integration time:
-
Permalink:
seanpianka/Zipcodes@221fa7d3184813fff5da3dbf9aea41fea635bac7 -
Branch / Tag:
refs/tags/v3.0.0 - Owner: https://github.com/seanpianka
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@221fa7d3184813fff5da3dbf9aea41fea635bac7 -
Trigger Event:
push
-
Statement type:
File details
Details for the file zipcodes-3.0.0-cp39-abi3-win_amd64.whl.
File metadata
- Download URL: zipcodes-3.0.0-cp39-abi3-win_amd64.whl
- Upload date:
- Size: 930.2 kB
- Tags: CPython 3.9+, Windows x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
57e61aa5182aaeec2a1f44af3d0358c54c0495aaa0c08f8aebb022c94aed5682
|
|
| MD5 |
e19036bacfa0a118efaac7a856f32e21
|
|
| BLAKE2b-256 |
37a09ab7dc8b1884141de27cc9677b25e98061eb5b46e9a2eabc3e748765e8b4
|
Provenance
The following attestation bundles were made for zipcodes-3.0.0-cp39-abi3-win_amd64.whl:
Publisher:
release.yml on seanpianka/Zipcodes
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
zipcodes-3.0.0-cp39-abi3-win_amd64.whl -
Subject digest:
57e61aa5182aaeec2a1f44af3d0358c54c0495aaa0c08f8aebb022c94aed5682 - Sigstore transparency entry: 1819416510
- Sigstore integration time:
-
Permalink:
seanpianka/Zipcodes@221fa7d3184813fff5da3dbf9aea41fea635bac7 -
Branch / Tag:
refs/tags/v3.0.0 - Owner: https://github.com/seanpianka
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@221fa7d3184813fff5da3dbf9aea41fea635bac7 -
Trigger Event:
push
-
Statement type:
File details
Details for the file zipcodes-3.0.0-cp39-abi3-musllinux_1_2_x86_64.whl.
File metadata
- Download URL: zipcodes-3.0.0-cp39-abi3-musllinux_1_2_x86_64.whl
- Upload date:
- Size: 1.3 MB
- Tags: CPython 3.9+, musllinux: musl 1.2+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2f2ce64cd478f753ac9cda90095889e25b6f66cc3b1ebc0f30809d87e40d19a6
|
|
| MD5 |
7d30610b9e2c9c8b39565a9f1c8e2d54
|
|
| BLAKE2b-256 |
3375225120a686fb32df12719f1cdf363b701a0471507960f2393933677c3224
|
Provenance
The following attestation bundles were made for zipcodes-3.0.0-cp39-abi3-musllinux_1_2_x86_64.whl:
Publisher:
release.yml on seanpianka/Zipcodes
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
zipcodes-3.0.0-cp39-abi3-musllinux_1_2_x86_64.whl -
Subject digest:
2f2ce64cd478f753ac9cda90095889e25b6f66cc3b1ebc0f30809d87e40d19a6 - Sigstore transparency entry: 1819415742
- Sigstore integration time:
-
Permalink:
seanpianka/Zipcodes@221fa7d3184813fff5da3dbf9aea41fea635bac7 -
Branch / Tag:
refs/tags/v3.0.0 - Owner: https://github.com/seanpianka
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@221fa7d3184813fff5da3dbf9aea41fea635bac7 -
Trigger Event:
push
-
Statement type:
File details
Details for the file zipcodes-3.0.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.
File metadata
- Download URL: zipcodes-3.0.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
- Upload date:
- Size: 1.0 MB
- Tags: CPython 3.9+, manylinux: glibc 2.17+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
862d8f6b6b82b50f5268ae2651cf08af1892526ea929fbe658d3872ebe395b1a
|
|
| MD5 |
7fa8dd8b579e9f17c8cb14f7438d291f
|
|
| BLAKE2b-256 |
ea0d46a183b1c500f81d3013c423557a469b1a3daf2a08627f71fc718a9b94a0
|
Provenance
The following attestation bundles were made for zipcodes-3.0.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:
Publisher:
release.yml on seanpianka/Zipcodes
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
zipcodes-3.0.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl -
Subject digest:
862d8f6b6b82b50f5268ae2651cf08af1892526ea929fbe658d3872ebe395b1a - Sigstore transparency entry: 1819417258
- Sigstore integration time:
-
Permalink:
seanpianka/Zipcodes@221fa7d3184813fff5da3dbf9aea41fea635bac7 -
Branch / Tag:
refs/tags/v3.0.0 - Owner: https://github.com/seanpianka
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@221fa7d3184813fff5da3dbf9aea41fea635bac7 -
Trigger Event:
push
-
Statement type:
File details
Details for the file zipcodes-3.0.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.
File metadata
- Download URL: zipcodes-3.0.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
- Upload date:
- Size: 1.0 MB
- Tags: CPython 3.9+, manylinux: glibc 2.17+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6cb450f427d5568530e4c6a6a497c0d572eaacc987c6f75f8a92b206d6056280
|
|
| MD5 |
1243b0e108be06e51214cfdb4c90ddac
|
|
| BLAKE2b-256 |
8e6b4ef5c0a557c042c18d87bcb8a00708083c9ab4d3d5660bc911134a9ef6bd
|
Provenance
The following attestation bundles were made for zipcodes-3.0.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl:
Publisher:
release.yml on seanpianka/Zipcodes
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
zipcodes-3.0.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl -
Subject digest:
6cb450f427d5568530e4c6a6a497c0d572eaacc987c6f75f8a92b206d6056280 - Sigstore transparency entry: 1819417143
- Sigstore integration time:
-
Permalink:
seanpianka/Zipcodes@221fa7d3184813fff5da3dbf9aea41fea635bac7 -
Branch / Tag:
refs/tags/v3.0.0 - Owner: https://github.com/seanpianka
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@221fa7d3184813fff5da3dbf9aea41fea635bac7 -
Trigger Event:
push
-
Statement type:
File details
Details for the file zipcodes-3.0.0-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl.
File metadata
- Download URL: zipcodes-3.0.0-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl
- Upload date:
- Size: 2.0 MB
- Tags: CPython 3.9+, macOS 10.12+ universal2 (ARM64, x86-64), macOS 10.12+ x86-64, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
70b1d5687930c186c883ef1c05cf76c3d05a68191162e2f3f47a132dc81571a7
|
|
| MD5 |
99f9a72d014ffcb91cdcb2d918ec9c0d
|
|
| BLAKE2b-256 |
a00c5c001eb2ee68cd143d57e2c23f47bc6eb88ed8bb8bd73876bd0541c626d3
|
Provenance
The following attestation bundles were made for zipcodes-3.0.0-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl:
Publisher:
release.yml on seanpianka/Zipcodes
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
zipcodes-3.0.0-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl -
Subject digest:
70b1d5687930c186c883ef1c05cf76c3d05a68191162e2f3f47a132dc81571a7 - Sigstore transparency entry: 1819415909
- Sigstore integration time:
-
Permalink:
seanpianka/Zipcodes@221fa7d3184813fff5da3dbf9aea41fea635bac7 -
Branch / Tag:
refs/tags/v3.0.0 - Owner: https://github.com/seanpianka
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@221fa7d3184813fff5da3dbf9aea41fea635bac7 -
Trigger Event:
push
-
Statement type: