Basic ISBN-10 Validation in Python: Part 2

In my previous post Practicing Python, I started work on this exercise at Codementor. I got a decent solution working, but it still had a few deficiencies. Namely:

  • It did not account for dashes in the ISBN
  • Some of the error handling was not complete (namely making sure that the last digit was either numeric or an “x” character)
  • The test code was also incomplete (did not test the last digit as “x” or a ISBN with dashes)

Because of this, I decided to spend a little bit of my practice time enhancing the code. I also combined all of my methods/functions into a class because in the future, I’d like to maybe build an application that can display and run my practice exercises. I considered splitting things into modules, but I preferred the cleaner file structure of a class as well as the opportunity to use some polymorphism later on to “process” these exercises.

I like to do test driven development, so the first thing I did was to compose and add some tests that would exercise these scenarios:

        isbn = "155404295X"
        assert ISBNExercise.validate_isbn10(isbn) is True
        isbn = "1-55404-295-X"
        assert ISBNExercise.validate_isbn10(isbn) is True
        isbn = "1-55404-294-X"
        assert ISBNExercise.validate_isbn10(isbn) is False

The next thing I did was create a method to remove the dashes from the ISBN string. This is a nice “one liner” in Python using the str.replace method:

    def remove_dashes(code_string: str) -> str:
        return code_string.replace("-", "")

Then, I updated my main method to use this new remove_dashes helper method as well as adding in the additional error handling:

    def validate_isbn10(code_string: str) -> bool:
        # Remove dashes from the string
        isbn_string = ISBNExercise.remove_dashes(code_string)

        # Reject if string is wrong length, or is not numeric (including special case for digit 10)
        if len(isbn_string) != 10 or \
                not isbn_string[0:9].isnumeric() or \
                not (isbn_string[9].isnumeric() or isbn_string[9].lower() == "x"):
            return False
        checksum = 0
        for i in range(0, 10):
            if i == 9 and isbn_string[i].lower() == "x":
                digit = 10
            else:
                digit = int(isbn_string[i])
            checksum += digit * (10 - i)
        return (checksum % 11) == 0

Now that I finished this, all of my tests are passing and I feel like my ISBN processor is much more robust. I think that tomorrow I will convert the test function into something that uses the Python unit test infrastructure instead.

If you’re interested, here is my complete class as it exists now:

class ISBNExercise:

    @staticmethod
    def remove_dashes(code_string: str) -> str:
        return code_string.replace("-", "")

    @staticmethod
    def validate_isbn10(code_string: str) -> bool:
        # Remove dashes from the string
        isbn_string = ISBNExercise.remove_dashes(code_string)

        # Reject if string is wrong length, or is not numeric (including special case for digit 10)
        if len(isbn_string) != 10 or \
                not isbn_string[0:9].isnumeric() or \
                not (isbn_string[9].isnumeric() or isbn_string[9].lower() == "x"):
            return False
        checksum = 0
        for i in range(0, 10):
            if i == 9 and isbn_string[i].lower() == "x":
                digit = 10
            else:
                digit = int(isbn_string[i])
            checksum += digit * (10 - i)
        return (checksum % 11) == 0

    @staticmethod
    def test_isbns():
        isbn = "123"
        assert ISBNExercise.validate_isbn10(isbn) is False
        isbn = "0136091814"
        assert ISBNExercise.validate_isbn10(isbn) is True
        isbn = "1616550416"
        assert ISBNExercise.validate_isbn10(isbn) is False
        isbn = "0553418025"
        assert ISBNExercise.validate_isbn10(isbn) is True
        isbn = "3859574859"
        assert ISBNExercise.validate_isbn10(isbn) is False
        isbn = "155404295X"
        assert ISBNExercise.validate_isbn10(isbn) is True
        isbn = "1-55404-295-X"
        assert ISBNExercise.validate_isbn10(isbn) is True
        isbn = "1-55404-294-X"
        assert ISBNExercise.validate_isbn10(isbn) is False

    def run_exercise(self):
        self.test_isbns()
        print("ISBN Exercise - Complete!")


if __name__ == '__main__':
    ex = ISBNExercise()
    ex.run_exercise()
  1. Practicing Python: Quick ISBN-10 Validation
  2. Basic ISBN-10 Validation in Python: Part 2
  3. ISBN Validation: Adding Simple Python Unit Tests
  4. Reliable ISBN-13 Validation
  5. ISBN Validator: Making it General Purpose
  6. Simple ISBN-10 to ISBN-13 Conversion
  7. Testing Exceptions in Python with unittest
  8. Using Simple Code Coverage in Python

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top