Coverage for src/lanraragi_api/api/category.py: 97%

29 statements  

« prev     ^ index     » next       coverage.py v7.16.0, created at 2026-09-22 23:20 +0000

1from typing import Any 

2 

3from lanraragi_api.api.base import BaseAPICall 

4from lanraragi_api.entity.base import OperationResponse 

5from lanraragi_api.entity.category import CategoryMetadata 

6 

7 

8class CategoryAPI(BaseAPICall): 

9 """Endpoints related to Categories. 

10 

11 Shared request and error behavior is documented on ``BaseAPICall``. 

12 """ 

13 

14 def get_all_categories(self) -> list[CategoryMetadata]: 

15 """Get all the categories saved on the server. 

16 

17 Returns: 

18 list[CategoryMetadata]: Metadata of every category on the server. 

19 

20 Raises: 

21 APIHttpError: Any non-2xx status code returned by the server. 

22 APIResponseDecodeError: If the response body is not a list of 

23 objects matching ``CategoryMetadata``. 

24 """ 

25 return self.request_model_list("GET", "/api/categories", CategoryMetadata) 

26 

27 def get_category(self, id: str) -> CategoryMetadata | None: 

28 """Get the details of the specified category ID. 

29 

30 Args: 

31 id: ID of the Category desired. 

32 

33 Returns: 

34 CategoryMetadata | None: Details of the category, or None when the 

35 server answers with 400. 

36 

37 Raises: 

38 APIHttpError: Any status code other than 200 and 400. 

39 

40 Note: 

41 The 400 response of the server is turned into None instead of 

42 raising. 

43 """ 

44 path = f"/api/categories/{id}" 

45 resp = self.request("GET", path, expected_statuses={200, 400}) 

46 if resp.status_code == 400: 

47 return None 

48 return self.parse_model( 

49 CategoryMetadata, self.parse_json_response(resp, path), path 

50 ) 

51 

52 def create_category( 

53 self, name: str, search: str | None = None, pinned: bool | None = None 

54 ) -> OperationResponse: 

55 """Create a new Category. 

56 

57 Args: 

58 name: Name of the Category. 

59 search: Matching predicate, if creating a Dynamic Category. 

60 Defaults to None. 

61 pinned: Whether the created category will be pinned. Defaults to 

62 None. 

63 

64 Returns: 

65 OperationResponse: Result of the operation, with the ID of the 

66 created Category in the extra ``category_id`` field. 

67 

68 Raises: 

69 APIResponseDecodeError: If the response body is not valid JSON, or 

70 does not match ``OperationResponse``. 

71 APIOperationError: If the operation failed and raising is enabled. 

72 

73 Note: 

74 A 400 response is returned in the operation result, with ``success`` 

75 set to 0, instead of raising. 

76 """ 

77 return self.request_operation( 

78 "PUT", 

79 "/api/categories", 

80 data={ 

81 "name": name, 

82 "search": search, 

83 "pinned": pinned, 

84 }, 

85 ) 

86 

87 def update_category( 

88 self, 

89 id: str, 

90 name: str | None = None, 

91 search: str | None = None, 

92 pinned: bool | None = None, 

93 ) -> OperationResponse: 

94 """Modify a Category. 

95 

96 Args: 

97 id: ID of the Category to update. 

98 name: New name of the Category. Defaults to None. 

99 search: Predicate. Trying to add a predicate to a category that 

100 already contains Archives will give you an error. Defaults to 

101 None. 

102 pinned: Add this argument to pin the Category. If you don't, the 

103 category will be unpinned on update. Defaults to None. 

104 

105 Returns: 

106 OperationResponse: Result of the operation, with the ID of the 

107 updated Category in the extra ``category_id`` field. 

108 

109 Raises: 

110 APIResponseDecodeError: If the response body is not valid JSON, or 

111 does not match ``OperationResponse``. 

112 APIOperationError: If the operation failed and raising is enabled. 

113 

114 Note: 

115 Failure responses such as 400 (error response) or 423 (Category 

116 locked for modification) are returned in the operation result, with 

117 ``success`` set to 0, instead of raising. 

118 """ 

119 return self.request_operation( 

120 "PUT", 

121 f"/api/categories/{id}", 

122 data={ 

123 "name": name, 

124 "search": search, 

125 "pinned": pinned, 

126 }, 

127 ) 

128 

129 def delete_category(self, id: str) -> OperationResponse: 

