Merge pull request #2 from Babibubebon/develop

Release 1.1.0
This commit is contained in:
Babibubebon 2023-04-08 04:19:03 +09:00 committed by GitHub
commit 3e8fcdb089
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 333 additions and 206 deletions

View file

@ -1,6 +1,10 @@
name: Test
on: [ push, pull_request ]
on:
push:
branches:
- develop
pull_request:
jobs:
test:

View file

@ -34,6 +34,59 @@ isdn.jp が提供している書誌情報を取得 ([Web からの情報取得](
ISDN(code='2784702901978', prefix='278', group='4', registrant='702901', publication='97', check_digit='8')
>>> record.product_name
'みほん同人誌'
>>> record
ISDNRecord(
isdn=ISDN(
code='2784702901978',
prefix='278',
group='4',
registrant='702901',
publication='97',
check_digit='8'
),
region='日本',
class_='オリジナル',
type='同人誌',
rating_gender='区別なし',
rating_age='一般',
product_name='みほん同人誌',
product_yomi='みほんどうじんし',
publisher_code='14142139',
publisher_name='見本サークル',
publisher_yomi='みほんさーくる',
issue_date=datetime.date(2008, 3, 12),
genre_code='106',
genre_name='評論・情報',
genre_user=None,
c_code='C3055',
author='専門',
shape='単行本',
contents='電子通信',
price=Decimal('100'),
price_unit='JPY',
barcode2='2923055001007',
product_comment=None,
product_style=None,
product_size=None,
product_capacity=None,
product_capacity_unit=None,
sample_image_uri=HttpUrl('https://isdn.jp/images/thumbs/2784702901978.png', ),
useroptions=[
UserOption(property='執筆者', value='みほん執筆者1'),
UserOption(property='執筆者', value='みほん執筆者2'),
UserOption(property='執筆者', value='みほん執筆者3'),
UserOption(property='執筆者', value='みほん執筆者4'),
UserOption(property='執筆者', value='みほん執筆者5'),
UserOption(property='執筆者', value='みほん執筆者6')
],
external_links=[
ExternalLink(title='国際標準同人誌番号', uri=HttpUrl('http://isdn.jp/', )),
ExternalLink(
title='mixiコミュニティ',
uri=HttpUrl('http://mixi.jp/view_community.pl?id=3188828', )
)
]
)
```
## CLI
@ -61,4 +114,5 @@ $ isdn list
```
$ isdn bulk-download /path/to/download
$ isdn bulk-download /path/to/download --write-image
```

View file

@ -1,150 +1,11 @@
import importlib.metadata
import re
from dataclasses import asdict, dataclass, field
from datetime import date
__version__ = importlib.metadata.version("isdn")
class IsdnError(Exception):
class InvalidIsdnError(ValueError):
pass
class InvalidIsdnError(IsdnError):
pass
@dataclass
class ISDN:
code: str
prefix: str | None = None
group: str | None = None
registrant: str | None = None
publication: str | None = None
check_digit: str | None = None
@property
def parts(self) -> list[str | None]:
self.code = self.normalize(self.code)
return [self.prefix, self.group, self.registrant, self.publication, self.check_digit]
def __post_init__(self):
if all(self.parts) and self.code != "".join(self.parts):
raise ValueError(f"ISDNs of arguments do not match: {self.code} != {''.join(self.parts)}")
def to_disp_isdn(self) -> str | None:
if not all(self.parts):
return None
return f"ISDN{self.prefix}-{self.group}-{self.registrant}-{self.publication}-{self.check_digit}"
def validate(self, raise_error: bool = False) -> bool:
if not re.fullmatch(r"\d+", self.code):
if raise_error:
raise InvalidIsdnError("Contains non-numeric characters")
else:
return False
if len(self.code) != 13:
if raise_error:
raise InvalidIsdnError("ISDN must be 13 digits")
else:
return False
if not (self.code.startswith("278") or self.code.startswith("279")):
if raise_error:
raise InvalidIsdnError("ISDN prefix must be 278 or 279")
else:
return False
if self.calc_check_digit(self.code) != self.code[12]:
if raise_error:
raise InvalidIsdnError("Invalid check digit")
else:
return False
# Validate parts
if self.group and not (1 <= len(self.group) <= 5):
if raise_error:
raise InvalidIsdnError("Group part must be 1 to 5 digits")
else:
return False
if self.registrant and not (1 <= len(self.registrant) <= 7):
if raise_error:
raise InvalidIsdnError("Publisher part must be 1 to 7 digits")
else:
return False
if self.publication and not (1 <= len(self.publication) <= 2):
if raise_error:
raise InvalidIsdnError("Publication part must be 1 to 2 digits")
else:
return False
return True
@staticmethod
def normalize(isdn: int | str) -> str:
return str(isdn).replace("-", "").strip()
@staticmethod
def calc_check_digit(isdn: str) -> str:
isdn = [int(n) for n in isdn]
cd = 10 - (sum([(n if i % 2 == 0 else n * 3) for i, n in enumerate(isdn[:12])]) % 10)
return str(cd % 10)
@classmethod
def from_disp_isdn(cls, disp_isdn: str) -> "ISDN":
parts = disp_isdn.lstrip("ISDN").split("-")
isdn = "".join(parts)
if len(parts) != 5:
raise InvalidIsdnError("ISDN must have 5 parts")
return cls(isdn, *parts)
@dataclass
class UserOption:
property: str
value: str
@dataclass
class ExternalLink:
uri: str
title: str | None
@dataclass
class ISDNRecord:
isdn: ISDN
disp_isdn: str
region: str
class_: str
type: str
rating_gender: str
rating_age: str
product_name: str
product_yomi: str | None
publisher_code: str
publisher_name: str
publisher_yomi: str | None
issue_date: date
genre_code: str | None
genre_name: str | None
genre_user: str | None
c_code: str | None
author: str | None
shape: str | None
contents: str | None
price: int | None
price_unit: str | None
barcode2: str | None
product_comment: str | None
product_style: str | None
product_size: str | None
product_capacity: int | None
product_capacity_unit: str | None
sample_image_uri: str
useroption: list[UserOption] = field(default_factory=list)
external_link: list[ExternalLink] = field(default_factory=list)
def to_dict(self) -> dict:
return asdict(self)
from .client import ISDNClient
from .model import ISDN, ISDNRecord

View file

@ -2,8 +2,9 @@ from typing import Iterator
import requests
from . import ISDNRecord, __version__
from .parser import ISDNJpXMLParser
from . import __version__
from .model import ISDNRecord, ISDNRoot
from .parser import ISDNJpSitemapXMLParser
ISDN_XML_ENDPOINT = "https://isdn.jp/xml/{isdn}"
ISDN_IMAGE_ENDPOINT = "https://isdn.jp/images/thumbs/{isdn}.png"
@ -37,7 +38,7 @@ class ISDNClient:
def get(self, isdn: str) -> ISDNRecord:
r = self._get(isdn, self.xml_endpoint_url)
return ISDNJpXMLParser.parse_record(r.content)
return ISDNRoot.from_xml_first(r.content)
def get_raw(self, isdn: str) -> bytes:
r = self._get(isdn, self.xml_endpoint_url)
@ -54,4 +55,4 @@ class ISDNClient:
def list(self) -> Iterator[str]:
r = self._list()
return ISDNJpXMLParser.parse_list(r.raw)
return ISDNJpSitemapXMLParser.parse_list(r.raw)

View file

@ -1,13 +1,12 @@
import json
import os
import time
import click
from requests.exceptions import HTTPError
from . import ISDN, __version__
from . import __version__
from .client import ISDNClient
from .parser import ISDNJpXMLParser
from .model import ISDN, ISDNRoot
@click.group()
@ -29,9 +28,9 @@ def get_isdn(isdn: str, format: str):
case "xml":
res = c.get_raw(isdn)
case "dict":
res = c.get(isdn).to_dict()
res = c.get(isdn).dict()
case "json":
res = json.dumps(c.get(isdn).to_dict(), ensure_ascii=False)
res = c.get(isdn).json(ensure_ascii=False)
case _:
raise NotImplementedError
click.echo(res)
@ -72,7 +71,7 @@ def bulk_download(
out.write(res)
if write_image:
record = ISDNJpXMLParser.parse_record(res)
record = ISDNRoot.from_xml_first(res)
if record.sample_image_uri:
img = c.get_image(isdn)
with open(image_path, "wb") as out:

166
isdn/model.py Normal file
View file

@ -0,0 +1,166 @@
import re
from datetime import date
from decimal import Decimal
from pydantic import Field, HttpUrl, root_validator, validator
from pydantic.dataclasses import dataclass
from pydantic_xml import BaseXmlModel, element
from . import InvalidIsdnError
NSMAP = {"": "https://isdn.jp/schemas/0.1"}
@dataclass
class ISDN:
code: str
prefix: str | None = None
group: str | None = None
registrant: str | None = None
publication: str | None = None
check_digit: str | None = None
@root_validator(pre=True)
def validate_code(cls, values):
code = str(values["code"])
if code.startswith("ISDN") and "-" in code:
parts = code.lstrip("ISDN").split("-")
code = "".join(parts)
if len(parts) != 5:
raise InvalidIsdnError("ISDN must have 5 parts")
values.update(
{
"prefix": parts[0],
"group": parts[1],
"registrant": parts[2],
"publication": parts[3],
"check_digit": parts[4],
}
)
else:
code = cls.normalize(code)
arg_parts = [values.get(k) for k in ["prefix", "group", "registrant", "publication", "check_digit"]]
if all(arg_parts) and code != "".join(arg_parts):
raise ValueError(f"ISDNs of arguments do not match: {code} != {''.join(arg_parts)}")
return values | {"code": code}
@property
def parts(self) -> list[str | None]:
self.code = self.normalize(self.code)
return [self.prefix, self.group, self.registrant, self.publication, self.check_digit]
@staticmethod
def normalize(isdn: int | str) -> str:
return str(isdn).replace("-", "").strip()
@staticmethod
def calc_check_digit(isdn: str) -> str:
isdn = [int(n) for n in isdn]
cd = 10 - (sum([(n if i % 2 == 0 else n * 3) for i, n in enumerate(isdn[:12])]) % 10)
return str(cd % 10)
def to_disp_isdn(self) -> str | None:
if not all(self.parts):
return None
return f"ISDN{self.prefix}-{self.group}-{self.registrant}-{self.publication}-{self.check_digit}"
def validate(self, raise_error: bool = False) -> bool:
if not re.fullmatch(r"\d+", self.code):
if raise_error:
raise InvalidIsdnError("Contains non-numeric characters")
else:
return False
if len(self.code) != 13:
if raise_error:
raise InvalidIsdnError("ISDN must be 13 digits")
else:
return False
if not (self.code.startswith("278") or self.code.startswith("279")):
if raise_error:
raise InvalidIsdnError("ISDN prefix must be 278 or 279")
else:
return False
if self.calc_check_digit(self.code) != self.code[12]:
if raise_error:
raise InvalidIsdnError("Invalid check digit")
else:
return False
# Validate parts
if self.group and not (1 <= len(self.group) <= 5):
if raise_error:
raise InvalidIsdnError("Group part must be 1 to 5 digits")
else:
return False
if self.registrant and not (1 <= len(self.registrant) <= 7):
if raise_error:
raise InvalidIsdnError("Publisher part must be 1 to 7 digits")
else:
return False
if self.publication and not (1 <= len(self.publication) <= 2):
if raise_error:
raise InvalidIsdnError("Publication part must be 1 to 2 digits")
else:
return False
return True
class UserOption(BaseXmlModel, tag="useroption", nsmap=NSMAP):
property: str = element(tag="property", default="")
value: str = element(tag="value", default="")
class ExternalLink(BaseXmlModel, tag="external-link", nsmap=NSMAP):
title: str | None = element(tag="title")
uri: HttpUrl = element(tag="uri")
class ISDNRecord(BaseXmlModel, nsmap=NSMAP):
isdn: ISDN = element(tag="disp-isdn")
region: str = element(tag="region")
class_: str = element(tag="class")
type: str = element(tag="type")
rating_gender: str = element(tag="rating_gender")
rating_age: str = element(tag="rating_age")
product_name: str = element(tag="product-name")
product_yomi: str | None = element(tag="product-yomi")
publisher_code: str = element(tag="publisher-code")
publisher_name: str = element(tag="publisher-name")
publisher_yomi: str | None = element(tag="publisher-yomi")
issue_date: date = element(tag="issue-date")
genre_code: str | None = element(tag="genre-code")
genre_name: str | None = element(tag="genre-name")
genre_user: str | None = element(tag="genre-user")
c_code: str | None = element(tag="c-code")
author: str | None = element(tag="author")
shape: str | None = element(tag="shape")
contents: str | None = element(tag="contents")
price: Decimal | None = element(tag="price")
price_unit: str | None = element(tag="price-unit")
barcode2: str | None = element(tag="barcode2")
product_comment: str | None = element(tag="product-comment")
product_style: str | None = element(tag="product-style")
product_size: str | None = element(tag="product-size")
product_capacity: Decimal | None = element(tag="product-capacity")
product_capacity_unit: str | None = element(tag="product-capacity-unit")
sample_image_uri: HttpUrl | None = element(tag="sample-image-uri")
useroptions: list[UserOption] = Field(default_factory=list)
external_links: list[ExternalLink] = Field(default_factory=list)
@validator("isdn", pre=True)
def parse_disp_isdn(cls, isdn: str) -> ISDN:
return ISDN(code=isdn)
class ISDNRoot(BaseXmlModel, tag="isdn", nsmap=NSMAP):
records: list[ISDNRecord] = element(tag="item")
@classmethod
def from_xml_first(cls, source: str | bytes) -> ISDNRecord:
isdn_root = cls.from_xml(source)
return isdn_root.records[0]

View file

@ -3,51 +3,10 @@ from typing import IO, Iterator
from lxml import etree
from . import ISDN, ExternalLink, ISDNRecord, UserOption
namespaces = {"isdn": "https://isdn.jp/schemas/0.1", "sitemap": "http://www.sitemaps.org/schemas/sitemap/0.9"}
namespaces = {"sitemap": "http://www.sitemaps.org/schemas/sitemap/0.9"}
class ISDNJpXMLParser:
@staticmethod
def convert_tag_name(tag: str) -> str:
key = etree.QName(tag).localname.replace("-", "_")
if key == "class":
key += "_"
return key
@staticmethod
def parse_value(elem: etree.Element, key: str) -> str | UserOption | ExternalLink:
data = dict()
for child in elem:
data[ISDNJpXMLParser.convert_tag_name(child.tag)] = child.text
match key:
case "useroption":
return UserOption(**data)
case "external_link":
return ExternalLink(**data)
case _:
return elem.text
@staticmethod
def parse_record(doc: str | bytes) -> ISDNRecord:
root = etree.fromstring(doc)
item = root.find("./isdn:item", namespaces=namespaces)
data = dict()
for child in item:
key = ISDNJpXMLParser.convert_tag_name(child.tag)
value = ISDNJpXMLParser.parse_value(child, key)
if key in {"useroption", "external_link"}:
if key not in data:
data[key] = []
data[key].append(value)
else:
data[key] = value
return ISDNRecord(isdn=ISDN.from_disp_isdn(data["disp_isdn"]), **data)
class ISDNJpSitemapXMLParser:
@staticmethod
def parse_list(file: str | IO) -> Iterator[str]:
for event, elm in etree.iterparse(

87
poetry.lock generated
View file

@ -477,6 +477,79 @@ files = [
dev = ["pre-commit", "tox"]
testing = ["pytest", "pytest-benchmark"]
[[package]]
name = "pydantic"
version = "1.10.7"
description = "Data validation and settings management using python type hints"
category = "main"
optional = false
python-versions = ">=3.7"
files = [
{file = "pydantic-1.10.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e79e999e539872e903767c417c897e729e015872040e56b96e67968c3b918b2d"},
{file = "pydantic-1.10.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:01aea3a42c13f2602b7ecbbea484a98169fb568ebd9e247593ea05f01b884b2e"},
{file = "pydantic-1.10.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:516f1ed9bc2406a0467dd777afc636c7091d71f214d5e413d64fef45174cfc7a"},
{file = "pydantic-1.10.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae150a63564929c675d7f2303008d88426a0add46efd76c3fc797cd71cb1b46f"},
{file = "pydantic-1.10.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ecbbc51391248116c0a055899e6c3e7ffbb11fb5e2a4cd6f2d0b93272118a209"},
{file = "pydantic-1.10.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f4a2b50e2b03d5776e7f21af73e2070e1b5c0d0df255a827e7c632962f8315af"},
{file = "pydantic-1.10.7-cp310-cp310-win_amd64.whl", hash = "sha256:a7cd2251439988b413cb0a985c4ed82b6c6aac382dbaff53ae03c4b23a70e80a"},
{file = "pydantic-1.10.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:68792151e174a4aa9e9fc1b4e653e65a354a2fa0fed169f7b3d09902ad2cb6f1"},
{file = "pydantic-1.10.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dfe2507b8ef209da71b6fb5f4e597b50c5a34b78d7e857c4f8f3115effaef5fe"},
{file = "pydantic-1.10.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10a86d8c8db68086f1e30a530f7d5f83eb0685e632e411dbbcf2d5c0150e8dcd"},
{file = "pydantic-1.10.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d75ae19d2a3dbb146b6f324031c24f8a3f52ff5d6a9f22f0683694b3afcb16fb"},
{file = "pydantic-1.10.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:464855a7ff7f2cc2cf537ecc421291b9132aa9c79aef44e917ad711b4a93163b"},
{file = "pydantic-1.10.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:193924c563fae6ddcb71d3f06fa153866423ac1b793a47936656e806b64e24ca"},
{file = "pydantic-1.10.7-cp311-cp311-win_amd64.whl", hash = "sha256:b4a849d10f211389502059c33332e91327bc154acc1845f375a99eca3afa802d"},
{file = "pydantic-1.10.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cc1dde4e50a5fc1336ee0581c1612215bc64ed6d28d2c7c6f25d2fe3e7c3e918"},
{file = "pydantic-1.10.7-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0cfe895a504c060e5d36b287ee696e2fdad02d89e0d895f83037245218a87fe"},
{file = "pydantic-1.10.7-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:670bb4683ad1e48b0ecb06f0cfe2178dcf74ff27921cdf1606e527d2617a81ee"},
{file = "pydantic-1.10.7-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:950ce33857841f9a337ce07ddf46bc84e1c4946d2a3bba18f8280297157a3fd1"},
{file = "pydantic-1.10.7-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c15582f9055fbc1bfe50266a19771bbbef33dd28c45e78afbe1996fd70966c2a"},
{file = "pydantic-1.10.7-cp37-cp37m-win_amd64.whl", hash = "sha256:82dffb306dd20bd5268fd6379bc4bfe75242a9c2b79fec58e1041fbbdb1f7914"},
{file = "pydantic-1.10.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8c7f51861d73e8b9ddcb9916ae7ac39fb52761d9ea0df41128e81e2ba42886cd"},
{file = "pydantic-1.10.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6434b49c0b03a51021ade5c4daa7d70c98f7a79e95b551201fff682fc1661245"},
{file = "pydantic-1.10.7-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64d34ab766fa056df49013bb6e79921a0265204c071984e75a09cbceacbbdd5d"},
{file = "pydantic-1.10.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:701daea9ffe9d26f97b52f1d157e0d4121644f0fcf80b443248434958fd03dc3"},
{file = "pydantic-1.10.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:cf135c46099ff3f919d2150a948ce94b9ce545598ef2c6c7bf55dca98a304b52"},
{file = "pydantic-1.10.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b0f85904f73161817b80781cc150f8b906d521fa11e3cdabae19a581c3606209"},
{file = "pydantic-1.10.7-cp38-cp38-win_amd64.whl", hash = "sha256:9f6f0fd68d73257ad6685419478c5aece46432f4bdd8d32c7345f1986496171e"},
{file = "pydantic-1.10.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c230c0d8a322276d6e7b88c3f7ce885f9ed16e0910354510e0bae84d54991143"},
{file = "pydantic-1.10.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:976cae77ba6a49d80f461fd8bba183ff7ba79f44aa5cfa82f1346b5626542f8e"},
{file = "pydantic-1.10.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d45fc99d64af9aaf7e308054a0067fdcd87ffe974f2442312372dfa66e1001d"},
{file = "pydantic-1.10.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d2a5ebb48958754d386195fe9e9c5106f11275867051bf017a8059410e9abf1f"},
{file = "pydantic-1.10.7-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:abfb7d4a7cd5cc4e1d1887c43503a7c5dd608eadf8bc615413fc498d3e4645cd"},
{file = "pydantic-1.10.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:80b1fab4deb08a8292d15e43a6edccdffa5377a36a4597bb545b93e79c5ff0a5"},
{file = "pydantic-1.10.7-cp39-cp39-win_amd64.whl", hash = "sha256:d71e69699498b020ea198468e2480a2f1e7433e32a3a99760058c6520e2bea7e"},
{file = "pydantic-1.10.7-py3-none-any.whl", hash = "sha256:0cd181f1d0b1d00e2b705f1bf1ac7799a2d938cce3376b8007df62b29be3c2c6"},
{file = "pydantic-1.10.7.tar.gz", hash = "sha256:cfc83c0678b6ba51b0532bea66860617c4cd4251ecf76e9846fa5a9f3454e97e"},
]
[package.dependencies]
typing-extensions = ">=4.2.0"
[package.extras]
dotenv = ["python-dotenv (>=0.10.4)"]
email = ["email-validator (>=1.0.3)"]
[[package]]
name = "pydantic-xml"
version = "0.6.0"
description = "pydantic xml extension"
category = "main"
optional = false
python-versions = ">=3.8,<4.0"
files = [
{file = "pydantic_xml-0.6.0-py3-none-any.whl", hash = "sha256:7a0e4b8ea972b1425126e298c2572e4dbaecb82905b5d9b119a98b7d59fb0204"},
{file = "pydantic_xml-0.6.0.tar.gz", hash = "sha256:ba7f47602187e18c5403376e51275d593833815517915202205223f33044967b"},
]
[package.dependencies]
lxml = {version = ">=4.9.1,<5.0.0", optional = true, markers = "extra == \"lxml\""}
pydantic = ">=1.9.0,<2.0.0"
[package.extras]
docs = ["Sphinx (>=5.3.0,<6.0.0)", "furo (>=2022.12.7,<2023.0.0)", "sphinx-copybutton (>=0.5.1,<0.6.0)", "sphinx_design (>=0.3.0,<0.4.0)", "toml (>=0.10.2,<0.11.0)"]
lxml = ["lxml (>=4.9.1,<5.0.0)"]
[[package]]
name = "pytest"
version = "7.2.2"
@ -554,6 +627,18 @@ files = [
{file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
]
[[package]]
name = "typing-extensions"
version = "4.5.0"
description = "Backported and Experimental Type Hints for Python 3.7+"
category = "main"
optional = false
python-versions = ">=3.7"
files = [
{file = "typing_extensions-4.5.0-py3-none-any.whl", hash = "sha256:fb33085c39dd998ac16d1431ebc293a8b3eedd00fd4a32de0ff79002c19511b4"},
{file = "typing_extensions-4.5.0.tar.gz", hash = "sha256:5cb5f4a79139d699607b3ef622a1dedafa84e115ab0024e0d9c044a9479ca7cb"},
]
[[package]]
name = "urllib3"
version = "1.26.15"
@ -574,4 +659,4 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
[metadata]
lock-version = "2.0"
python-versions = "^3.10"
content-hash = "c2d28a1355e6578e9415007ba906bfcab073adba9c398e82f45391989a4edc39"
content-hash = "25fb6ccc2bf825d93cd9f36ad58792f0da7f2bc1044b26a508fa4370956249c6"

View file

@ -1,6 +1,6 @@
[tool.poetry]
name = "isdn"
version = "1.0.1"
version = "1.1.0"
description = "Library for ISDN (International Standard Dojin Numbering)"
authors = ["Babibubebon <babibubebon@babibubebo.org>"]
homepage = "https://github.com/Babibubebon/isdn-python"
@ -24,6 +24,7 @@ python = "^3.10"
click = "^8.1.3"
requests = "^2.28.2"
lxml = "^4.9.2"
pydantic-xml = {extras = ["lxml"], version = "^0.6.0"}
[tool.poetry.group.dev.dependencies]
isort = "^5.12.0"

View file

@ -1,6 +1,6 @@
from unittest import TestCase
from isdn import ISDN, InvalidIsdnError
from isdn import ISDN
class TestIsdn(TestCase):
@ -9,7 +9,12 @@ class TestIsdn(TestCase):
ISDN(2784702901978, "278", "4", "702901", "97", "8")
ISDN("278-4-702901-97-8", "278", "4", "702901", "97", "8")
disp_isdn = "ISDN278-4-702901-97-8"
isdn = ISDN(disp_isdn)
self.assertEqual(disp_isdn, isdn.to_disp_isdn())
with self.assertRaises(ValueError):
ISDN("ISDN278-4-702901")
ISDN("2784702901978", "278", "4", "000000", "97", "8")
def test_parts(self):
@ -53,11 +58,3 @@ class TestIsdn(TestCase):
self.assertEqual("7", ISDN.calc_check_digit("278470290107"))
self.assertEqual("8", ISDN.calc_check_digit("278470290113"))
self.assertEqual("9", ISDN.calc_check_digit("278470290103"))
def test_from_disp_isdn(self):
disp_isdn = "ISDN278-4-702901-97-8"
isdn = ISDN.from_disp_isdn(disp_isdn)
self.assertEqual(disp_isdn, isdn.to_disp_isdn())
with self.assertRaises(InvalidIsdnError):
ISDN.from_disp_isdn("ISDN278-4-702901")