Skip to content

Commit

Permalink
Merge pull request #5 from benhovinga/dev
Browse files Browse the repository at this point in the history
Version 0.4-alpha
  • Loading branch information
benhovinga authored Dec 31, 2023
2 parents 131947e + 45c1838 commit ab5c074
Show file tree
Hide file tree
Showing 13 changed files with 388 additions and 242 deletions.
18 changes: 11 additions & 7 deletions .github/workflows/python-app.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
# This workflow will install Python dependencies, run tests and lint with a single version of Python
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python
# This workflow will install Python dependencies, run tests and lint with multiple versions of Python.

name: Python application

Expand All @@ -16,24 +15,29 @@ jobs:
build:

runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]

steps:
- uses: actions/checkout@v3
- name: Set up Python 3.12
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v3
with:
python-version: "3.12"
python-version: ${{ matrix.python-version }}
- name: Display Python version
run: python -c "import sys; print(sys.version)"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install flake8 pytest
pip install flake8 pytest pytest-cov
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
- name: Lint with flake8
run: |
# stop the build if there are Python syntax errors or undefined names
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
flake8 . --count --exit-zero --statistics
- name: Test with pytest
run: |
pytest
pytest -v --cov --no-cov-on-fail --cov-report term-missing
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
__pycache__
_hidden.py
htmlcov
.coverage
.DS_Store
.vscode
_old_stuff
.pytest_cache
23 changes: 0 additions & 23 deletions .vscode/launch.json

This file was deleted.

14 changes: 0 additions & 14 deletions .vscode/settings.json

This file was deleted.

3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# AAMVA Driver License and Identification Card 2D Barcode Decoder

Version: v0.3-alpha
Version: v0.4-alpha

This Python library facilitates the decoding of the PDF417 2D Barcode found on the back of most Drivers Licenses and Identification Cards in Canada and USA.

