Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/coreapi/document.py: 29%

172 statements  

« prev     ^ index     » next       coverage.py v6.4.4, created at 2023-07-17 14:22 -0600

1# coding: utf-8 

2from __future__ import unicode_literals 

3from collections import OrderedDict, namedtuple 

4from coreapi.compat import string_types 

5import itypes 

6 

7 

8def _to_immutable(value): 

9 if isinstance(value, dict): 

10 return Object(value) 

11 elif isinstance(value, list): 

12 return Array(value) 

13 return value 

14 

15 

16def _repr(node): 

17 from coreapi.codecs.python import PythonCodec 

18 return PythonCodec().encode(node) 

19 

20 

21def _str(node): 

22 from coreapi.codecs.display import DisplayCodec 

23 return DisplayCodec().encode(node) 

24 

25 

26def _key_sorting(item): 

27 """ 

28 Document and Object sorting. 

29 Regular attributes sorted alphabetically. 

30 Links are sorted based on their URL and action. 

31 """ 

32 key, value = item 

33 if isinstance(value, Link): 

34 action_priority = { 

35 'get': 0, 

36 'post': 1, 

37 'put': 2, 

38 'patch': 3, 

39 'delete': 4 

40 }.get(value.action, 5) 

41 return (1, (value.url, action_priority)) 

42 return (0, key) 

43 

44 

45# The field class, as used by Link objects: 

46 

47# NOTE: 'type', 'description' and 'example' are now deprecated, 

48# in favor of 'schema'. 

49Field = namedtuple('Field', ['name', 'required', 'location', 'schema', 'description', 'type', 'example']) 

50Field.__new__.__defaults__ = (False, '', None, None, None, None) 

51 

52 

53# The Core API primitives: 

54 

55class Document(itypes.Dict): 

56 """ 

57 The Core API document type. 

58 

59 Expresses the data that the client may access, 

60 and the actions that the client may perform. 

61 """ 

62 def __init__(self, url=None, title=None, description=None, media_type=None, content=None): 

63 content = {} if (content is None) else content 

64 

65 if url is not None and not isinstance(url, string_types): 

66 raise TypeError("'url' must be a string.") 

67 if title is not None and not isinstance(title, string_types): 

68 raise TypeError("'title' must be a string.") 

69 if description is not None and not isinstance(description, string_types): 

70 raise TypeError("'description' must be a string.") 

71 if media_type is not None and not isinstance(media_type, string_types): 

72 raise TypeError("'media_type' must be a string.") 

73 if not isinstance(content, dict): 

74 raise TypeError("'content' must be a dict.") 

75 if any([not isinstance(key, string_types) for key in content.keys()]): 

76 raise TypeError('content keys must be strings.') 

77 

78 self._url = '' if (url is None) else url 

79 self._title = '' if (title is None) else title 

80 self._description = '' if (description is None) else description 

81 self._media_type = '' if (media_type is None) else media_type 

82 self._data = {key: _to_immutable(value) for key, value in content.items()} 

83 

84 def clone(self, data): 

85 return self.__class__(self.url, self.title, self.description, self.media_type, data) 

86 

87 def __iter__(self): 

88 items = sorted(self._data.items(), key=_key_sorting) 

89 return iter([key for key, value in items]) 

90 

91 def __repr__(self): 

92 return _repr(self) 

93 

94 def __str__(self): 

95 return _str(self) 

96 

97 def __eq__(self, other): 

98 if self.__class__ == other.__class__: 

99 return ( 

100 self.url == other.url and 

101 self.title == other.title and 

102 self._data == other._data 

103 ) 

104 return super(Document, self).__eq__(other) 

105 

106 @property 

107 def url(self): 

108 return self._url 

109 

110 @property 

111 def title(self): 

112 return self._title 

113 

114 @property 

115 def description(self): 

116 return self._description 

117 

118 @property 

119 def media_type(self): 

120 return self._media_type 

121 

122 @property 

123 def data(self): 

124 return OrderedDict([ 

125 (key, value) for key, value in self.items() 

126 if not isinstance(value, Link) 

127 ]) 

128 

129 @property 

130 def links(self): 

131 return OrderedDict([ 

132 (key, value) for key, value in self.items() 

133 if isinstance(value, Link) 

134 ]) 

135 

136 

137class Object(itypes.Dict): 

138 """ 

139 An immutable mapping of strings to values. 

140 """ 

141 def __init__(self, *args, **kwargs): 

142 data = dict(*args, **kwargs) 

143 if any([not isinstance(key, string_types) for key in data.keys()]): 

144 raise TypeError('Object keys must be strings.') 

145 self._data = {key: _to_immutable(value) for key, value in data.items()} 

146 

147 def __iter__(self): 

148 items = sorted(self._data.items(), key=_key_sorting) 

149 return iter([key for key, value in items]) 

150 

151 def __repr__(self): 

152 return _repr(self) 

153 

154 def __str__(self): 

