diff --git a/.gitignore b/.gitignore index f24cd99..799ae11 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ # Packages *.egg *.egg-info +venv dist build eggs diff --git a/README.rst b/README.rst index d56d257..8a44ac3 100644 --- a/README.rst +++ b/README.rst @@ -34,6 +34,8 @@ Low level API example Canada >>> v.year 2006 + >>> v.manufacturer + Honada Canda >>> v.is_pre_2010 True >>> v.wmi @@ -48,7 +50,6 @@ Low level API example False - Methods ------- diff --git a/libvin/__init__.py b/libvin/__init__.py index 8a75200..86960e8 100644 --- a/libvin/__init__.py +++ b/libvin/__init__.py @@ -1,3 +1,2 @@ from conversion import convert from decoding import decode -from verification import is_valid diff --git a/libvin/check.py b/libvin/check.py deleted file mode 100644 index e69de29..0000000 diff --git a/libvin/decoding.py b/libvin/decoding.py index a8d4d77..dd996fe 100644 --- a/libvin/decoding.py +++ b/libvin/decoding.py @@ -4,28 +4,88 @@ """ from libvin.static import * +from libvin import wmi_map +VIN_SIZE = 17 +"""For model years 1981 to present, the VIN is composed of 17 +alphanumeric values""" + + +PROHIBITED_LETTERS = 'IOQ' +r"""The letters I,O, Q are prohibited from any VIN position""" + + +PROHIBITED_MODEL_LETTERS = 'UZ0' +r"""The tenth position of the VIN represents the Model Year and +does not permit the use of the characters U and Z, as well +as the numeric zero (0) +""" + + +r"""The ninth position of the VIN is a calculated value based on +the other 16 alphanumeric values, it's called the +"Check Digit". The result of the check digit can ONLY be a +numeric 0-9 or letter "X". +""" +ALLOWED_CHECK_DIGIT_LETTERS = 'X0123456789' + + +class BadVin(Exception): + r"""If VIN is incorrect according to specification, exception is raised.""" + pass + class Vin(object): + def __init__(self, vin): + if not vin: + raise BadVin('Vin is empty') + self.vin = vin.upper() + + if not self.is_valid(): + raise BadVin('Vin is not valid') @property def country(self): """ Returns the World Manufacturer's Country. """ + if not self.vin[0] in WORLD_MANUFACTURER_MAP: + return None + countries = WORLD_MANUFACTURER_MAP[self.vin[0]]['countries'] for codes in countries: if self.vin[0] in codes: return countries[codes] - return 'Unknown' + return None def decode(self): return self.vin + def is_valid(self): + r"""Returns True if a VIN is valid, otherwise returns False.""" + if hasattr(self, '_is_valid'): + return getattr(self, '_is_valid') + + _is_valid = False + + if len(self.vin) != VIN_SIZE: + _is_valid = False + elif any(x in PROHIBITED_LETTERS for x in self.vin): + _is_valid = False + elif self.vin[9] in PROHIBITED_MODEL_LETTERS: + _is_valid = False + elif not self.vin[8] in ALLOWED_CHECK_DIGIT_LETTERS: + _is_valid = False + else: + _is_valid = True + + setattr(self, '_is_valid', _is_valid) + return _is_valid + @property def is_pre_2010(self): """ @@ -42,44 +102,6 @@ def is_pre_2010(self): """ return self.vin[6].isdigit() - @property - def is_valid(self): - """ - Returns True if a VIN is valid, otherwise returns False. - """ - if len(self.vin) != 17: - """ - For model years 1981 to present, the VIN is composed of 17 - alphanumeric values - """ - return False - - elif any(x in 'IOQ' for x in self.vin): - """ - The letters I,O, Q are prohibited from any VIN position - """ - return False - - elif self.vin[9] in 'UZ0': - """ - The tenth position of the VIN represents the Model Year and - does not permit the use of the characters U and Z, as well - as the numeric zero (0) - """ - return False - - elif self.vin[8] not in 'X0123456789': - """ - The ninth position of the VIN is a calculated value based on - the other 16 alphanumeric values, it's called the - "Check Digit". The result of the check digit can ONLY be a - numeric 0-9 or letter "X". - """ - return False - - else: - return True - @property def less_than_500_built_per_year(self): """ @@ -96,6 +118,8 @@ def region(self): """ Returns the World Manufacturer's Region. Possible results: """ + if not self.vin[0] in WORLD_MANUFACTURER_MAP: + return None return WORLD_MANUFACTURER_MAP[self.vin[0]]['region'] @property @@ -132,16 +156,27 @@ def wmi(self): """ return self.vin[0:3] + @property + def manufacturer(self): + wmi = self.wmi + print self.wmi + if wmi[:3] in wmi_map.WMI_MAP: + return wmi_map.WMI_MAP[wmi[:3]] + if wmi[:2] in wmi_map.WMI_MAP: + return wmi_map.WMI_MAP[wmi[:2]] + return None + + make = manufacturer + @property def year(self): """ Returns the model year of the vehicle """ if self.is_pre_2010: - return YEARS_CODES_PRE_2010[self.vin[9]] + return YEARS_CODES_PRE_2010.get(self.vin[9], None) else: - print self.vin[9] - return YEARS_CODES_PRE_2040[self.vin[9]] + return YEARS_CODES_PRE_2040.get(self.vin[9], None) def decode(vin): diff --git a/libvin/make.csv b/libvin/make.csv new file mode 100644 index 0000000..f85ecd1 --- /dev/null +++ b/libvin/make.csv @@ -0,0 +1,469 @@ +10T,Oshkosh +137,"AM General, Hummer" +15G,Gillig +17N,John Deere +18X,WRV +19U,Acura +1A4,Chrysler +1A8,Chrysler +1AC,AMC +1AM,AMC +1B3,Dodge +1B4,Dodge +1B6,Dodge +1B7,Dodge +1B7,Dodge +1BA,Blue Bird +1BB,Blue Bird +1BD,Blue Bird +1C3,Chrysler +1C4,Chrysler +1C8,Chrysler +1C9,Chance +1CY,Crane Carrier +1D3,Dodge +1D4,Dodge +1D5,Dodge +1D7,Dodge +1D8,Dodge +1EC,Fleetwood +1F1,Ford +1F6,Ford +1FA,Ford +1FB,Ford +1FC,Ford +1FD,Ford +1FE,Ford +1FM,Ford +1FT,Ford +1FU,Freightliner +1FV,Freightliner +1G1,Chevrolet +1G2,Pontiac +1G3,Oldsmobile +1G4,Buick +1G5,"GMC, Pontiac" +1G6,Cadillac +1G8,"Chevrolet, Saturn" +1GA,Chevrolet +1GB,Chevrolet +1GC,Chevrolet +1GD,GMC +1GE,Cadillac +1GF,Flexible +1GG,Isuzu +1GH,"GMC, Oldsmobile" +1GJ,GMC +1GK,GMC +1GM,Pontiac +1GN,Chevrolet +1GT,GMC +1GY,Cadillac +1HG,Honda +1HS,International +1HT,International +1HV,International +1J4,Jeep +1J7,Jeep +1J8,Jeep +1JC,"AMC, Jeep" +1JD,AMC +1JT,"AMC, Jeep" +1L1,Lincoln +1LN,Lincoln +1M1,Mack +1M2,Mack +1M3,Mack +1M8,MCI +1ME,Mercury +1MR,Lincoln +1N4,Nissan +1N6,"Datsun, Nissan" +1N9,Neoplan +1NK,Kenworth +1NP,Peterbilt +1NX,Toyota +1P3,Plymouth +1P4,Plymouth +1P7,Plymouth +1P9,Panoz +1RF,Roadmaster +1S9,Saleen +177,Thomas +1T8,Thomas +1TU,TMC +1V1,Volkswagen +1VW,Volkswagen +1WA,Autostar +1WB,Autostar +1WU,White Volvo +1WV,Winnebago +1XK,Kenworth +1XM,AMC +1XP,Peterbilt +1Y1,"Chevrolet, Geo" +1YV,Mazda +1Z3,Mitsubishi +1Z5,Mitsubishi +1Z7,Mitsubishi +1ZV,Ford +1ZW,Mercury +2A3,Chrysler +2A4,Chrysler +2A8,Chrysler +2B1,Orion +2B3,Dodge +2B4,Dodge +2B5,Dodge +2B6,Dodge +2B7,Dodge +2B8,Dodge +2BC,"AMC, Jeep" +2C1,"Chevrolet, Geo" +2C3,Chrysler +2C4,Chrysler +2C7,Pontiac +2C8,Chrysler +2CC,"AMC, Eagle" +2CK,"Geo, Pontiac" +2CM,AMC +2CN,"Chevrolet, Geo" +2D4,Dodge +2D6,Dodge +2D7,Dodge +2D8,Dodge +2E3,Eagle +2FA,Ford +2FD,Ford +2FM,Ford +2FT,Ford +2FU,Freightliner +2FV,Freightliner +2FW,Sterling +2FZ,Sterling +2G0,GMC +2G1,Chevrolet +2G2,Pontiac +2G3,Oldsmobile +2G4,BuickX +2G5,GMC +2G7,Pontiac +2G8,Chevrolet +2GA,Chevrolet +2GB,Chevrolet +2GD,GMC +2GJ,GMC +2GK,GMC +2GN,Chevrolet +2GT,GMC +2HG,Honda +2HH,Acura +2HJ,Honda +2HK,Honda +2HM,Hyundai +2HN,Acura +2HS,International +2HT,International +2J4,Jeep +2LM,Lincoln +2M2,Mack +2ME,Mercury +2MH,Mercury +2MR,Mercury +2NK,Kenworth +2NP,Peterbilt +2P3,Plymouth +2P4,Plymouth +2P5,Plymouth +2P9,Prevost +2PC,Prevost +2S2,Suzuki +2S3,Suzuki +2T1,Toyota +2T2,Lexus +2WK,Western Star Trucks +2WL,Western Star Trucks +2XK,Kenworth +2XM,Eagle +2XP,Peterbilt +3A4,Chrysler +3A8,Chrysler +3AB,Dina +3AL,Freightliner +3B3,Dodge +3B4,Dodge +3B6,Dodge +3B7,Dodge +3BK,Kenworth +3BP,Peterbilt +3C3,Chrysler +3C4,Chrysler +3C8,Chrysler +3CA,Chrysler +3D3,Dodge +3D5,Dodge +3D6,Dodge +3D7,Dodge +3FA,Ford +3FC,Ford +3FD,Ford +3FE,"Ford, Freightliner" +3FR,Ford +3FT,Ford +3G1,Chevrolet +3G2,Pontiac +3G4,Buick +3G5,Buick +3G7,Pontiac +3GB,Chevrolet +3GC,Chevrolet +3GD,GMC +3GE,Chevrolet +3GK,GMC +3GN,Chevrolet +3GT,GMC +3GY,Cadillac +3HA,International +3HG,Honda +3HM,Honda +3HS,International +3HT,International +3LN,Lincoln +3MA,Mercury +3ME,Mercury +3N1,Nissan +3NK,Kenworth +3NM,Peterbilt +3P3,Plymouth +3TM,Toyota +3VW,Volkswagen +3WK,Kenworth +45V,Utilimaster +46G,Gillig +49H,Sterling +4A3,Mitsubishi +4A4,Mitsubishi +4B3,Dodge +4C3,Chrysler +4CD,Oshkosh +4DR,"Genesis, International" +4E3,Eagle +4F2,Mazda +4F4,Mazda +4G1,Chevrolet +4G2,Pontiac +4GD,GMC +4GT,"Isuzu, WhiteGMC" +4JG,Mercedes-Benz +4KB,Chevrolet +4KD,GMC +4KL,Isuzu +4M2,Mercury +4N1,Nissan +4N2,Nissan +4NU,Isuzu +4P3,Plymouth +4S1,Isuzu +4S2,Isuzu +4S3,Subaru +4S4,Subaru +4S6,Honda +4S7,Spartan +4SL,Magnum +4T1,Toyota +4T3,Toyota +4TA,Toyota +4US,BMW +4UZ,Freightliner +4V1,"Volvo, WhiteGMC" +4V2,"Volvo, WhiteGMC" +4V4,"Volvo, WhiteGMC" +4V5,"Volvo, WhiteGMC" +4VA,Volvo +4VG,"Volvo, WhiteGMC" +4VH,Volvo +4VL,Volvo +4VM,Volvo +4VZ,Spartan +5AS,GEM +5B4,Workhorse +5CK,Western Star Trucks +5FN,Honda +5FY,New Flyer +5GA,Buick +5GR,Hummer +5GT,Hummer +5GZ,Saturn +5J6,Honda +5J8,Acura +5KJ,Western Star Trucks +5KK,Western Star Trucks +5LM,Lincoln +5LT,Lincoln +5N1,Nissan +5N3,Infiniti +5NM,Hyundai +5NP,Hyundai +5PV,Hino +5S3,Saab +5SX,Amercian LeFrance +5T4,Workhorse +5TB,Toyota +5TD,Toyota +5TE,Toyota +5TF,Toyota +5UM,BMW +5UX,BMW +5Y2,Pontiac +6G2,Pontiac +6MM,Mitsubishi +6MP,Mercury +9BF,Ford +9BW,Volkswagen +9DW,Volkswagen +J81,"Chevrolet,Geo" +J87,Isuzu +J8B,Chevrolet +J8D,GMC +J8Z,Chevrolet +JA3,Mitsubishi +JA4,Mitsubishi +JA7,Mitsubishi +JAA,Isuzu +JAB,Isuzu +JAC,Isuzu +JAE,Acura +JAL,Isuzu +JB3,Dodge +JB4,Dodge +JB7,Dodge +JC2,Ford +JD1,Daihatsu +JD2,Daihatsu +JE3,Eagle +JF1,Subaru +JF2,Subaru +JF3,Subaru +JF4,Saab +JG1,"Chevrolet, Geo" +JG7,Pontiac +JGC,Geo +JH4,Acura +JHB,Hino +JHL,Honda +JHM,Honda +JJ3,Chrysler +JL6,Mitsubishi +JLS,Sterling +JM1,Mazda +JM2,Mazda +JM3,Mazda +JN1,"Datsun, Nissan" +JN3,Nissan +JN4,Nissan +JN6,"Datsun, Nissan" +JN8,Nissan +JNA,Nissan +JNK,Infiniti +JNR,Infiniti +JNX,Infiniti +JP3,Plymouth +JP4,Plymouth +JP7,Plymouth +JR2X,Isuzu +JS2,Suzuki +JS3,Suzuki +JS4,Suzuki +JT2,Toyota +JT3,Toyota +JT4,Toyota +JT5,Toyota +JT6,Lexus +JT8,Lexus +JTD,Toyota +JTE,Toyota +JTH,Lexus +JTJ,Lexus +JTK,Scion +JTL,Scion +JTM,Toyota +JTN,Toyota +JW6,Mitsubishi +JW7,Mitsubishi +KL1,Chevrolet +KL2,Pontiac +KL5,Suzuki +KL7,Asuna +KLA,Daewoo +KM8,Hyundai +KMF,Hyundai +KMH,Hyundai +KNA,Kia +KND,"Hyundai, Kia" +KNJ,Ford +KPH,Mitsubishi +LES,Isuzu +LM5,Isuzu +ML3,Dodge +SA9,Morgan +SAJ,Jaguar +SAL,Land Rover +SAT,Triumph +SAX,Sterling +SCA,Rolls-Royce +SCB,Bentley +SCC,Lotus +SCF,Aston Martin +SDL,TVR Engineering +SHH,Honda +SHS,Honda +SJN,Nissan UK +TRU,Audi +VF1,Renault +VF7,Citroën +VF3,Peugeot +VG6,Mack +VSS,Seat +W06,Cadillac +WA1,Audi +WAU,Audi +WBA,BMW +WBS,BMW +WBX,BMW +WD0,Dodge +WD1,Dodge +WD2,Dodge +WD5,Dodge +WD8,Dodge +WDB,"Maybach, Mercedes-Benz" +WDC,Mercedes-Benz +WDD,Mercedes-Benz +WDP,Dodge +WDX,Dodge +WDY,Dodge +WF1,Merkur +WKK,Fahrzeugwerke +WME,Mercedes-Benz +WMW,Mini +WP0,Porsche +WP1,Porsche +WUA,Audi +WV2,Volkswagen +WV3,Volkswagen +WVG,Volkswagen +WVW,Volkswagen +XTA,Lada +YB3,Volvo +YS3,Saab +YV1,Volvo +YV2,Volvo +YV4,Volvo +YV5,Volvo +ZA9,Lamborghini +ZAM,Maserati +ZAR,Alfa Romeo +ZC2,Chrysler +ZFA,Fiat +ZFF,Ferrari +ZHWX,Lamborghini +11V,Ottawa diff --git a/libvin/verification.py b/libvin/verification.py deleted file mode 100644 index 7e4291f..0000000 --- a/libvin/verification.py +++ /dev/null @@ -1,33 +0,0 @@ -""" -Copyright Lukasz Szybalski -VIN Vehicle information number checker, -Inputs vin number and outputs true/false -""" - -def is_valid(vin): - """Vehicle Information Number. This will return whether the entered vin number is authentic/correct. - Example: - import vin - vin.check_vin(my_vin_number) - """ - vin=str(vin).strip() - if len(vin) != 17: - return False - else: - converted=[] - vin=vin.upper() - for i in range(len(vin)): - converted.insert(i,convert_vin(vin[i])) - multiplier=[8,7,6,5,4,3,2,10,0,9,8,7,6,5,4,3,2] - add=0 - for i in range(len(vin)): - add+=(converted[i]*multiplier[i]) - final= (add%11) - if final ==10: - final='X' - if str(final)==vin[8]: - return True - else: - return False - - diff --git a/libvin/wmi_map.py b/libvin/wmi_map.py new file mode 100644 index 0000000..91b303c --- /dev/null +++ b/libvin/wmi_map.py @@ -0,0 +1,18 @@ +import csv +import os + +def load_make_sheet(pfile): + rv = {} + with open(pfile, 'rb') as f: + reader = csv.reader(f) + _ = reader.next() + + for row in reader: + vin_code, vin_model = row + rv[vin_code] = vin_model + + return rv + +WMI_MAP = load_make_sheet(os.path.join(os.path.abspath( + os.path.dirname(__file__)), 'make.csv')) + diff --git a/tests/__init__.py b/tests/__init__.py index 2b58a11..3735080 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -24,8 +24,4 @@ 'MODEL': 'Acadia', 'MAKE': 'GMC', 'YEAR': 2008, 'COUNTRY': 'United States', 'REGION': 'north_america', 'SEQUENTIAL_NUMBER': '123735', 'FEWER_THAN_500_PER_YEAR': False}, - # http://www.vindecoder.net/?vin=2FTCF15F2ECA55516&submit=Decode - {'VIN': '2FTCF15F2ECA55516', 'WMI': '2FT', 'VDS': 'CF15F2', 'VIS': 'ECA55516', - 'MODEL': 'F-150', 'MAKE': 'Ford', 'YEAR': 2014, 'COUNTRY': 'Canada', - 'REGION': 'north_america', 'SEQUENTIAL_NUMBER': '55516', 'FEWER_THAN_500_PER_YEAR': False}, ] diff --git a/tests/test_decoding.py b/tests/test_decoding.py index b495ce4..2ce899a 100644 --- a/tests/test_decoding.py +++ b/tests/test_decoding.py @@ -70,4 +70,10 @@ def test_is_valid(self): for test in TEST_DATA: v = Vin(test['VIN']) print "Testing: %s" % test['VIN'] - assert_equals(v.is_valid, True) + assert_equals(v.is_valid(), True) + + def test_manufacturer(self): + for test in TEST_DATA: + v = Vin(test['VIN']) + print 'Testing: %s' % test['VIN'] + assert_true(test['MAKE'] in v.make)