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
« 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
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
16def _repr(node):
17 from coreapi.codecs.python import PythonCodec
18 return PythonCodec().encode(node)
21def _str(node):
22 from coreapi.codecs.display import DisplayCodec
23 return DisplayCodec().encode(node)
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)
45# The field class, as used by Link objects:
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)
53# The Core API primitives:
55class Document(itypes.Dict):
56 """
57 The Core API document type.
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
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.')
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()}
84 def clone(self, data):
85 return self.__class__(self.url, self.title, self.description, self.media_type, data)
87 def __iter__(self):
88 items = sorted(self._data.items(), key=_key_sorting)
89 return iter([key for key, value in items])
91 def __repr__(self):
92 return _repr(self)
94 def __str__(self):
95 return _str(self)
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)
106 @property
107 def url(self):
108 return self._url
110 @property
111 def title(self):
112 return self._title
114 @property
115 def description(self):
116 return self._description
118 @property
119 def media_type(self):
120 return self._media_type
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 ])
129 @property
130 def links(self):
131 return OrderedDict([
132 (key, value) for key, value in self.items()
133 if isinstance(value, Link)
134 ])
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()}
147 def __iter__(self):
148 items = sorted(self._data.items(), key=_key_sorting)
149 return iter([key for key, value in items])
151 def __repr__(self):
152 return _repr(self)
154 def __str__(self):
155 return _str(self)
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 ])
164 @property
165 def links(self):
166 return OrderedDict([
167 (key, value) for key, value in self.items()
168 if isinstance(value, Link)
169 ])
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)]
179 def __repr__(self):
180 return _repr(self)
182 def __str__(self):
183 return _str(self)
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.")
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 ])
222 @property
223 def url(self):
224 return self._url
226 @property
227 def action(self):
228 return self._action
230 @property
231 def encoding(self):
232 return self._encoding
234 @property
235 def transform(self):
236 return self._transform
238 @property
239 def title(self):
240 return self._title
242 @property
243 def description(self):
244 return self._description
246 @property
247 def fields(self):
248 return self._fields
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 )
261 def __repr__(self):
262 return _repr(self)
264 def __str__(self):
265 return _str(self)
268class Error(itypes.Dict):
269 def __init__(self, title=None, content=None):
270 data = {} if (content is None) else content
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.')
279 self._title = '' if (title is None) else title
280 self._data = {key: _to_immutable(value) for key, value in data.items()}
282 def __iter__(self):
283 items = sorted(self._data.items(), key=_key_sorting)
284 return iter([key for key, value in items])
286 def __repr__(self):
287 return _repr(self)
289 def __str__(self):
290 return _str(self)
292 def __eq__(self, other):
293 return (
294 isinstance(other, Error) and
295 self.title == other.title and
296 self._data == other._data
297 )
299 @property
300 def title(self):
301 return self._title
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