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).
  • 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/matching now raise ValueError for input shorter than five characters (after trimming), instead of returning False/[].
    • 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._clean and zipcodes._contains_nondigits (and the _digits/_valid_zipcode_length module attributes) no longer exist.

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


Download files

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

Source Distribution

zipcodes-3.0.0.tar.gz (734.5 kB view details)

Uploaded Source

Built Distributions

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

zipcodes-3.0.0-cp39-abi3-win_amd64.whl (930.2 kB view details)

Uploaded CPython 3.9+Windows x86-64

zipcodes-3.0.0-cp39-abi3-musllinux_1_2_x86_64.whl (1.3 MB view details)

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

zipcodes-3.0.0-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-3.0.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (1.0 MB view details)

Uploaded CPython 3.9+manylinux: glibc 2.17+ ARM64

zipcodes-3.0.0-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-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

Hashes for zipcodes-3.0.0.tar.gz
Algorithm Hash digest
SHA256 9a9426527f1288d334444a4de7d1960d9f6670bb26b843b274234fcf95ce4497
MD5 e648b34a768ebd5cba58e6e9fae1a2a3
BLAKE2b-256 fda64f0b4666c54eccffe057124b63e4385b80598dd94a967d6d5eb0a6363ae7

See more details on using hashes here.

Provenance

The following attestation bundles were made for zipcodes-3.0.0.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-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

Hashes for zipcodes-3.0.0-cp39-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 57e61aa5182aaeec2a1f44af3d0358c54c0495aaa0c08f8aebb022c94aed5682
MD5 e19036bacfa0a118efaac7a856f32e21
BLAKE2b-256 37a09ab7dc8b1884141de27cc9677b25e98061eb5b46e9a2eabc3e748765e8b4

See more details on using hashes here.

Provenance

The following attestation bundles were made for zipcodes-3.0.0-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-3.0.0-cp39-abi3-musllinux_1_2_x86_64.whl.

File metadata

File hashes

Hashes for zipcodes-3.0.0-cp39-abi3-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 2f2ce64cd478f753ac9cda90095889e25b6f66cc3b1ebc0f30809d87e40d19a6
MD5 7d30610b9e2c9c8b39565a9f1c8e2d54
BLAKE2b-256 3375225120a686fb32df12719f1cdf363b701a0471507960f2393933677c3224

See more details on using hashes here.

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

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-3.0.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for zipcodes-3.0.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 862d8f6b6b82b50f5268ae2651cf08af1892526ea929fbe658d3872ebe395b1a
MD5 7fa8dd8b579e9f17c8cb14f7438d291f
BLAKE2b-256 ea0d46a183b1c500f81d3013c423557a469b1a3daf2a08627f71fc718a9b94a0

See more details on using hashes here.

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

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-3.0.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for zipcodes-3.0.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 6cb450f427d5568530e4c6a6a497c0d572eaacc987c6f75f8a92b206d6056280
MD5 1243b0e108be06e51214cfdb4c90ddac
BLAKE2b-256 8e6b4ef5c0a557c042c18d87bcb8a00708083c9ab4d3d5660bc911134a9ef6bd

See more details on using hashes here.

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

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-3.0.0-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl.

File metadata

File hashes

Hashes for zipcodes-3.0.0-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl
Algorithm Hash digest
SHA256 70b1d5687930c186c883ef1c05cf76c3d05a68191162e2f3f47a132dc81571a7
MD5 99f9a72d014ffcb91cdcb2d918ec9c0d
BLAKE2b-256 a00c5c001eb2ee68cd143d57e2c23f47bc6eb88ed8bb8bd73876bd0541c626d3

See more details on using hashes here.

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

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