Expand All @@ -22,6 +22,7 @@ Below are some resources that made creating this library possible.
- A.9.3 Driver Hair Color
- A.9.8 Driver Race and Ethnicity
- [List of Issuer Identification Numbers (IIN)](https://www.aamva.org/identity/issuer-identification-numbers-(iin)) (aamva.org)
- [Some older standards listed here](https://docs.scandit.com/parser/dlid.html)


## Deep Dive
Expand Down
71 changes: 71 additions & 0 deletions aamva_standard/barcode.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
COMPLIANCE_INDICATOR = "@"
DESIGNATOR_LENGTH = 10


def header_length(version: int) -> int:
return 19 if version < 2 else 21


def trim_to_indicator(_str: str, indicator: str) -> str:
if _str[0] != indicator:
try:
index = _str.index(indicator)
except ValueError:
raise ValueError(f"Indicator \"{indicator}\" is missing")
_str = _str[index:]
return _str


def read_file_header(file: str) -> dict:
file = trim_to_indicator(file, COMPLIANCE_INDICATOR)
if file[4:9] != "ANSI ":
raise ValueError(f"header file type missing \"{file[4:9]}\" != \"ANSI \"")
header = dict(
data_element_separator=str(file[1]),
record_separator=str(file[2]),
segment_terminator=str(file[3]),
issuer_identification_number=int(file[9:15]),
aamva_version_number=int(file[15:17])
)
if header["aamva_version_number"] < 2:
header["jurisdiction_version_number"] = 0
header["number_of_entries"] = int(file[17:19])
else:
header["jurisdiction_version_number"] = int(file[17:19])
header["number_of_entries"] = int(file[19:21])
return header


def read_subfile_designator(file: str, aamva_version_number: int, designator_index: int) -> tuple:
file = trim_to_indicator(file, COMPLIANCE_INDICATOR)
cursor = designator_index * DESIGNATOR_LENGTH + header_length(aamva_version_number)
return (str(file[cursor:cursor + 2]), int(file[cursor + 2:cursor + 6]), int(file[cursor + 6:cursor + 10]))


def read_subfile(file: str, data_element_separator: str, segment_terminator: str, subfile_type: str, offset: int, length: int) -> dict:
end_offset = offset + length - 1
if file[offset:offset + 2] != subfile_type:
raise ValueError(f"Subfile is missing subfile type {ascii(file[offset:offset + 2])} != {ascii(subfile_type)}")
elif file[end_offset] != segment_terminator:
raise ValueError(f"Subfile is missing segment terminator {ascii(file[end_offset])} != {ascii(segment_terminator)}")
subfile = {}
elements = filter(
None, file[offset + 2:end_offset].split(data_element_separator))
for item in elements:
subfile[item[:3]] = item[3:]
return subfile


def read_file(file: str) -> dict:
file = trim_to_indicator(file, COMPLIANCE_INDICATOR)
header = read_file_header(file)
if header["number_of_entries"] < 1:
raise ValueError("number of entries cannot be less than 1")
subfiles = {}
for i in range(header["number_of_entries"]):
designator = read_subfile_designator(file, header["aamva_version_number"], i)
subfiles[designator[0]] = read_subfile(file, header["data_element_separator"], header["segment_terminator"], *designator)
return {
"header": header,
"subfiles": subfiles
}
98 changes: 98 additions & 0 deletions aamva_standard/issuing_authority.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
from dataclasses import dataclass


@dataclass(frozen=True)
class IssuingAuthority:
issuer_identification_number: int
jurisdiction: str
abbr: str
country: str


ISSUING_AUTHORITIES = (
# Source:
# https://www.aamva.org/identity/issuer-identification-numbers-(iin)
IssuingAuthority(604426, "Prince Edward Island", "PE", "Canada"),
IssuingAuthority(604427, "American Samoa", "AS", "USA"),
IssuingAuthority(604428, "Quebec", "GC", "Canada"),
IssuingAuthority(604429, "Yukon", "YT", "Canada"),
IssuingAuthority(604430, "Norther Marianna Islands", "MP", "USA"),
IssuingAuthority(604431, "Puerto Rico", "PR", "USA"),
IssuingAuthority(604432, "Alberta", "AB", "Canada"),
IssuingAuthority(604433, "Nunavut", "NU", "Canada"),
IssuingAuthority(604434, "Northwest Territories", "NT", "Canada"),
IssuingAuthority(636000, "Virginia", "VA", "USA"),
IssuingAuthority(636001, "New York", "NY", "USA"),
IssuingAuthority(636002, "Massachusetts", "MA", "USA"),
IssuingAuthority(636003, "Maryland", "MD", "USA"),
IssuingAuthority(636004, "North Carolina", "NC", "USA"),
IssuingAuthority(636005, "South Carolina", "SC", "USA"),
IssuingAuthority(636006, "Connecticut", "CT", "USA"),
IssuingAuthority(636007, "Louisiana", "LA", "USA"),
IssuingAuthority(636008, "Montana", "MT", "USA"),
IssuingAuthority(636009, "New Mexico", "NM", "USA"),
IssuingAuthority(636010, "Florida", "FL", "USA"),
IssuingAuthority(636011, "Delaware", "DE", "USA"),
IssuingAuthority(636012, "Ontario", "ON", "Canada"),
IssuingAuthority(636013, "Nova Scotia", "NS", "Canada"),
IssuingAuthority(636014, "California", "CA", "USA"),
IssuingAuthority(636015, "Texas", "TX", "USA"),
IssuingAuthority(636016, "Newfoundland", "NF", "Canada"),
IssuingAuthority(636017, "New Brunswick", "NB", "Canada"),
IssuingAuthority(636018, "Iowa", "IA", "USA"),
IssuingAuthority(636019, "Guam", "GU", "USA"),
IssuingAuthority(636020, "Colorado", "GM", "USA"),
IssuingAuthority(636021, "Arkansas", "AR", "USA"),
IssuingAuthority(636022, "Kansas", "KS", "USA"),
IssuingAuthority(636023, "Ohio", "OH", "USA"),
IssuingAuthority(636024, "Vermont", "VT", "USA"),
IssuingAuthority(636025, "Pennsylvania", "PA", "USA"),
IssuingAuthority(636026, "Arizona", "AZ", "USA"),
IssuingAuthority(636027, "State Dept. (Diplomatic)", None, "USA"),
IssuingAuthority(636028, "British Columbia", "BC", "Canada"),
IssuingAuthority(636029, "Oregon", "OR", "USA"),
IssuingAuthority(636030, "Missouri", "MO", "USA"),
IssuingAuthority(636031, "Wisconsin", "WI", "USA"),
IssuingAuthority(636032, "Michigan", "MI", "USA"),
IssuingAuthority(636033, "Alabama", "AL", "USA"),
IssuingAuthority(636034, "North Dakota", "ND", "USA"),
IssuingAuthority(636035, "Illinois", "IL", "USA"),
IssuingAuthority(636036, "New Jersey", "NJ", "USA"),
IssuingAuthority(636037, "Indiana", "IN", "USA"),
IssuingAuthority(636038, "Minnesota", "MN", "USA"),
IssuingAuthority(636039, "New Hampshire", "NH", "USA"),
IssuingAuthority(636040, "Utah", "UT", "USA"),
IssuingAuthority(636041, "Maine", "ME", "USA"),
IssuingAuthority(636042, "South Dakota", "SD", "USA"),
IssuingAuthority(636043, "District of Columbia", "DC", "USA"),
IssuingAuthority(636044, "Saskatchewan", "SK", "Canada"),
IssuingAuthority(636045, "Washington", "WA", "USA"),
IssuingAuthority(636046, "Kentucky", "KY", "USA"),
IssuingAuthority(636047, "Hawaii", "HI", "USA"),
IssuingAuthority(636048, "Manitoba", "MB", "Canada"),
IssuingAuthority(636049, "Nevada", "NV", "USA"),
IssuingAuthority(636050, "Idaho", "ID", "USA"),
IssuingAuthority(636051, "Mississippi", "MS", "USA"),
IssuingAuthority(636052, "Rhode Island", "RI", "USA"),
IssuingAuthority(636053, "Tennessee", "TN", "USA"),
IssuingAuthority(636054, "Nebraska", "NE", "USA"),
IssuingAuthority(636055, "Georgia", "GA", "USA"),
IssuingAuthority(636056, "Coahuila", "CU", "Mexico"),
IssuingAuthority(636057, "Hidalgo", "HL", "Mexico"),
IssuingAuthority(636058, "Oklahoma", "OK", "USA"),
IssuingAuthority(636059, "Alaska", "AK", "USA"),
IssuingAuthority(636060, "Wyoming", "WY", "USA"),
IssuingAuthority(636061, "West Virginia", "WV", "USA"),
IssuingAuthority(636062, "Virgin Islands", "VI", "USA"),
)


def get_authority_by_id(id_number: int) -> IssuingAuthority:
try:
issuing_authority = tuple(
filter(
lambda i: i.issuer_identification_number == id_number,
ISSUING_AUTHORITIES))[0]
except IndexError:
raise KeyError(f"id_number: {id_number}, not found")
return issuing_authority
Loading

0 comments on commit ab5c074

Please sign in to comment.