130 """Remove a Category. 

131 

132 Args: 

133 id: Category ID. 

134 

135 Returns: 

136 OperationResponse: Result of the operation. 

137 

138 Raises: 

139 APIResponseDecodeError: If the response body is not valid JSON, or 

140 does not match ``OperationResponse``. 

141 APIOperationError: If the operation failed and raising is enabled. 

142 

143 Note: 

144 A 423 response, sent when the Category is locked for modification, 

145 is returned in the operation result, with ``success`` set to 0, 

146 instead of raising. 

147 """ 

148 return self.request_operation("DELETE", f"/api/categories/{id}") 

149 

150 def add_archive_to_category( 

151 self, category_id: str, archive_id: str 

152 ) -> OperationResponse: 

153 """Add the specified Archive ID (see Archive API) to the given Category. 

154 

155 Args: 

156 category_id: Category ID to add the Archive to. 

157 archive_id: Archive ID to add. 

158 

159 Returns: 

160 OperationResponse: Result of the operation. 

161 

162 Raises: 

163 APIResponseDecodeError: If the response body is not valid JSON, or 

164 does not match ``OperationResponse``. 

165 APIOperationError: If the operation failed and raising is enabled. 

166 

167 Note: 

168 A 423 response, sent when the Category is locked for modification, 

169 is returned in the operation result, with ``success`` set to 0, 

170 instead of raising. 

171 """ 

172 return self.request_operation( 

173 "PUT", f"/api/categories/{category_id}/{archive_id}" 

174 ) 

175 

176 def remove_archive_from_category( 

177 self, category_id: str, archive_id: str 

178 ) -> OperationResponse: 

179 """Remove an Archive ID from a Category. 

180 

181 Args: 

182 category_id: Category ID. 

183 archive_id: Archive ID. 

184 

185 Returns: 

186 OperationResponse: Result of the operation. 

187 

188 Raises: 

189 APIResponseDecodeError: If the response body is not valid JSON, or 

190 does not match ``OperationResponse``. 

191 APIOperationError: If the operation failed and raising is enabled. 

192 

193 Note: 

194 A 423 response, sent when the Category is locked for modification, 

195 is returned in the operation result, with ``success`` set to 0, 

196 instead of raising. 

197 """ 

198 return self.request_operation( 

199 "DELETE", f"/api/categories/{category_id}/{archive_id}" 

200 ) 

201 

202 def get_bookmark_link(self) -> dict[Any, Any]: 

203 """Retrieve the ID of the category linked to the bookmark feature. 

204 

205 Returns: 

206 dict: Decoded response, with the linked category ID in 

207 ``category_id``, as an empty string if no category is linked. 

208 

209 Raises: 

210 APIHttpError: Any non-2xx status code returned by the server. 

211 APIResponseDecodeError: If the response body is not valid JSON. 

212 """ 

213 return self.request_json("GET", "/api/categories/bookmark_link") 

214 

215 def update_bookmark_link(self, id: str) -> OperationResponse: 

216 """Link the bookmark feature to the specified static category. 

217 

218 This determines which category archives are added to when using the 

219 bookmark button. 

220 

221 Args: 

222 id: ID of the static category to link with the bookmark feature. 

223 

224 Returns: 

225 OperationResponse: Result of the operation, with the ID of the 

226 linked category in the extra ``category_id`` field. 

227 

228 Raises: 

229 APIResponseDecodeError: If the response body is not valid JSON, or 

230 does not match ``OperationResponse``. 

231 APIOperationError: If the operation failed and raising is enabled. 

232 

233 Note: 

234 Failure responses such as 400 (error response) or 404 (no category 

235 with the given ID) are returned in the operation result, with 

236 ``success`` set to 0, instead of raising. 

237 """ 

238 return self.request_operation("PUT", f"/api/categories/bookmark_link/{id}") 

239 

240 def disable_bookmark_feature(self) -> OperationResponse: 

241 """Disable the bookmark feature by removing the link to any category. 

242 

243 Returns: 

244 OperationResponse: Result of the operation, with the ID of the 

245 previously linked category in the extra ``category_id`` field, 

246 as an empty string if no category was linked. 

247 

248 Raises: 

249 APIResponseDecodeError: If the response body is not valid JSON, or 

250 does not match ``OperationResponse``. 

251 APIOperationError: If the operation failed and raising is enabled. 

252 """ 

253 return self.request_operation("DELETE", "/api/categories/bookmark_link")