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

1import dataclasses 

2import enum 

3import functools 

4import types 

5import typing 

6 

7 

8TAG_NAME_CACHE_SIZE: typing.Final = 4096 

9 

10 

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(":", "_") 

15 

16 

17@typing.final 

18@dataclasses.dataclass(kw_only=True, slots=True, frozen=True) 

19class OneMetaTag: 

20 """Helper public tag wrapper.""" 

21 

22 name: str 

23 value: str 

24 

25 @property 

26 def normalized_name(self) -> str: 

27 return _transform_tag_name(self.name) 

28 

29 

30@typing.final 

31@dataclasses.dataclass(kw_only=True, slots=True, frozen=True) 

32class ValuesGroup: 

33 """Helper inner wrapper.""" 

34 

35 original: str 

36 normalized: str 

37 

38 

39@typing.final 

40@dataclasses.dataclass(kw_only=True, slots=True, frozen=True) 

41class TagsGroup: 

42 """For user returns this exact struct.""" 

43 

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) 

49 

50 

51@typing.final 

52@dataclasses.dataclass(kw_only=True, slots=True, frozen=True) 

53class SocialMediaSnippet: 

54 """Social media snippet group.""" 

55 

56 title: str = "" 

57 description: str = "" 

58 image: str = "" 

59 image_width: int = 0 

60 image_height: int = 0 

61 url: str = "" 

62 

63 

64@typing.final 

65@dataclasses.dataclass(kw_only=True, slots=True, frozen=True) 

66class SnippetGroup: 

67 """Groupping for social media.""" 

68 

69 open_graph: SocialMediaSnippet = dataclasses.field(default_factory=SocialMediaSnippet) 

70 twitter: SocialMediaSnippet = dataclasses.field(default_factory=SocialMediaSnippet) 

71 

72 

73@typing.final 

74class WhatToParse(enum.IntEnum): 

75 TITLE = 0 

76 BASIC = 1 

77 OPEN_GRAPH = 2 

78 TWITTER = 3 

79 OTHER = 4 

80 

81 

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") 

97 

98 

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)