Skip to main content

Parse AAMVA PDF417 barcode data from US and Canadian driver's licenses and ID cards (Python port of aamva-parser).

Project description

py-aamva-parser

Python 3.10+ License: ISC Typed

Python library to parse AAMVA PDF417 barcode payloads from US and Canadian driver's licenses and ID cards.

This project is a Python port of aamva-parser by joptimus, originally implemented in TypeScript for Node.js. Parsing logic, version-specific field maps, and helpers are intended to track that upstream project.

The port was produced with AI assistance and reviewed by a human before publication.

Supports AAMVA barcode versions 01–12 (CDS 2000–2025). Includes helpers for age checks, full name formatting, and CDL detection (v12).

Requirements

  • Python 3.10+

Installation

The PyPI / pip name is aamva-parser (hyphen). The import name is aamva_parser (underscore): import aamva_parser or from aamva_parser import parse.

From PyPI:

pip install aamva-parser

From a git checkout:

pip install .
pip install -e ".[dev]"   # editable, with dev dependencies

With Poetry (2.0+ recommended; the package is PEP 621 metadata with setuptools as the build backend):

From PyPI:

poetry add aamva-parser

From a local path or Git URL:

poetry add git+https://github.com/btmash/py-aamva-parser.git

The importable package name is aamva_parser (underscore).

Usage

Parse a barcode

from aamva_parser import parse, get_version, is_expired, get_age, is_under_21, get_full_name

barcode_data = """
@
ANSI 636026080102DL00410288ZA03290015DLDAQD12345678
DCSPUBLIC
DDEN
DACJOHN
DDFN
DADQUINCY
DDGN
DCAD
DCBNONE
DCDNONE
DBD08242015
DBB01311970
DBA01312035
DBC1
DAU069 in
DAYGRN
DAG789 E OAK ST
DAIANYTOWN
DAJCA
DAK902230000
DCF83D9BN217QO983B1
DCGUSA
DAW180
DAZBRO
DCK12345678900000000000
DDB02142014
DDK1
ZAZAAN
ZAB
ZAC
"""

lic = parse(barcode_data)

print(lic.first_name)       # "JOHN"
print(lic.last_name)        # "PUBLIC"
print(lic.date_of_birth)    # datetime.datetime(1970, 1, 31, 0, 0)
print(lic.expiration_date)  # datetime.datetime(2035, 1, 31, 0, 0)
print(lic.expired)          # False (snapshot from parsed expiry vs "now")

age = get_age(barcode_data)
under_21 = is_under_21(barcode_data)
name = get_full_name(barcode_data)  # "JOHN QUINCY PUBLIC"

expired = is_expired(barcode_data)
version = get_version(barcode_data)  # "08"

Jurisdiction, CDL, and “acceptable” checks

from aamva_parser import get_state, is_cdl, is_acceptable, is_under_18

state = get_state(barcode_data)       # e.g. "CA", or None
cdl = is_cdl(barcode_data)             # True when v12 CDL indicator is set
ok = is_acceptable(barcode_data)      # issued, not expired, required fields present
minor = is_under_18(barcode_data)     # False if 18+ or no DOB

Instance methods on License

After lic = parse(...), you can call methods on the same object instead of re-parsing:

lic = parse(barcode_data)

if lic.is_expired():
    ...
if lic.has_been_issued() and lic.is_acceptable():
    ...

Types, annotations, and enums

parse() returns a License dataclass. ParsedLicense is a typing.TypeAlias to License for annotations; at runtime isinstance(lic, License) and isinstance(lic, ParsedLicense) are equivalent.

from aamva_parser import License, ParsedLicense, Gender, EyeColor, parse

def summarize(card: ParsedLicense) -> str:
    return f"{card.first_name} {card.last_name} ({card.state})"

lic: License = parse(barcode_data)

if lic.gender == Gender.MALE:
    print("Male")

if lic.eye_color == EyeColor.GREEN:
    print("Green eyes")

Package version

import aamva_parser

print(aamva_parser.__version__)

LicenseParser class

For multiple operations on the same raw string:

from aamva_parser import LicenseParser

parser = LicenseParser(barcode_data)
version = parser.parse_version()
lic = parser.parse()

Each module-level helper (get_age, is_expired, …) parses the string again. Prefer LicenseParser when you need the version, a License, and several helpers, so you pay for one parse.

API

The package exports License (concrete result type), ParsedLicense (type alias to License for annotations), enums, and LicenseParser. Module-level functions:

Each helper such as get_full_name(barcode) or is_acceptable(barcode) runs parse(barcode) internally. For hot paths, build one LicenseParser and call parse() once, then read fields or call License methods.

Deprecated Parse / GetVersion / IsExpired (PascalCase) are re-exported from the package root; prefer snake_case.

