Coverage for src/epublib/soup.py: 95%
20 statements
« prev ^ index » next coverage.py v7.10.6, created at 2025-09-18 16:07 -0300
« prev ^ index » next coverage.py v7.10.6, created at 2025-09-18 16:07 -0300
1# type: ignore
3from typing import Annotated, Any
5import bs4
7from epublib.exceptions import EPUBError
9# Reproducing the behavior of typing.Required
10type Required[T] = Annotated[T, "required"]
13class EnforcingSoup(bs4.BeautifulSoup):
14 """A BeautifulSoup subclass that enforces the presence of certain tags."""
16 def __init__(
17 self,
18 markup: str | bytes,
19 features: str | None = None,
20 *args: Any,
21 **kwargs: Any,
22 ):
23 super().__init__(
24 markup,
25 features,
26 *args,
27 **kwargs,
28 )
30 required = (
31 field
32 for field, annotation in self.__annotations__.items()
33 if annotation.__origin__ is Required
34 )
36 for tag in required:
37 if not getattr(self, tag):
38 raise EPUBError(f"Package document missing {tag}")
41class PackageDocumentSoup(EnforcingSoup):
42 """A BeautifulSoup subclass for the package document."""
44 manifest: Required[bs4.Tag]
45 metadata: Required[bs4.Tag]
46 spine: Required[bs4.Tag]
49class NCXSoup(EnforcingSoup):
50 """A BeautifulSoup subclass for the NCX file."""
52 ncx: Required[bs4.Tag]
53 docTitle: Required[bs4.Tag]
54 head: Required[bs4.Tag]
55 navMap: Required[bs4.Tag]