155 return _str(self) 

156 

157 @property 

158 def data(self): 

159 return OrderedDict([ 

160 (key, value) for key, value in self.items() 

161 if not isinstance(value, Link) 

162 ]) 

163 

164 @property 

165 def links(self): 

166 return OrderedDict([ 

167 (key, value) for key, value in self.items() 

168 if isinstance(value, Link) 

169 ]) 

170 

171 

172class Array(itypes.List): 

173 """ 

174 An immutable list type container. 

175 """ 

176 def __init__(self, *args): 

177 self._data = [_to_immutable(value) for value in list(*args)] 

178 

179 def __repr__(self): 

180 return _repr(self) 

181 

182 def __str__(self): 

183 return _str(self) 

184 

185 

186class Link(itypes.Object): 

187 """ 

188 Links represent the actions that a client may perform. 

189 """ 

190 def __init__(self, url=None, action=None, encoding=None, transform=None, title=None, description=None, fields=None): 

191 if (url is not None) and (not isinstance(url, string_types)): 

192 raise TypeError("Argument 'url' must be a string.") 

193 if (action is not None) and (not isinstance(action, string_types)): 

194 raise TypeError("Argument 'action' must be a string.") 

195 if (encoding is not None) and (not isinstance(encoding, string_types)): 

196 raise TypeError("Argument 'encoding' must be a string.") 

197 if (transform is not None) and (not isinstance(transform, string_types)): 

198 raise TypeError("Argument 'transform' must be a string.") 

199 if (title is not None) and (not isinstance(title, string_types)): 

200 raise TypeError("Argument 'title' must be a string.") 

201 if (description is not None) and (not isinstance(description, string_types)): 

202 raise TypeError("Argument 'description' must be a string.") 

203 if (fields is not None) and (not isinstance(fields, (list, tuple))): 

204 raise TypeError("Argument 'fields' must be a list.") 

205 if (fields is not None) and any([ 

206 not (isinstance(item, string_types) or isinstance(item, Field)) 

207 for item in fields 

208 ]): 

209 raise TypeError("Argument 'fields' must be a list of strings or fields.") 

210 

211 self._url = '' if (url is None) else url 

212 self._action = '' if (action is None) else action 

213 self._encoding = '' if (encoding is None) else encoding 

214 self._transform = '' if (transform is None) else transform 

215 self._title = '' if (title is None) else title 

216 self._description = '' if (description is None) else description 

217 self._fields = () if (fields is None) else tuple([ 

218 item if isinstance(item, Field) else Field(item, required=False, location='') 

219 for item in fields 

220 ]) 

221 

222 @property 

223 def url(self): 

224 return self._url 

225 

226 @property 

227 def action(self): 

228 return self._action 

229 

230 @property 

231 def encoding(self): 

232 return self._encoding 

233 

234 @property 

235 def transform(self): 

236 return self._transform 

237 

238 @property 

239 def title(self): 

240 return self._title 

241 

242 @property 

243 def description(self): 

244 return self._description 

245 

246 @property 

247 def fields(self): 

248 return self._fields 

249 

250 def __eq__(self, other): 

251 return ( 

252 isinstance(other, Link) and 

253 self.url == other.url and 

254 self.action == other.action and 

255 self.encoding == other.encoding and 

256 self.transform == other.transform and 

257 self.description == other.description and 

258 sorted(self.fields, key=lambda f: f.name) == sorted(other.fields, key=lambda f: f.name) 

259 ) 

260 

261 def __repr__(self): 

262 return _repr(self) 

263 

264 def __str__(self): 

265 return _str(self) 

266 

267 

268class Error(itypes.Dict): 

269 def __init__(self, title=None, content=None): 

270 data = {} if (content is None) else content 

271 

272 if title is not None and not isinstance(title, string_types): 

273 raise TypeError("'title' must be a string.") 

274 if content is not None and not isinstance(content, dict): 

275 raise TypeError("'content' must be a dict.") 

276 if any([not isinstance(key, string_types) for key in data.keys()]): 

277 raise TypeError('content keys must be strings.') 

278 

279 self._title = '' if (title is None) else title 

280 self._data = {key: _to_immutable(value) for key, value in data.items()} 

281 

282 def __iter__(self): 

283 items = sorted(self._data.items(), key=_key_sorting) 

284 return iter([key for key, value in items]) 

285 

286 def __repr__(self): 

287 return _repr(self) 

288 

289 def __str__(self): 

290 return _str(self) 

291 

292 def __eq__(self, other): 

293 return ( 

294 isinstance(other, Error) and 

295 self.title == other.title and 

296 self._data == other._data 

297 ) 

298 

299 @property 

300 def title(self): 

301 return self._title 

302 

303 def get_messages(self): 

304 messages = [] 

305 for value in self.values(): 

306 if isinstance(value, Array): 

307 messages += [ 

308 item for item in value if isinstance(item, string_types) 

309 ] 

310 return messages