Function Returns Description
parse(barcode_data) License Parse PDF417 payload into a License.
get_version(barcode_data) str | None AAMVA version (e.g. "08").
is_expired(barcode_data) bool Whether the expiration date is in the past.
get_age(barcode_data) int | None Age in years, or None without DOB.
is_under_21(barcode_data) bool Under 21, or False if 21+ or no DOB.
is_under_18(barcode_data) bool Under 18, or False if 18+ or no DOB.
is_acceptable(barcode_data) bool Not expired, issued, and required fields set.
get_full_name(barcode_data) str | None "FIRST MIDDLE LAST"; None if no names.
get_state(barcode_data) str | None Jurisdiction (e.g. "CA").
is_cdl(barcode_data) bool CDL indicator set (v12 / CDS 2025).

Deprecated aliases

Deprecated Use instead
Parse() parse()
GetVersion() get_version()
IsExpired() is_expired()

License methods

  • is_expired() — compare expiration_date to the current time.
  • has_been_issued()True if issue_date is set and the current time is after it.
  • is_acceptable() — stricter checklist (name, address, dates, document ID, etc.).

Supported fields (License)

Field Type Attribute
First name str | None first_name
Last name str | None last_name
Middle name str | None middle_name
Expiration date datetime | None expiration_date
Issue date datetime | None issue_date
Date of birth datetime | None date_of_birth
Gender Gender gender
Eye color EyeColor eye_color
Hair color HairColor hair_color
Height (inches) float | None height
Weight str | None weight
Street address str | None street_address
Street address line 2 str | None street_address_supplement
City str | None city
State str | None state
Postal code str | None postal_code
Driver's license ID str | None drivers_license_id
Document ID str | None document_id
Issuing country IssuingCountry country
Name suffix NameSuffix suffix
First name truncation Truncation first_name_truncation
Middle name truncation Truncation middle_name_truncation
Last name truncation Truncation last_name_truncation
Place of birth str | None place_of_birth
Audit information str | None audit_information
Inventory control number str | None inventory_control_number
First name alias str | None first_name_alias
Last name alias str | None last_name_alias
Suffix alias str | None suffix_alias
CDL indicator str | None cdl_indicator
Non-domiciled indicator str | None non_domiciled_indicator
Enhanced credential indicator str | None enhanced_credential_indicator
Permit indicator str | None permit_indicator
Expired (parsed snapshot) bool expired
AAMVA version str | None version
Raw barcode str | None pdf417

AAMVA version support

CDS Year Barcode Supported
2000 2000 01 Yes
2003 2003 02 Yes
2005 2005 03 Yes
2009 2009 04–05 Yes
2010 2010 06 Yes
2011 2011 07 Yes
2012 2012 08 Yes
2013 2013 09 Yes
2016 2016 10 Yes
2020 2020 11 Yes
2025 2025 12 Yes

Example

Raw PDF417 payload (version 08)

@

ANSI 636026080102DL00410288ZA03290015DLDAQD12345678
DCSPUBLIC
DDEN
DACJOHN
DDFN
DADQUINCY
DDGN
DCAD
DCBNONE
DCDNONE
DBD08242015
DBB01311970
DBA01312035
DBC1
DAU069 in
DAYGRN
DAG789 E OAK ST
DAIANYTOWN
DAJCA
DAK902230000
DCF83D9BN217QO983B1
DCGUSA
DAW180
DAZBRO
DCK12345678900000000000
DDB02142014
DDK1
ZAZAAN
ZAB
ZAC

Parsed values (illustrative)

# After lic = parse(barcode_data)
{
    "first_name": "JOHN",
    "last_name": "PUBLIC",
    "middle_name": "QUINCY",
    "date_of_birth": datetime(1970, 1, 31),
    "expiration_date": datetime(2035, 1, 31),
    "issue_date": datetime(2015, 8, 24),
    "gender": Gender.MALE,
    "eye_color": EyeColor.GREEN,
    "hair_color": HairColor.BROWN,
    "height": 69,
    "weight": "180",
    "street_address": "789 E OAK ST",
    "city": "ANYTOWN",
    "state": "CA",
    "postal_code": "902230000",
    "drivers_license_id": "D12345678",
    "document_id": "83D9BN217QO983B1",
    "country": IssuingCountry.UNITED_STATES,
    "inventory_control_number": "12345678900000000000",
    "expired": False,
    "version": "08",
}

AAMVA element IDs by version

Bold = mandatory in upstream docs. -- = not used in that barcode version.

Tables are split so they fit typical viewports; the two halves are the same data as one wide 01–12 grid.

Versions 01–06

