Coverage for youversion/models/common.py: 100%

48 statements  

« prev     ^ index     » next       coverage.py v7.14.3, created at 2026-06-26 11:31 +0100

1"""Common data models used across different API endpoints.""" 

2 

3from typing import Any, Protocol 

4 

5try: 

6 from typing import TypeAlias 

7except ImportError: 

8 # Python < 3.10 compatibility 

9 from typing import TypeAlias 

10 

11 

12class UserBaseProtocol(Protocol): 

13 """Protocol for base user information.""" 

14 

15 id: int 

16 name: str 

17 avatar: str | None 

18 email: str | None 

19 

20 def __getattr__(self, name: str) -> Any: 

21 """Allow access to dynamically added fields.""" 

22 class_name = self.__class__.__name__ 

23 raise AttributeError(f"'{class_name}' has no attribute '{name}'") 

24 

25 

26# Type alias for convenience 

27UserBase: TypeAlias = UserBaseProtocol 

28 

29 

30class AvatarProtocol(Protocol): 

31 """Protocol for user avatar information.""" 

32 

33 url: str 

34 width: int | None 

35 height: int | None 

36 

37 def __getattr__(self, name: str) -> Any: 

38 """Allow access to dynamically added fields.""" 

39 class_name = self.__class__.__name__ 

40 raise AttributeError(f"'{class_name}' has no attribute '{name}'") 

41 

42 

43# Type alias for convenience 

44Avatar: TypeAlias = AvatarProtocol 

45 

46 

47class ImagesProtocol(Protocol): 

48 """Protocol for image information.""" 

49 

50 url: str 

51 width: int | None 

52 height: int | None 

53 alt: str | None 

54 

55 def __getattr__(self, name: str) -> Any: 

56 """Allow access to dynamically added fields.""" 

57 class_name = self.__class__.__name__ 

58 raise AttributeError(f"'{class_name}' has no attribute '{name}'") 

59 

60 

61# Type alias for convenience 

62Images: TypeAlias = ImagesProtocol 

63 

64 

65class LinkProtocol(Protocol): 

66 """Protocol for link information.""" 

67 

68 url: str 

69 text: str | None 

70 target: str | None 

71 

72 def __getattr__(self, name: str) -> Any: 

73 """Allow access to dynamically added fields.""" 

74 class_name = self.__class__.__name__ 

75 raise AttributeError(f"'{class_name}' has no attribute '{name}'") 

76 

77 

78# Type alias for convenience 

79Link: TypeAlias = LinkProtocol 

80 

81 

82class LocalizeProtocol(Protocol): 

83 """Protocol for localized text information.""" 

84 

85 text: str 

86 language_tag: str | None 

87 

88 def __getattr__(self, name: str) -> Any: 

89 """Allow access to dynamically added fields.""" 

90 class_name = self.__class__.__name__ 

91 raise AttributeError(f"'{class_name}' has no attribute '{name}'") 

92 

93 

94# Type alias for convenience 

95Localize: TypeAlias = LocalizeProtocol 

96 

97 

98class ApiErrorProtocol(Protocol): 

99 """Protocol for API error information.""" 

100 

101 code: str 

102 message: str 

103 field: str | None 

104 

105 def __getattr__(self, name: str) -> Any: 

106 """Allow access to dynamically added fields.""" 

107 class_name = self.__class__.__name__ 

108 raise AttributeError(f"'{class_name}' has no attribute '{name}'") 

109 

110 

111# Type alias for convenience 

112ApiError: TypeAlias = ApiErrorProtocol 

113 

114 

115class ApiErrorsProtocol(Protocol): 

116 """Protocol for multiple API errors.""" 

117 

118 errors: list[ApiError] 

119 

120 def __getattr__(self, name: str) -> Any: 

121 """Allow access to dynamically added fields.""" 

122 class_name = self.__class__.__name__ 

123 raise AttributeError(f"'{class_name}' has no attribute '{name}'") 

124 

125 

126# Type alias for convenience 

127ApiErrors: TypeAlias = ApiErrorsProtocol 

128 

129 

130class PaginationInfoProtocol(Protocol): 

131 """Protocol for pagination information.""" 

132 

133 page: int 

134 per_page: int 

135 total: int 

136 total_pages: int 

137 has_next: bool 

138 has_prev: bool 

139 

140 def __getattr__(self, name: str) -> Any: 

141 """Allow access to dynamically added fields.""" 

142 class_name = self.__class__.__name__ 

143 raise AttributeError(f"'{class_name}' has no attribute '{name}'") 

144 

145 

146# Type alias for convenience 

147PaginationInfo: TypeAlias = PaginationInfoProtocol 

148 

149 

150class ApiResponseProtocol(Protocol): 

151 """Protocol for generic API response wrapper.""" 

152 

153 data: Any 

154 pagination: PaginationInfo | None 

155 errors: list[ApiError] | None 

156 

157 def __getattr__(self, name: str) -> Any: 

158 """Allow access to dynamically added fields.""" 

159 class_name = self.__class__.__name__ 

160 raise AttributeError(f"'{class_name}' has no attribute '{name}'") 

161 

162 

163# Type alias for convenience 

164ApiResponse: TypeAlias = ApiResponseProtocol