doorplate
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, aValueError; inparse_manythat element isNone. Anything else comes back withwarnings. default_stateis a lookup hint for input with no state; it never fillsstate. A value that is not a US state, DC or territory raisesValueError.confidenceis reserved and alwaysNone: 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
- Project README: what it does, the Rust library, the command-line tool.
- How it works, benchmarks and the roadmap (a confidence score and customization packs are planned).
- Examples: a notebook tour and DataFrame pipelines.
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)
| File | Size | Uploaded | |
|---|---|---|---|
| doorplate-0.2.0.tar.gz | 861.3 kB | Details |
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| 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 logRelease 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 logRelease 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 logRelease 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 logRelease 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