-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
194 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
from typing import List, Tuple | ||
|
||
from faker.providers.sbn.rules import RegistrantRule | ||
|
||
from .. import BaseProvider | ||
from .rules import RULES | ||
from .sbn import SBN, SBN9 | ||
|
||
|
||
class Provider(BaseProvider): | ||
"""Generates fake SBNs. These are the precursor to the ISBN and are | ||
largely similar to ISBN-10. | ||
See https://www.isbn-international.org/content/what-isbn for the | ||
format of ISBNs. SBNs have no EAN prefix or Registration Group. | ||
""" | ||
|
||
def _body(self) -> List[str]: | ||
"""Generate the information required to create an SBN""" | ||
|
||
reg_pub_len: int = SBN.MAX_LENGTH - 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 | ||
registrant, publication = self._registrant_publication(reg_pub, rules) | ||
return [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 sbn9(self, separator: str = "-") -> str: | ||
registrant, publication = self._body() | ||
sbn = SBN9(registrant, publication) | ||
return sbn.format(separator) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
from .. import Provider as SBNProvider | ||
|
||
|
||
class Provider(SBNProvider): | ||
pass |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
""" | ||
This module exists solely to figure how long a registrant/publication | ||
number may be within an SBN. It's the same as the ISBN implementation | ||
for ean 978, reg_group 0. | ||
""" | ||
|
||
from collections import namedtuple | ||
from typing import List | ||
|
||
RegistrantRule = namedtuple("RegistrantRule", ["min", "max", "registrant_length"]) | ||
|
||
# Structure: RULES = [Rule1, Rule2, ...] | ||
RULES: List[RegistrantRule] = [ | ||
RegistrantRule("0000000", "1999999", 2), | ||
RegistrantRule("2000000", "2279999", 3), | ||
RegistrantRule("2280000", "2289999", 4), | ||
RegistrantRule("2290000", "6479999", 3), | ||
RegistrantRule("6480000", "6489999", 7), | ||
RegistrantRule("6490000", "6999999", 3), | ||
RegistrantRule("7000000", "8499999", 4), | ||
RegistrantRule("8500000", "8999999", 5), | ||
RegistrantRule("9000000", "9499999", 6), | ||
RegistrantRule("9500000", "9999999", 7), | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
""" | ||
This module is responsible for generating the check digit and formatting | ||
SBN numbers. | ||
""" | ||
from typing import Any, Optional | ||
|
||
|
||
class SBN: | ||
MAX_LENGTH = 9 | ||
|
||
def __init__( | ||
self, | ||
registrant: Optional[str] = None, | ||
publication: Optional[str] = None, | ||
) -> None: | ||
self.registrant = registrant | ||
self.publication = publication | ||
|
||
|
||
class SBN9(SBN): | ||
def __init__(self, *args: Any, **kwargs: Any) -> None: | ||
super().__init__(*args, **kwargs) | ||
self.check_digit = self._check_digit() | ||
|
||
def _check_digit(self) -> str: | ||
"""Calculate the check digit for SBN-9. | ||
SBNs use the same check digit calculation as ISBN. See | ||
https://en.wikipedia.org/wiki/International_Standard_Book_Number | ||
for calculation. Only modification is weights range from 1 to 9 | ||
instead of 1 to 10. | ||
""" | ||
weights = range(1, 9) | ||
body = "".join([part for part in [self.registrant, self.publication] if part is not None]) | ||
remainder = sum(int(b) * w for b, w in zip(body, weights)) % 11 | ||
check_digit = "X" if remainder == 10 else str(remainder) | ||
return str(check_digit) | ||
|
||
def format(self, separator: str = "") -> str: | ||
return separator.join( | ||
[ | ||
part | ||
for part in [ | ||
self.registrant, | ||
self.publication, | ||
self.check_digit, | ||
] | ||
if part is not None | ||
] | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import pytest | ||
|
||
from faker.providers.sbn import SBN9 | ||
from faker.providers.sbn.en_US import Provider as SBNProvider | ||
from faker.providers.sbn.rules import RegistrantRule | ||
|
||
|
||
class TestISBN9: | ||
def test_check_digit_is_correct(self): | ||
sbn = SBN9(registrant="340", publication="01381") | ||
assert sbn.check_digit == "X" | ||
sbn = SBN9(registrant="06", publication="230125") | ||
assert sbn.check_digit == "2" | ||
sbn = SBN9(registrant="10103", publication="202") | ||
assert sbn.check_digit == "3" | ||
|
||
def test_format_length(self): | ||
sbn = SBN9(registrant="4516", publication="7331") | ||
assert len(sbn.format()) == 9 | ||
sbn = SBN9(registrant="451", publication="10036") | ||
assert len(sbn.format()) == 9 | ||
|
||
|
||
class TestProvider: | ||
prov = SBNProvider(None) | ||
|
||
def test_reg_pub_separation(self): | ||
r1 = RegistrantRule("0000000", "0000001", 1) | ||
r2 = RegistrantRule("0000002", "0000003", 2) | ||
assert self.prov._registrant_publication("00000000", [r1, r2]) == ( | ||
"0", | ||
"0000000", | ||
) | ||
assert self.prov._registrant_publication("00000010", [r1, r2]) == ( | ||
"0", | ||
"0000010", | ||
) | ||
assert self.prov._registrant_publication("00000019", [r1, r2]) == ( | ||
"0", | ||
"0000019", | ||
) | ||
assert self.prov._registrant_publication("00000020", [r1, r2]) == ( | ||
"00", | ||
"000020", | ||
) | ||
assert self.prov._registrant_publication("00000030", [r1, r2]) == ( | ||
"00", | ||
"000030", | ||
) | ||
assert self.prov._registrant_publication("00000031", [r1, r2]) == ( | ||
"00", | ||
"000031", | ||
) | ||
assert self.prov._registrant_publication("00000039", [r1, r2]) == ( | ||
"00", | ||
"000039", | ||
) | ||
|
||
def test_rule_not_found(self): | ||
with pytest.raises(Exception): | ||
r = RegistrantRule("0000000", "0000001", 1) | ||
self.prov._registrant_publication("0000002", [r]) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters