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.
Metadata
Release files for zipcodes 3.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| zipcodes-3.0.0.tar.gz | 734.5 kB | Details |
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| zipcodes-3.0.0-cp39-abi3-win_amd64.whl | CPython 3.9 | abi3 | Windows x86-64 | Details |
| zipcodes-3.0.0-cp39-abi3-musllinux_1_2_x86_64.whl | CPython 3.9 | abi3 | Linux musl 1.2+ x86-64 | Details |
| zipcodes-3.0.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl | CPython 3.9 | abi3 | Linux glibc 2.17+ x86-64 | Details |
| zipcodes-3.0.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl | CPython 3.9 | abi3 | Linux glibc 2.17+ ARM64 | Details |
| zipcodes-3.0.0-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl | CPython 3.9 | abi3 | macOS 11.0+ ARM64, macOS 10.12+ x86-64, macOS 10.12+ universal2 (ARM64, x86-64) | Details |
Total release size: 7.0 MB
Release files / zipcodes-3.0.0.tar.gz
| Download URL | zipcodes-3.0.0.tar.gz |
|---|---|
| Size | 734.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
9a9426527f1288d334444a4de7d1960d9f6670bb26b843b274234fcf95ce4497
|
|
BLAKE2b-256 checksum How to use checksums |
fda64f0b4666c54eccffe057124b63e4385b80598dd94a967d6d5eb0a6363ae7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jun 14, 2026.
Transparency logRelease files / zipcodes-3.0.0-cp39-abi3-win_amd64.whl
| Download URL | zipcodes-3.0.0-cp39-abi3-win_amd64.whl |
|---|---|
| Size | 930.2 kB |
| Tags | CPython 3.9 Windows x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
57e61aa5182aaeec2a1f44af3d0358c54c0495aaa0c08f8aebb022c94aed5682
|
|
BLAKE2b-256 checksum How to use checksums |
37a09ab7dc8b1884141de27cc9677b25e98061eb5b46e9a2eabc3e748765e8b4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jun 14, 2026.
Transparency logRelease files / zipcodes-3.0.0-cp39-abi3-musllinux_1_2_x86_64.whl
| Download URL | zipcodes-3.0.0-cp39-abi3-musllinux_1_2_x86_64.whl |
|---|---|
| Size | 1.3 MB |
| Tags | CPython 3.9 Linux musl 1.2+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
2f2ce64cd478f753ac9cda90095889e25b6f66cc3b1ebc0f30809d87e40d19a6
|
|
BLAKE2b-256 checksum How to use checksums |
3375225120a686fb32df12719f1cdf363b701a0471507960f2393933677c3224
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jun 14, 2026.
Transparency logRelease files / zipcodes-3.0.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
| Download URL | zipcodes-3.0.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl |
|---|---|
| Size | 1.0 MB |
| Tags | CPython 3.9 Linux glibc 2.17+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
862d8f6b6b82b50f5268ae2651cf08af1892526ea929fbe658d3872ebe395b1a
|
|
BLAKE2b-256 checksum How to use checksums |
ea0d46a183b1c500f81d3013c423557a469b1a3daf2a08627f71fc718a9b94a0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jun 14, 2026.
Transparency logRelease files / zipcodes-3.0.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
| Download URL | zipcodes-3.0.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl |
|---|---|
| Size | 1.0 MB |
| Tags | CPython 3.9 Linux glibc 2.17+ ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
6cb450f427d5568530e4c6a6a497c0d572eaacc987c6f75f8a92b206d6056280
|
|
BLAKE2b-256 checksum How to use checksums |
8e6b4ef5c0a557c042c18d87bcb8a00708083c9ab4d3d5660bc911134a9ef6bd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jun 14, 2026.
Transparency logRelease files / zipcodes-3.0.0-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl
| Download URL | zipcodes-3.0.0-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl |
|---|---|
| Size | 2.0 MB |
| Tags | CPython 3.9 abi3 macOS 10.12+ universal2 (ARM64, x86-64) macOS 10.12+ x86-64 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
70b1d5687930c186c883ef1c05cf76c3d05a68191162e2f3f47a132dc81571a7
|
|
BLAKE2b-256 checksum How to use checksums |
a00c5c001eb2ee68cd143d57e2c23f47bc6eb88ed8bb8bd73876bd0541c626d3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jun 14, 2026.
Transparency log