Well, there appears to be a lot more to ISBNs than I originally thought! This is a great example of how curiosity can be a good catalyst for practice at times. As I mentioned in the last post, I found out that there is a way to convert the “old” ISBN-10 codes to the newer ISBN-13 format. What would our ISBN exercise be like if we couldn’t upgrade an old code to a new one?
In simple terms, the process to convert from ISBN-10 to ISBN-13 is as follows:
- Remove the “check digit” (tenth digit) from the ISBN-10 number.
- Prepend “978” to the ISBN-10 sans check digit. This should result in a 12 digit number.
- Calculate a new ISBN-13 check digit such that the weighted sum of the ISBN-13 numbers is an even multiple of 10. Remember the previous section where we outlined this process.
- Add the new check digit to the previous 12 digit number, resulting in the ISBN-13 number!
The only part of this process that is complex is the calculation of the new check digit. For this reason, I decided to create a separate function for this purpose. It’s worth noting, that I can also use this function to create an alternative validation function where I validate the ISBN-13 by calculating the check digit and checking it against the 13th digit in the ISBN-13 code. Remember that currently, I am including the 13th digit in the sum and then checking that the sum of all 13 digits is an even multiple of 10. Either will work. Regardless, here is my solution for calculating the check digit of the first 12 digits of an ISBN-13 number. Also note that, unlike the validation functions, because I am returning a value from the function, I raise an exception if the format of the input is invalid or if the ISBN-10 code itself is invalid. This is a design decision. If I wanted to “fail soft” I could also have returned None
from the function, however I decided that if someone passes in a bad ISBN, I want to force them to handle the exception in code, vs. potentially failing silently.
So, here is my solution (in two functions) to the conversion problem. Also notice that I’ve created a new exception class to indicate that there is a problem with the formatting of the input.
class ISBNValidator: class FormatException(Exception): pass ... @staticmethod def calculate_isbn_13_checkdigit(isbn13_first12_numbers: str) -> str: if len(isbn13_first12_numbers) != 12 or not isbn13_first12_numbers.isnumeric(): raise ISBNValidator.FormatException("Improper format in first 12 numbers of ISBN13") checksum = 0 for (count, digit) in enumerate(isbn13_first12_numbers): weight = 1 + ((count % 2) * 2) checksum += (int(digit) * weight) checkdigit = (10 - (checksum % 10)) % 10 return str(checkdigit) @staticmethod def convert_isbn_10_to_13(isbn10_code_string: str) -> str: if not ISBNValidator.validate_isbn10(isbn10_code_string): raise ISBNValidator.FormatException(f"{isbn10_code_string} is not a valid ISBN-10 code string.") # Remove the dashes and spaces from the code string and drop the check digit new_isbn = ISBNValidator.prepare_code_string(isbn10_code_string)[0:9] new_isbn = "978" + new_isbn check_digit = ISBNValidator.calculate_isbn_13_checkdigit(new_isbn) new_isbn += check_digit return new_isbn
Of course, our exercise would not be complete without unit test code, so here is what I came up with to test this newly developed function:
class TestISBNValidator(TestCase): ... test_data_convert_10_to_13 = { "0201882957": "9780201882957", "1420951300": "9781420951301", "0452284236": "9780452284234", "1292101768": "9781292101767", "0345391802": "9780345391803" } ... def test_convert_isbn_10_to_13(self): for (isbn_10, isbn_13) in TestISBNValidator.test_data_convert_10_to_13.items(): result = ISBNValidator.convert_isbn_10_to_13(isbn_10) self.assertEqual(isbn_13, result)
You may notice at this point that I went out of my way above to explain that I had created a new exception type (FormatException
) to indicate when I thought there was a formatting error in the input, but my test code does not exercise that logic. I think it might be a good thing for tomorrow to talk about code coverage as well as how to write unit tests that check for exceptional conditions and exception handling in functions or methods. One thing leads to another!
- Practicing Python: Quick ISBN-10 Validation
- Basic ISBN-10 Validation in Python: Part 2
- ISBN Validation: Adding Simple Python Unit Tests
- Reliable ISBN-13 Validation
- ISBN Validator: Making it General Purpose
- Simple ISBN-10 to ISBN-13 Conversion
- Testing Exceptions in Python with unittest
- Using Simple Code Coverage in Python