Skip to main content

doorplate

CI PyPI Python versions PyPI downloads License: MIT OR Apache-2.0

Parse US addresses into components, with USPS Publication 28 and RESO output, a match key, a URL-safe slug, and diagnostics. A Rust library with Python bindings (PyO3, abi3 wheels for Python 3.10 and later); no runtime dependencies and no data to download.

This library formats and parses; it does not validate that an address exists or can receive mail.

Alpha release (0.2.0). Expect rough edges. The match key is versioned (k1:) and will probably get new versions as the parser improves, so store the version prefix with your keys and plan to re-key. Accuracy and speed are measurements on a few datasets, not guarantees.

Help wanted: messy addresses. If it parses your addresses badly, please open an issue with a sample. I want to fix them. Private data can be anonymized first (how).

Quickstart

pip install doorplate
import doorplate as dp

a = dp.parse("123 north main street apt 4, Miami FL 33101")
a.street_name          # "MAIN"
a.unit_number          # "4"
a.place                # {"name": "MIAMI", "state": "FL", "county": "DADE", ...}
a.warnings             # []
a.to_usps()["line1"]   # "123 N MAIN ST APT 4"
a.to_display()["line1"]  # "123 N Main St Apt 4" (to_long(): "123 North Main Street Apartment 4"); display only, not Pub 28
a.key()                # "k1:S|123|N|MAIN|ST||FL|33101"
a.slug()               # "123-n-main-st-apt-4-miami-fl-33101"
a.to_reso()            # RESO Data Dictionary 2.0 fields
a.to_dict()            # everything above as plain Python values

dp.parse_fields(line1="THE COVE", line2="1111 ASBURY WAY",
                city="Orlando", state="FL", zip="32801")
dp.parse_many(["1 Main St, Miami FL", "PO Box 5, Tampa FL"])  # GIL released
dp.parse_fields_many(line1=["1 Main St", "PO Box 5"], city=["Miami", "Tampa"])
dp.parse("281 EVELYN DR DOTHAN", default_state="AL").city     # "DOTHAN"
  • Unusable input (no address text at all) raises ParseError, a ValueError; in parse_many that element is None. Anything else comes back with warnings.
  • default_state is a lookup hint for input with no state; it never fills state. A value that is not a US state, DC or territory raises ValueError.
  • confidence is reserved and always None: no score is computed.

Install

pip install doorplate                  # the parser
pip install "doorplate[pandas]"        # plus the pandas helper
pip install "doorplate[polars]"        # plus the Polars helper

Python 3.10 or later. Wheels are built for Linux (x86_64, aarch64), macOS (universal2) and Windows (x64), and tested on Python 3.10 and 3.13. To build from source (Rust 1.83 or later):

pip install maturin
cd crates/doorplate-py
maturin develop --release      # builds the extension into your virtualenv
pytest tests

The API

Call Returns
parse(text, default_state=None) a ParsedAddress
parse_fields(line1=, line2=, city=, state=, zip=, default_state=None) a ParsedAddress from pre-split fields
parse_fields_many(line1=, line2=, city=, state=, zip=, default_state=None) the same as a batch of pre-split rows, given as columns (lists or tuples; an element may be None). Give any of the columns; they must have the same length
parse_many(texts, default_state=None) a list of ParsedAddress (or None for an element with no address text), in order; parses in parallel with the GIL released
Parser(default_state=None) an object with the same parse, parse_fields and parse_many
tag(text) (dict, type) with usaddress's labels and "Street Address" / "PO Box" / "Ambiguous"
parse_tokens(text) [(token, label)] with usaddress's labels
address_type(text) street, po_box, rural_route, military, general_delivery, dual or unknown

A ParsedAddress is read-only and typed (py.typed, stubs in __init__.pyi). It has the components (street_number, street_dir_prefix, street_name, street_suffix, street_dir_suffix, unit_type, unit_number, second_unit_type, second_unit_number, po_box, attention, building, city, state, zip5, zip4, military_post_office), the structured shapes (highway, rural_route, military, place, plain dicts), general_delivery, warnings (stable snake_case codes), unparsed, input and confidence, and the methods key(unit=False), slug(), to_usps(max_line=None), to_display(), to_long(), to_reso(), to_usaddress_tags(), tag(), address_type(), repeated_labels() and to_dict().