Field 01 02 03 04 05 06
First Name DAC DCT DCT DAC DAC DAC
Last Name DAB DCS DCS DCS DCS DCS
Middle Name DAD DAD DAD DAD DAD DAD
Expiration Date DBA DBA DBA DBA DBA DBA
Issue Date DBD DBD DBD DBD DBD DBD
Date of Birth DBB DBB DBB DBB DBB DBB
Gender DBC DBC DBC DBC DBC DBC
Eye Color DAY DAY DAY DAY DAY DAY
Height DAU DAU DAU DAU DAU DAU
Street Address DAG DAG DAG DAG DAG DAG
City DAI DAI DAI DAI DAI DAI
State DAJ DAJ DAJ DAJ DAJ DAJ
Postal Code DAK DAK DAK DAK DAK DAK
License ID DBJ DAQ DAQ DAQ DAQ DAQ
Document ID -- DCF DCF DCF DCF DCF
Country -- DCG DCG DCG DCG DCG
Weight -- DAW DAW DAW DAW DAW
CDL Indicator -- -- -- -- -- --
Non-Domiciled Indicator -- -- -- -- -- --
Enhanced Credential -- -- -- -- -- --
Permit Indicator -- -- -- -- -- --

Versions 07–12

Field 07 08 09 10 11 12
First Name DAC DAC DAC DAC DAC DAC
Last Name DCS DCS DCS DCS DCS DCS
Middle Name DAD DAD DAD DAD DAD DAD
Expiration Date DBA DBA DBA DBA DBA DBA
Issue Date DBD DBD DBD DBD DBD DBD
Date of Birth DBB DBB DBB DBB DBB DBB
Gender DBC DBC DBC DBC DBC DBC
Eye Color DAY DAY DAY DAY DAY DAY
Height DAU DAU DAU DAU DAU DAU
Street Address DAG DAG DAG DAG DAG DAG
City DAI DAI DAI DAI DAI DAI
State DAJ DAJ DAJ DAJ DAJ DAJ
Postal Code DAK DAK DAK DAK DAK DAK
License ID DAQ DAQ DAQ DAQ DAQ DAQ
Document ID DCF DCF DCF DCF DCF DCF
Country DCG DCG DCG DCG DCG DCG
Weight DAW DAW DAW DAW DAW DAW
CDL Indicator -- -- -- -- -- DDM
Non-Domiciled Indicator -- -- -- -- -- DDN
Enhanced Credential -- -- -- -- -- DDO
Permit Indicator -- -- -- -- -- DDP

Development

Automated tests mirror the upstream Jest layout under js-aamva-parser/tests/ (for example regex.test.tstests/test_regex.py, indexApi.test.tstests/test_index_api.py). Deprecated PascalCase helpers are implemented in src/aamva_parser/compat.py.

pip and venv

python -m venv .venv
source .venv/bin/activate   # Windows: .venv\Scripts\activate
pip install -e ".[dev]"
ruff check src tests
ruff format --check src tests
mypy
pytest

Poetry

From the repository root (Poetry 2.0+):

poetry install -E dev
poetry run ruff check src tests
poetry run ruff format --check src tests
poetry run mypy
poetry run pytest

Optional: poetry shell, then run the same commands without the poetry run prefix.

License

ISC

Credits

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

aamva_parser-0.1.2.tar.gz (23.0 kB view details)

Uploaded Source

Built Distribution

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

aamva_parser-0.1.2-py3-none-any.whl (16.4 kB view details)

Uploaded Python 3

File details

Details for the file aamva_parser-0.1.2.tar.gz.

File metadata

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

File hashes

Hashes for aamva_parser-0.1.2.tar.gz
Algorithm Hash digest
SHA256 2798dd72b35c1a062c60672df8f95b5968913bb5968c43f67f3173dc26e5814c
MD5 48f39108a491084088561f83f166ef0b
BLAKE2b-256 e1e863015cbaf885d75e29dd47bf0666848c0f3e65ffefd58dca2dcd940c1691

See more details on using hashes here.

Provenance

The following attestation bundles were made for aamva_parser-0.1.2.tar.gz:

Publisher: publish.yml on btmash/py-aamva-parser

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

File details

Details for the file aamva_parser-0.1.2-py3-none-any.whl.

File metadata

  • Download URL: aamva_parser-0.1.2-py3-none-any.whl
  • Upload date:
  • Size: 16.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for aamva_parser-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 864c387466b9a318cf79ea59357b8089f030b7bede27d3ace7065a10f98bc3da
MD5 8d6329bfc3ce1816feae40e6db88493c
BLAKE2b-256 8466697467c01f36eb1f6ac1b8f491e207cf0bce061cb62cbb55e474c550ecf6

See more details on using hashes here.

Provenance

The following attestation bundles were made for aamva_parser-0.1.2-py3-none-any.whl:

Publisher: publish.yml on btmash/py-aamva-parser

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