from typing import List, Tuple from faker.providers.isbn.rules import RegistrantRule from .. import BaseProvider from .isbn import ISBN, ISBN10, ISBN13 from .rules import RULES class Provider(BaseProvider): """Generates fake ISBNs. ISBN rules vary across languages/regions so this class makes no attempt at replicating all of the rules. It only replicates the 978 EAN prefix for the English registration groups, meaning the first 4 digits of the ISBN-13 will either be 978-0 or 978-1. Since we are only replicating 978 prefixes, every ISBN-13 will have a direct mapping to an ISBN-10. See https://www.isbn-international.org/content/what-isbn for the format of ISBNs. See https://www.isbn-international.org/range_file_generation for the list of rules pertaining to each prefix/registration group. """ def _body(self) -> List[str]: """Generate the information required to create an ISBN-10 or ISBN-13. """ ean: str = self.random_element(RULES.keys()) reg_group: str = self.random_element(RULES[ean].keys()) # Given the chosen ean/group, decide how long the # registrant/publication string may be. # We must allocate for the calculated check digit, so # subtract 1 reg_pub_len: int = ISBN.MAX_LENGTH - len(ean) - len(reg_group) - 1 # Generate a registrant/publication combination reg_pub: str = self.numerify("#" * reg_pub_len) # Use rules to separate the registrant from the publication rules: List[RegistrantRule] = RULES[ean][reg_group] registrant, publication = self._registrant_publication(reg_pub, rules) return [ean, reg_group, registrant, publication] @staticmethod def _registrant_publication(reg_pub: str, rules: List[RegistrantRule]) -> Tuple[str, str]: """Separate the registration from the publication in a given string. :param reg_pub: A string of digits representing a registration and publication. :param rules: A list of RegistrantRules which designate where to separate the values in the string. :returns: A (registrant, publication) tuple of strings. """ for rule in rules: if rule.min <= reg_pub[:-1] <= rule.max: reg_len = rule.registrant_length break else: raise Exception("Registrant/Publication not found in registrant " "rule list.") registrant, publication = reg_pub[:reg_len], reg_pub[reg_len:] return registrant, publication def isbn13(self, separator: str = "-") -> str: ean, group, registrant, publication = self._body() isbn = ISBN13(ean, group, registrant, publication) return isbn.format(separator) def isbn10(self, separator: str = "-") -> str: ean, group, registrant, publication = self._body() isbn = ISBN10(ean, group, registrant, publication) return isbn.format(separator)