A ParsedAddress computes its output formats when you ask, so take only what you need. The key is a versioned contract (key format); the slug is for display.

Batches, pandas and Polars

parse_many(texts) takes a list or tuple of strings, releases the GIL, parses in parallel with Rust threads, and returns the results in order. On 100,000 addresses on a 14-thread machine (crates/doorplate-py/benchmarks/bench_batch.py):

Time Per second Speed-up
a loop of parse 2.11 s 47,000 1x
parse_many 0.32 s 315,000 6.6x
parse_many and a pandas DataFrame of 7 columns 0.49 s 205,000 4.3x
parse_many and a Polars DataFrame of 7 columns 0.40 s 252,000 5.3x

Helpers. Install the extra and parse a column in one call:

from doorplate.polars import parse_column   # or doorplate.pandas

out = df.hstack(parse_column(df["address"]))                    # Polars
out = df.join(parse_column(df["address"], default_state="FL"))  # pandas

parse_column(series, *, columns=None, prefix="addr_", default_state=None) returns a DataFrame with one string column per name, in the same order (and, in pandas, the same index) as the input. A null, blank or non-string row, or one with no address text, is null in every column. The default columns are street_number, street_dir_prefix, street_name, street_suffix, street_dir_suffix, unit_type, unit_number, city, state, zip5, zip4, address_type, key, slug and warnings (codes joined by commas). AVAILABLE_COLUMNS in either module lists the rest (key_unit, usps_line1, usps_line2, usps_last_line, po_box, attention, building, unparsed, ...). They are built over parse_many.

Polars by hand (when you want other fields):

import polars as pl
import doorplate as dp

df = pl.read_csv("addresses.csv")
parsed = dp.parse_many(df["address"].fill_null("").to_list())  # None for a blank row

cols = ["street_number", "street_name", "street_suffix", "unit_number", "city", "state", "zip5"]
flat = pl.DataFrame(
    {f"addr_{c}": [getattr(a, c) if a else None for a in parsed] for c in cols}
    | {
        "addr_key": [a.key() if a else None for a in parsed],
        "addr_warnings": [",".join(a.warnings) if a else None for a in parsed],
    },
    schema={f"addr_{c}": pl.String for c in cols}
    | {"addr_key": pl.String, "addr_warnings": pl.String},
)
out = df.hstack(flat)

(Give the schema: a column that is None in every row would otherwise be typed Null.) For every field, build structs from to_dict() and pick fields from them:

full = pl.DataFrame([a.to_dict() if a else None for a in parsed], infer_schema_length=None)
full.select(
    "street_name",
    pl.col("place").struct.field("name").alias("place_name"),
    pl.col("usps").struct.field("line1").alias("usps_line1"),
)

pandas by hand:

import pandas as pd
import doorplate as dp

df = pd.read_csv("addresses.csv")
parsed = dp.parse_many(df["address"].fillna("").tolist())  # None for a blank row

cols = ["street_number", "street_name", "street_suffix", "unit_number", "city", "state", "zip5"]
flat = pd.DataFrame([{c: getattr(a, c) for c in cols} if a else {} for a in parsed])
flat["key"] = [a.key() if a else None for a in parsed]
flat["warnings"] = [",".join(a.warnings) if a else None for a in parsed]
out = pd.concat([df, flat.add_prefix("addr_")], axis=1)

For every field with pandas, use to_dict() and pd.json_normalize([a.to_dict() if a else {} for a in parsed]).

Calling to_dict() on every row costs about as much as parsing again (a loop with to_dict ran at 24,000 per second), and reading attributes from 100,000 Python objects is now most of the time, so pick columns when you can. Pre-split columns (line1, city, ...): call parse_fields_many(line1=df["line1"].tolist(), city=...).

Coming from usaddress

