Coverage for meta_tags_parser/structs.py: 100%
61 statements
« prev ^ index » next coverage.py v7.10.3, created at 2026-08-22 19:08 +0000
« prev ^ index » next coverage.py v7.10.3, created at 2026-08-22 19:08 +0000
1import dataclasses
2import enum
3import functools
4import types
5import typing
8TAG_NAME_CACHE_SIZE: typing.Final = 4096
11# tag names come from arbitrary web pages, so the cache has to be bounded
12@functools.lru_cache(maxsize=TAG_NAME_CACHE_SIZE)
13def _transform_tag_name(tag_name: str) -> str:
14 return tag_name.replace(":", "_")
17@typing.final
18@dataclasses.dataclass(kw_only=True, slots=True, frozen=True)
19class OneMetaTag:
20 """Helper public tag wrapper."""
22 name: str
23 value: str
25 @property
26 def normalized_name(self) -> str:
27 return _transform_tag_name(self.name)
30@typing.final
31@dataclasses.dataclass(kw_only=True, slots=True, frozen=True)
32class ValuesGroup:
33 """Helper inner wrapper."""
35 original: str
36 normalized: str
39@typing.final
40@dataclasses.dataclass(kw_only=True, slots=True, frozen=True)
41class TagsGroup:
42 """For user returns this exact struct."""
44 title: str = ""
45 basic: list[OneMetaTag] = dataclasses.field(default_factory=list)
46 open_graph: list[OneMetaTag] = dataclasses.field(default_factory=list)
47 twitter: list[OneMetaTag] = dataclasses.field(default_factory=list)
48 other: list[OneMetaTag] = dataclasses.field(default_factory=list)
51@typing.final
52@dataclasses.dataclass(kw_only=True, slots=True, frozen=True)
53class SocialMediaSnippet:
54 """Social media snippet group."""
56 title: str = ""
57 description: str = ""
58 image: str = ""
59 image_width: int = 0
60 image_height: int = 0
61 url: str = ""
64@typing.final
65@dataclasses.dataclass(kw_only=True, slots=True, frozen=True)
66class SnippetGroup:
67 """Groupping for social media."""
69 open_graph: SocialMediaSnippet = dataclasses.field(default_factory=SocialMediaSnippet)
70 twitter: SocialMediaSnippet = dataclasses.field(default_factory=SocialMediaSnippet)
73@typing.final
74class WhatToParse(enum.IntEnum):
75 TITLE = 0
76 BASIC = 1
77 OPEN_GRAPH = 2
78 TWITTER = 3
79 OTHER = 4
82@typing.final
83@dataclasses.dataclass(kw_only=True, slots=True, frozen=True)
84class SettingsFromUser:
85 what_to_parse: tuple[WhatToParse, ...] = (
86 WhatToParse.TITLE,
87 WhatToParse.BASIC,
88 WhatToParse.OPEN_GRAPH,
89 WhatToParse.TWITTER,
90 WhatToParse.OTHER,
91 )
92 optimize_input: bool = True
93 fallback_limit_chars: int = 65536
94 max_scan_chars: int = 524288
95 hard_limit_chars: int | None = None
96 boundary_tags: tuple[str, str] = ("</head>", "<body")
99DEFAULT_SETTINGS_FROM_USER: typing.Final = SettingsFromUser()
100WHAT_ATTRS_IN_SOCIAL_MEDIA_SNIPPET: typing.Final = SocialMediaSnippet.__dataclass_fields__.keys()
101DIMENSION_SNIPPET_FIELDS: typing.Final[frozenset[str]] = frozenset(("image_width", "image_height"))
102BASIC_META_TAGS: typing.Final[tuple[str, ...]] = (
103 "title",
104 "description",
105 "keywords",
106 "robots",
107 "viewport",
108)
109SETTINGS_FOR_SOCIAL_MEDIA: typing.Final[
110 typing.Mapping[
111 typing.Literal[WhatToParse.OPEN_GRAPH, WhatToParse.TWITTER],
112 typing.Mapping[str, str | tuple[str, ...]],
113 ]
114] = types.MappingProxyType(
115 {
116 WhatToParse.OPEN_GRAPH: types.MappingProxyType({"prop": ("property",), "prefix": "og:"}),
117 # weird thing about twitter: it use name and property simultaneously
118 # i mean name is old format, property is new, but all currently accepted as i see at the moment
119 WhatToParse.TWITTER: types.MappingProxyType({"prop": ("name", "property"), "prefix": "twitter:"}),
120 }
121)