Coverage for meta_tags_parser/structs.py: 100%
63 statements
« prev ^ index » next coverage.py v7.10.3, created at 2025-09-18 21:46 +0000
« prev ^ index » next coverage.py v7.10.3, created at 2025-09-18 21:46 +0000
1import dataclasses
2import enum
3import functools
4import types
5import typing
8@functools.cache
9def _normalize_tag_name(tag_name: str) -> str:
10 return tag_name.replace(":", "_")
13@typing.final
14@dataclasses.dataclass(kw_only=True, slots=True, frozen=True)
15class OneMetaTag:
16 """Helper public tag wrapper."""
18 name: str
19 value: str
21 @property
22 def normalized_name(self) -> str:
23 return _normalize_tag_name(self.name)
26@typing.final
27@dataclasses.dataclass(kw_only=True, slots=True, frozen=True)
28class ValuesGroup:
29 """Helper inner wrapper."""
31 original: str
32 normalized: str
35@typing.final
36@dataclasses.dataclass(kw_only=True, slots=True, frozen=True)
37class TagsGroup:
38 """For user returns this exact struct."""
40 title: str = ""
41 basic: list[OneMetaTag] = dataclasses.field(default_factory=list)
42 open_graph: list[OneMetaTag] = dataclasses.field(default_factory=list)
43 twitter: list[OneMetaTag] = dataclasses.field(default_factory=list)
44 other: list[OneMetaTag] = dataclasses.field(default_factory=list)
47@typing.final
48@dataclasses.dataclass(kw_only=True, slots=True, frozen=True)
49class SocialMediaSnippet:
50 """Social media snippet group."""
52 title: str = ""
53 description: str = ""
54 image: str = ""
55 image_width: int = 0
56 image_height: int = 0
57 url: str = ""
60@typing.final
61@dataclasses.dataclass(kw_only=True, slots=True, frozen=True)
62class SnippetGroup:
63 """Groupping for social media."""
65 open_graph: SocialMediaSnippet = dataclasses.field(default_factory=SocialMediaSnippet)
66 twitter: SocialMediaSnippet = dataclasses.field(default_factory=SocialMediaSnippet)
69@typing.final
70class WhatToParse(enum.IntEnum):
71 TITLE = 0
72 BASIC = 1
73 OPEN_GRAPH = 2
74 TWITTER = 3
75 OTHER = 4
78@typing.final
79@dataclasses.dataclass(kw_only=True, slots=True, frozen=True)
80class SettingsFromUser:
81 what_to_parse: tuple[WhatToParse, ...] = (
82 WhatToParse.TITLE,
83 WhatToParse.BASIC,
84 WhatToParse.OPEN_GRAPH,
85 WhatToParse.TWITTER,
86 WhatToParse.OTHER,
87 )
88 optimize_input: bool = True
89 fallback_limit_chars: int = 65536
90 max_scan_chars: int = 524288
91 hard_limit_chars: int | None = None
92 boundary_tags: tuple[str, str] = ("</head>", "<body")
95DEFAULT_SETTINGS_FROM_USER: typing.Final = SettingsFromUser()
96WHAT_ATTRS_IN_SOCIAL_MEDIA_SNIPPET: typing.Final = SocialMediaSnippet.__dataclass_fields__.keys()
97BASIC_META_TAGS: typing.Final[tuple[str, ...]] = (
98 "title",
99 "description",
100 "keywords",
101 "robots",
102 "viewport",
103)
104SETTINGS_FOR_SOCIAL_MEDIA: typing.Final[
105 typing.Mapping[
106 typing.Literal[WhatToParse.OPEN_GRAPH, WhatToParse.TWITTER],
107 typing.Mapping[str, str | tuple[str, ...]],
108 ]
109] = types.MappingProxyType(
110 {
111 WhatToParse.OPEN_GRAPH: types.MappingProxyType({"prop": ("property",), "prefix": "og:"}),
112 # weird thing about twitter: it use name and property simultaneously
113 # i mean name is old format, property is new, but all currently accepted as i see at the moment
114 WhatToParse.TWITTER: types.MappingProxyType({"prop": ("name", "property"), "prefix": "twitter:"}),
115 }
116)