Skip to main content

Query U.S. state zipcodes without SQLite.

Project description

Zipcodes

PyPI - Python Version PyPI crates.io Downloads Contributors

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 zipcodes is 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, and haversine.
  • is_valid now actually emits its DeprecationWarning (in 1.x it raised AttributeError); use is_real.
  • PyInstaller users no longer need --add-data for zips.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 matches active=True (pass a bool).

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('064690')
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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

zipcodes-2.0.1.tar.gz (731.9 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

zipcodes-2.0.1-cp39-abi3-win_amd64.whl (917.0 kB view details)

Uploaded CPython 3.9+Windows x86-64

zipcodes-2.0.1-cp39-abi3-musllinux_1_2_x86_64.whl (1.2 MB view details)

Uploaded CPython 3.9+musllinux: musl 1.2+ x86-64

zipcodes-2.0.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (1.0 MB view details)

Uploaded CPython 3.9+manylinux: glibc 2.17+ x86-64

zipcodes-2.0.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (1.0 MB view details)

Uploaded CPython 3.9+manylinux: glibc 2.17+ ARM64

zipcodes-2.0.1-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl (2.0 MB view details)

Uploaded CPython 3.9+macOS 10.12+ universal2 (ARM64, x86-64)macOS 10.12+ x86-64macOS 11.0+ ARM64

File details

Details for the file zipcodes-2.0.1.tar.gz.

File metadata

  • Download URL: zipcodes-2.0.1.tar.gz
  • Upload date:
  • Size: 731.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for zipcodes-2.0.1.tar.gz
Algorithm Hash digest
SHA256 4e1163afb00d0eb93abedd217e457c69ab7714b13cb7bfccd08f4bbf330a0299
MD5 dac6100296984d2fde11ffa31c4b1326
BLAKE2b-256 5e6049d9ad7e9ac15f527e9f7d6000d2cb007e5cc3b3450462047c419661e304

See more details on using hashes here.

Provenance

The following attestation bundles were made for zipcodes-2.0.1.tar.gz:

Publisher: release.yml on seanpianka/Zipcodes

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file zipcodes-2.0.1-cp39-abi3-win_amd64.whl.

File metadata

  • Download URL: zipcodes-2.0.1-cp39-abi3-win_amd64.whl
  • Upload date:
  • Size: 917.0 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

Hashes for zipcodes-2.0.1-cp39-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 83adba76314ee49ccfcc7e3d64c2e67d291b8b273fae3cd218682fd7051cdcf5
MD5 0935213f7aee98323e34b306a413636c
BLAKE2b-256 ec9ee1f90fa425cd596b0c06f00328621154939b649f2f17f94ed07f1c62e2f3

See more details on using hashes here.

Provenance

The following attestation bundles were made for zipcodes-2.0.1-cp39-abi3-win_amd64.whl:

Publisher: release.yml on seanpianka/Zipcodes

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file zipcodes-2.0.1-cp39-abi3-musllinux_1_2_x86_64.whl.

File metadata

File hashes

Hashes for zipcodes-2.0.1-cp39-abi3-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 1f838ad2e664ea812f8d5b1884cf3036d5f007613d2a0babdf1de12e52fd7363
MD5 69e9e5cc9885ea14d9d8d4b56e89297c
BLAKE2b-256 5c76cfc614230a0dc7d06d99128a6299f789cfe1716bdb71f2339b821ec68475

See more details on using hashes here.

Provenance

The following attestation bundles were made for zipcodes-2.0.1-cp39-abi3-musllinux_1_2_x86_64.whl:

Publisher: release.yml on seanpianka/Zipcodes

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file zipcodes-2.0.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for zipcodes-2.0.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 441ccecacd1354c3b3f5f10073961b30db7641161ba8f39f38e0c0296db34f0c
MD5 480760c6f8635658e3d5ded37feb3012
BLAKE2b-256 97258528ee1fa73e763616249e08a95eea0d30412d49280a3733c0bd8f85ff9f

See more details on using hashes here.

Provenance

The following attestation bundles were made for zipcodes-2.0.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: release.yml on seanpianka/Zipcodes

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file zipcodes-2.0.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for zipcodes-2.0.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 d5cfa433da0127bfb2489549672378d2283856574a571402f08b5b8aa9d92302
MD5 09561540e2a97e4f7c4a83d2310f29c3
BLAKE2b-256 5289dadffb520b803be86005bb860c0d7a56de8257f3ae32325997429b23caf3

See more details on using hashes here.

Provenance

The following attestation bundles were made for zipcodes-2.0.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl:

Publisher: release.yml on seanpianka/Zipcodes

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file zipcodes-2.0.1-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl.

File metadata

File hashes

Hashes for zipcodes-2.0.1-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl
Algorithm Hash digest
SHA256 96796c33a4232a9219784719756518589a33d9d0a6b67cd5a589ee95c0792c62
MD5 dbda21b26adf569e011c0c64613cd267
BLAKE2b-256 d248f9d9bbb5c81484ba31715f55f518e303746fea51f183f1a3921d2cabfed6

See more details on using hashes here.

Provenance

The following attestation bundles were made for zipcodes-2.0.1-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl:

Publisher: release.yml on seanpianka/Zipcodes

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page