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

1import dataclasses 

2import enum 

3import functools 

4import types 

5import typing 

6 

7 

8@functools.cache 

9def _normalize_tag_name(tag_name: str) -> str: 

10 return tag_name.replace(":", "_") 

11 

12 

13@typing.final 

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

15class OneMetaTag: 

16 """Helper public tag wrapper.""" 

17 

18 name: str 

19 value: str 

20 

21 @property 

22 def normalized_name(self) -> str: 

23 return _normalize_tag_name(self.name) 

24 

25 

26@typing.final 

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

28class ValuesGroup: 

29 """Helper inner wrapper.""" 

30 

31 original: str 

32 normalized: str 

33 

34 

35@typing.final 

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

37class TagsGroup: 

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

39 

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) 

45 

46 

47@typing.final 

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

49class SocialMediaSnippet: 

50 """Social media snippet group.""" 

51 

52 title: str = "" 

53 description: str = "" 

54 image: str = "" 

55 image_width: int = 0 

56 image_height: int = 0 

57 url: str = "" 

58 

59 

60@typing.final 

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

62class SnippetGroup: 

63 """Groupping for social media.""" 

64 

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

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

67 

68 

69@typing.final 

70class WhatToParse(enum.IntEnum): 

71 TITLE = 0 

72 BASIC = 1 

73 OPEN_GRAPH = 2 

74 TWITTER = 3 

75 OTHER = 4 

76 

77 

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

93 

94 

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)