Skip to content

Commit

Permalink
all car tests pass
Browse files Browse the repository at this point in the history
  • Loading branch information
davidpeckham committed Feb 11, 2024
1 parent 19151d8 commit b52fb32
Show file tree
Hide file tree
Showing 12 changed files with 167 additions and 110 deletions.
12 changes: 8 additions & 4 deletions src/vin/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,16 @@
from vin.constants import VIN_LENGTH
from vin.constants import VIN_MODEL_YEAR_CHARACTERS
from vin.constants import VIN_POSITION_WEIGHTS
from vin.database import Vehicle
from vin.database import DecodedVehicle
from vin.database import VehicleDatabase


class DecodingFailedError(Exception):
class DecodingError(Exception):
"""A property is not available when you choose not to decode the VIN"""

pass


class DecodingRequiredError(Exception):
"""A property is not available when you choose not to decode the VIN"""

Expand Down Expand Up @@ -124,8 +125,11 @@ def _decode_vin(self) -> None:
Args:
vin: The 17-digit Vehicle Identification Number.
Raises:
DecodingError: Unable to decode VIN using NHTSA vPIC.
"""
self._vehicle: Vehicle = None
self._vehicle: DecodedVehicle = None
db_path = files("vin").joinpath("vehicle.db")
with VehicleDatabase(path=db_path) as db:
model_year = self._decode_model_year()
Expand All @@ -136,7 +140,7 @@ def _decode_vin(self) -> None:
if not vehicle:
vehicle = db.lookup_vehicle(self.wmi, self.vds, abs(model_year) - 30)
if vehicle is None:
raise DecodingFailedError()
raise DecodingError()
self._vehicle = vehicle

@classmethod
Expand Down
43 changes: 38 additions & 5 deletions src/vin/database.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,44 @@
import logging
from operator import concat
import re
import sqlite3
from collections import namedtuple
from dataclasses import dataclass


log = logging.getLogger(__name__)

Vehicle = namedtuple(
"Vehicle", "manufacturer model_year make1 make2 model series country vehicle_type truck_type"
"Vehicle",
"manufacturer model_year make1 make2 model series trim country vehicle_type truck_type",
)


@dataclass
class DecodedVehicle:
manufacturer: str
model_year: str
make1: str
make2: str
model: str
series: str
trim: str
country: str
vehicle_type: str
truck_type: str

@property
def name(self) -> str:
name = " ".join(
[
str(getattr(self, p))
for p in ["model_year", "make2", "model", "series", "trim"]
if getattr(self, p) is not None
]
)
return name


def regex(value, pattern):
"""REGEXP shim for SQLite versions that lack it"""
rex = re.compile("^" + pattern)
Expand Down Expand Up @@ -51,13 +79,14 @@ def query(self, sql: str, args: tuple = ()) -> list[sqlite3.Row]:
results = cursor.execute(sql, args).fetchall()
cursor.close()

print(f"{sql} {args}")
# print(sql)
print(args)
for result in results:
print(dict(result))

return results

def lookup_vehicle(self, wmi: str, vds: str, model_year: int) -> Vehicle | None:
def lookup_vehicle(self, wmi: str, vds: str, model_year: int) -> DecodedVehicle | None:
"""get vehicle details
Args:
Expand All @@ -67,7 +96,7 @@ def lookup_vehicle(self, wmi: str, vds: str, model_year: int) -> Vehicle | None:
Vehicle: the vehicle details
"""
if results := self.query(sql=LOOKUP_VEHICLE_SQL, args=(wmi, model_year, vds)):
details = {"series": None, "model_year": model_year}
details = {"series": None, "trim": None, "model_year": model_year}
for row in results:
if row["model"] is not None:
for attr in [
Expand All @@ -82,12 +111,14 @@ def lookup_vehicle(self, wmi: str, vds: str, model_year: int) -> Vehicle | None:
details[attr] = row[attr]
elif row["series"] is not None:
details["series"] = row["series"]
elif row["trim"] is not None:
details["trim"] = row["trim"]
else:
raise Exception(
f"expected model and series WMI {wmi} VDS {vds} "
f"model year {model_year}, but got {row}"
)
return Vehicle(**details)
return DecodedVehicle(**details)
return None


Expand All @@ -99,6 +130,7 @@ def lookup_vehicle(self, wmi: str, vds: str, model_year: int) -> Vehicle | None:
make2.name as make2,
model.name as model,
series.name as series,
trim.name as trim,
pattern.from_year,
pattern.to_year,
vehicle_type.name as vehicle_type,
Expand All @@ -112,6 +144,7 @@ def lookup_vehicle(self, wmi: str, vds: str, model_year: int) -> Vehicle | None:
left join make make2 on make2.id = make_model.make_id
left join model on model.id = pattern.model_id
left join series on series.id = pattern.series_id
left join trim on trim.id = pattern.trim_id
join wmi on wmi.code = pattern.wmi
join vehicle_type on vehicle_type.id = wmi.vehicle_type_id
left join truck_type on truck_type.id = wmi.truck_type_id
Expand Down
Binary file modified src/vin/vehicle.db
Binary file not shown.
5 changes: 5 additions & 0 deletions tests/cars/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Tests these vPIC vehicle types:

* Passenger Cars
* Multipurpose Passenger Vehicles (MPV)
* Light Trucks
3 changes: 3 additions & 0 deletions tests/cars/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# SPDX-FileCopyrightText: 2024-present David Peckham <[email protected]>
#
# SPDX-License-Identifier: MIT
15 changes: 15 additions & 0 deletions tests/cars/test_decode.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import parametrize_from_file
from vin import VIN


@parametrize_from_file
def test_decode(vin: str, model_year: int, make: str, model: str) -> None:
v = VIN(vin)
assert f"{model_year} {make} {model}".rstrip().replace(" ", " ") == v._vehicle.name


def test_inconclusive_model_year() -> None:
"""This 1995 Chevy truck VIN model year character doesn't conclusively
identify the model year, but we can decode it using vPIC data.
"""
assert VIN("2GCEC19Z0S1245490").model_year == 1995
Loading

0 comments on commit b52fb32

Please sign in to comment.