usaddress doorplate
usaddress.parse(s) parse_tokens(s): [(token, label)]
usaddress.tag(s) tag(s): (dict, "Street Address" | "PO Box" | "Ambiguous")
RepeatedLabelError not raised: the runs are joined and parse(s).repeated_labels() lists the labels
(none) parse(s).address_type(): street, po_box, rural_route, military, general_delivery, dual, unknown
(none) parse(s, default_state="FL"): a state hint for input with no state

Tokens that cannot be placed are left out of tag(), and some usaddress labels (landmarks, corners, recipients) are not modeled. The mapping and the differences: usaddress compatibility.

More

Licensed under either of MIT or Apache-2.0 at your option.

Metadata

Release files for doorplate 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for doorplate 0.2.0
File Size Uploaded
doorplate-0.2.0.tar.gz 861.3 kB Details

Built distributions (wheels)

Table of built distributions (wheels) for doorplate 0.2.0
File
doorplate-0.2.0-cp310-abi3-win_amd64.whl CPython 3.10 abi3 Windows x86-64 Details
doorplate-0.2.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl CPython 3.10 abi3 Linux glibc 2.17+ x86-64 Details
doorplate-0.2.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl CPython 3.10 abi3 Linux glibc 2.17+ ARM64 Details
doorplate-0.2.0-cp310-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl CPython 3.10 abi3 macOS 10.12+ x86-64, macOS 10.12+ universal2 (ARM64, x86-64), macOS 11.0+ ARM64 Details

Total release size: 9.5 MB

Release files / doorplate-0.2.0.tar.gz

Download URL doorplate-0.2.0.tar.gz
Size 861.3 kB
Tags Source
SHA-256 checksum
How to use checksums
7c838668764bc56aa02f98a4a4246a12ba1d7c9961e75ce4ed86a1e8515a30fc
BLAKE2b-256 checksum
How to use checksums
edfbb5d01ac63dcbba40b5886a4b57f37512d67232364fcadd073993e10a4fa6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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 Oct 18, 2026.

Transparency log

Release files / doorplate-0.2.0-cp310-abi3-win_amd64.whl

Download URL doorplate-0.2.0-cp310-abi3-win_amd64.whl
Size 1.7 MB
Tags CPython 3.10 Windows x86-64 abi3
SHA-256 checksum
How to use checksums
99ccd990c3c26db94e1274e213a71e6ba7a20b397a2dac8d268fb2a959acb8be
BLAKE2b-256 checksum
How to use checksums
d27ad709cb006101df1c7a90dcec30a0bbc8e535809716a9bb6a572fe6e8c40a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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 Oct 18, 2026.

Transparency log

Release files / doorplate-0.2.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL doorplate-0.2.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 1.8 MB
Tags CPython 3.10 Linux glibc 2.17+ x86-64 abi3
SHA-256 checksum
How to use checksums
5d69e1a4de1df9f2c7ecfce8babc8578f2e13ca0165ad961ee3010b2811c199d
BLAKE2b-256 checksum
How to use checksums
fff5aac7df045210d5716ed119586d32302ca4c8e3def3bccef4e13ada55f11a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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 Oct 18, 2026.

Transparency log

Release files / doorplate-0.2.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl

Download URL doorplate-0.2.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Size 1.8 MB
Tags CPython 3.10 Linux glibc 2.17+ ARM64 abi3
SHA-256 checksum
How to use checksums
2a5ce9eb46e27967aec071ac079cd6b4dbc7cf9433a90349365ee723596e93ea
BLAKE2b-256 checksum
How to use checksums
d1eb37ceb4350e57a1dc176b5ee026540191b3c1e2871d32c98ff07c290392a3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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 Oct 18, 2026.

Transparency log

Release files / doorplate-0.2.0-cp310-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl

Download URL doorplate-0.2.0-cp310-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl
Size 3.4 MB
Tags CPython 3.10 abi3 macOS 10.12+ universal2 (ARM64, x86-64) macOS 10.12+ x86-64 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
fe13e6af4e7343a96d40f870c77f92298ff6631943692e6f1e7a305e6760c0d6
BLAKE2b-256 checksum
How to use checksums
c9f7cc50a20003f7afc8a2ed70ff1aea6848c6f11ea7c872ecfb19faab5b8df1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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 Oct 18, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.2.0 This release

5 release files

0.1.0

5 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page