arkindex-client 1.0.16__py3-none-any.whl → 1.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- apistar/__init__.py +9 -0
- apistar/exceptions.py +13 -0
- arkindex/auth.py +29 -14
- arkindex/client/__init__.py +4 -0
- arkindex/client/base.py +98 -0
- arkindex/{client.py → client/client.py} +24 -20
- arkindex/client/decoders.py +252 -0
- arkindex/client/transports.py +132 -0
- arkindex/compat.py +24 -0
- arkindex/document.py +212 -0
- arkindex/exceptions.py +73 -0
- arkindex/mock.py +3 -3
- arkindex/pagination.py +9 -8
- arkindex/schema/__init__.py +0 -0
- arkindex/schema/jsonschema.py +66 -0
- arkindex/schema/openapi.py +523 -0
- arkindex/schema/validator.py +54 -0
- arkindex_client-1.1.0.dist-info/LICENSE +661 -0
- arkindex_client-1.1.0.dist-info/METADATA +27 -0
- arkindex_client-1.1.0.dist-info/RECORD +23 -0
- {arkindex_client-1.0.16.dist-info → arkindex_client-1.1.0.dist-info}/WHEEL +1 -1
- {arkindex_client-1.0.16.dist-info → arkindex_client-1.1.0.dist-info}/top_level.txt +1 -0
- arkindex/transports.py +0 -14
- arkindex_client-1.0.16.dist-info/METADATA +0 -225
- arkindex_client-1.0.16.dist-info/RECORD +0 -11
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import http
|
|
3
|
+
from importlib.metadata import version
|
|
4
|
+
|
|
5
|
+
import requests
|
|
6
|
+
|
|
7
|
+
from arkindex import exceptions
|
|
8
|
+
from arkindex.client import decoders
|
|
9
|
+
|
|
10
|
+
REQUEST_TIMEOUT = (30, 60)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class BlockAllCookies(http.cookiejar.CookiePolicy):
|
|
14
|
+
"""
|
|
15
|
+
A cookie policy that rejects all cookies.
|
|
16
|
+
Used to override the default `requests` behavior.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
return_ok = set_ok = domain_return_ok = path_return_ok = (
|
|
20
|
+
lambda self, *args, **kwargs: False
|
|
21
|
+
)
|
|
22
|
+
netscape = True
|
|
23
|
+
rfc2965 = hide_cookie2 = False
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class BaseTransport:
|
|
27
|
+
schemes = None
|
|
28
|
+
|
|
29
|
+
def send(self, method, url, query_params=None, content=None, encoding=None):
|
|
30
|
+
raise NotImplementedError()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class HTTPTransport(BaseTransport):
|
|
34
|
+
schemes = ["http", "https"]
|
|
35
|
+
default_decoders = [
|
|
36
|
+
decoders.JSONDecoder(),
|
|
37
|
+
decoders.TextDecoder(),
|
|
38
|
+
decoders.DownloadDecoder(),
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
def __init__(
|
|
42
|
+
self,
|
|
43
|
+
auth=None,
|
|
44
|
+
decoders=None,
|
|
45
|
+
headers=None,
|
|
46
|
+
session=None,
|
|
47
|
+
allow_cookies=True,
|
|
48
|
+
verify=True,
|
|
49
|
+
):
|
|
50
|
+
if session is None:
|
|
51
|
+
session = requests.Session()
|
|
52
|
+
if auth is not None:
|
|
53
|
+
session.auth = auth
|
|
54
|
+
if not allow_cookies:
|
|
55
|
+
session.cookies.set_policy(BlockAllCookies())
|
|
56
|
+
|
|
57
|
+
self.session = session
|
|
58
|
+
self.verify = verify
|
|
59
|
+
self.decoders = list(decoders) if decoders else list(self.default_decoders)
|
|
60
|
+
|
|
61
|
+
client_version = version("arkindex-client")
|
|
62
|
+
self.headers = {
|
|
63
|
+
"accept": ", ".join([decoder.media_type for decoder in self.decoders]),
|
|
64
|
+
"user-agent": f"arkindex-client/{client_version}",
|
|
65
|
+
}
|
|
66
|
+
if headers:
|
|
67
|
+
self.headers.update({key.lower(): value for key, value in headers.items()})
|
|
68
|
+
|
|
69
|
+
def send(self, method, url, query_params=None, content=None, encoding=None):
|
|
70
|
+
options = self.get_request_options(query_params, content, encoding)
|
|
71
|
+
response = self.session.request(method, url, **options)
|
|
72
|
+
result = self.decode_response_content(response)
|
|
73
|
+
|
|
74
|
+
if 400 <= response.status_code <= 599:
|
|
75
|
+
title = "%d %s" % (response.status_code, response.reason)
|
|
76
|
+
raise exceptions.ErrorResponse(
|
|
77
|
+
title=title, status_code=response.status_code, content=result
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
return result
|
|
81
|
+
|
|
82
|
+
def get_decoder(self, content_type=None):
|
|
83
|
+
"""
|
|
84
|
+
Given the value of a 'Content-Type' header, return the appropriate
|
|
85
|
+
decoder for handling the response content.
|
|
86
|
+
"""
|
|
87
|
+
if content_type is None:
|
|
88
|
+
return self.decoders[0]
|
|
89
|
+
|
|
90
|
+
content_type = content_type.split(";")[0].strip().lower()
|
|
91
|
+
main_type = content_type.split("/")[0] + "/*"
|
|
92
|
+
wildcard_type = "*/*"
|
|
93
|
+
|
|
94
|
+
for codec in self.decoders:
|
|
95
|
+
if codec.media_type in (content_type, main_type, wildcard_type):
|
|
96
|
+
return codec
|
|
97
|
+
|
|
98
|
+
text = (
|
|
99
|
+
"Unsupported encoding '%s' in response Content-Type header." % content_type
|
|
100
|
+
)
|
|
101
|
+
message = exceptions.ErrorMessage(text=text, code="cannot-decode-response")
|
|
102
|
+
raise exceptions.ClientError(messages=[message])
|
|
103
|
+
|
|
104
|
+
def get_request_options(self, query_params=None, content=None, encoding=None):
|
|
105
|
+
"""
|
|
106
|
+
Return the 'options' for sending the outgoing request.
|
|
107
|
+
"""
|
|
108
|
+
options = {
|
|
109
|
+
"headers": dict(self.headers),
|
|
110
|
+
"params": query_params,
|
|
111
|
+
"timeout": REQUEST_TIMEOUT,
|
|
112
|
+
"verify": self.verify,
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if content is not None:
|
|
116
|
+
assert (
|
|
117
|
+
encoding == "application/json"
|
|
118
|
+
), "Only JSON request bodies are supported"
|
|
119
|
+
options["json"] = content
|
|
120
|
+
|
|
121
|
+
return options
|
|
122
|
+
|
|
123
|
+
def decode_response_content(self, response):
|
|
124
|
+
"""
|
|
125
|
+
Given an HTTP response, return the decoded data.
|
|
126
|
+
"""
|
|
127
|
+
if not response.content:
|
|
128
|
+
return None
|
|
129
|
+
|
|
130
|
+
content_type = response.headers.get("content-type")
|
|
131
|
+
decoder = self.get_decoder(content_type)
|
|
132
|
+
return decoder.decode(response)
|
arkindex/compat.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
try:
|
|
3
|
+
# Ideally we subclass `_TemporaryFileWrapper` to present a clear __repr__
|
|
4
|
+
# for downloaded files.
|
|
5
|
+
from tempfile import _TemporaryFileWrapper
|
|
6
|
+
|
|
7
|
+
class DownloadedFile(_TemporaryFileWrapper):
|
|
8
|
+
basename = None
|
|
9
|
+
|
|
10
|
+
def __repr__(self):
|
|
11
|
+
state = "closed" if self.closed else "open"
|
|
12
|
+
mode = "" if self.closed else " '%s'" % self.file.mode
|
|
13
|
+
return "<DownloadedFile '%s', %s%s>" % (self.name, state, mode)
|
|
14
|
+
|
|
15
|
+
def __str__(self):
|
|
16
|
+
return self.__repr__()
|
|
17
|
+
|
|
18
|
+
except ImportError:
|
|
19
|
+
# On some platforms (eg GAE) the private _TemporaryFileWrapper may not be
|
|
20
|
+
# available, just use the standard `NamedTemporaryFile` function
|
|
21
|
+
# in this case.
|
|
22
|
+
import tempfile
|
|
23
|
+
|
|
24
|
+
DownloadedFile = tempfile.NamedTemporaryFile
|
arkindex/document.py
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import collections
|
|
3
|
+
import re
|
|
4
|
+
import typing
|
|
5
|
+
|
|
6
|
+
LinkInfo = collections.namedtuple("LinkInfo", ["link", "name", "sections"])
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Document:
|
|
10
|
+
def __init__(
|
|
11
|
+
self,
|
|
12
|
+
content: typing.Sequence[typing.Union["Section", "Link"]] = None,
|
|
13
|
+
url: str = "",
|
|
14
|
+
title: str = "",
|
|
15
|
+
description: str = "",
|
|
16
|
+
version: str = "",
|
|
17
|
+
):
|
|
18
|
+
content = [] if (content is None) else list(content)
|
|
19
|
+
|
|
20
|
+
# Ensure all names within a document are unique.
|
|
21
|
+
seen_fields = set()
|
|
22
|
+
seen_sections = set()
|
|
23
|
+
for item in content:
|
|
24
|
+
if isinstance(item, Link):
|
|
25
|
+
msg = 'Link "%s" in Document must have a unique name.'
|
|
26
|
+
assert item.name not in seen_fields, msg % item.name
|
|
27
|
+
seen_fields.add(item.name)
|
|
28
|
+
else:
|
|
29
|
+
msg = 'Section "%s" in Document must have a unique name.'
|
|
30
|
+
assert item.name not in seen_sections, msg % item.name
|
|
31
|
+
seen_sections.add(item.name)
|
|
32
|
+
|
|
33
|
+
self.content = content
|
|
34
|
+
self.url = url
|
|
35
|
+
self.title = title
|
|
36
|
+
self.description = description
|
|
37
|
+
self.version = version
|
|
38
|
+
|
|
39
|
+
def get_links(self):
|
|
40
|
+
return [item for item in self.content if isinstance(item, Link)]
|
|
41
|
+
|
|
42
|
+
def get_sections(self):
|
|
43
|
+
return [item for item in self.content if isinstance(item, Section)]
|
|
44
|
+
|
|
45
|
+
def walk_links(self):
|
|
46
|
+
link_info_list = []
|
|
47
|
+
for item in self.content:
|
|
48
|
+
if isinstance(item, Link):
|
|
49
|
+
link_info = LinkInfo(link=item, name=item.name, sections=())
|
|
50
|
+
link_info_list.append(link_info)
|
|
51
|
+
else:
|
|
52
|
+
link_info_list.extend(item.walk_links())
|
|
53
|
+
return link_info_list
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class Section:
|
|
57
|
+
def __init__(
|
|
58
|
+
self,
|
|
59
|
+
name: str,
|
|
60
|
+
content: typing.Sequence[typing.Union["Section", "Link"]] = None,
|
|
61
|
+
title: str = "",
|
|
62
|
+
description: str = "",
|
|
63
|
+
):
|
|
64
|
+
content = [] if (content is None) else list(content)
|
|
65
|
+
|
|
66
|
+
# Ensure all names within a section are unique.
|
|
67
|
+
seen_fields = set()
|
|
68
|
+
seen_sections = set()
|
|
69
|
+
for item in content:
|
|
70
|
+
if isinstance(item, Link):
|
|
71
|
+
msg = 'Link "%s" in Section "%s" must have a unique name.'
|
|
72
|
+
assert item.name not in seen_fields, msg % (item.name, name)
|
|
73
|
+
seen_fields.add(item.name)
|
|
74
|
+
else:
|
|
75
|
+
msg = 'Section "%s" in Section "%s" must have a unique name.'
|
|
76
|
+
assert item.name not in seen_sections, msg % (item.name, name)
|
|
77
|
+
seen_sections.add(item.name)
|
|
78
|
+
|
|
79
|
+
self.content = content
|
|
80
|
+
self.name = name
|
|
81
|
+
self.title = title
|
|
82
|
+
self.description = description
|
|
83
|
+
|
|
84
|
+
def get_links(self):
|
|
85
|
+
return [item for item in self.content if isinstance(item, Link)]
|
|
86
|
+
|
|
87
|
+
def get_sections(self):
|
|
88
|
+
return [item for item in self.content if isinstance(item, Section)]
|
|
89
|
+
|
|
90
|
+
def walk_links(self, previous_sections=()):
|
|
91
|
+
link_info_list = []
|
|
92
|
+
sections = previous_sections + (self,)
|
|
93
|
+
for item in self.content:
|
|
94
|
+
if isinstance(item, Link):
|
|
95
|
+
name = ":".join([section.name for section in sections] + [item.name])
|
|
96
|
+
link_info = LinkInfo(link=item, name=name, sections=sections)
|
|
97
|
+
link_info_list.append(link_info)
|
|
98
|
+
else:
|
|
99
|
+
link_info_list.extend(item.walk_links(previous_sections=sections))
|
|
100
|
+
return link_info_list
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class Link:
|
|
104
|
+
"""
|
|
105
|
+
Links represent the actions that a client may perform.
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
def __init__(
|
|
109
|
+
self,
|
|
110
|
+
url: str,
|
|
111
|
+
method: str,
|
|
112
|
+
handler: typing.Callable = None,
|
|
113
|
+
name: str = "",
|
|
114
|
+
encoding: str = "",
|
|
115
|
+
response: "Response" = None,
|
|
116
|
+
title: str = "",
|
|
117
|
+
description: str = "",
|
|
118
|
+
fields: typing.Sequence["Field"] = None,
|
|
119
|
+
):
|
|
120
|
+
method = method.upper()
|
|
121
|
+
fields = [] if (fields is None) else list(fields)
|
|
122
|
+
|
|
123
|
+
url_path_names = set(
|
|
124
|
+
[item.strip("{}").lstrip("+") for item in re.findall("{[^}]*}", url)]
|
|
125
|
+
)
|
|
126
|
+
path_fields = [field for field in fields if field.location == "path"]
|
|
127
|
+
body_fields = [field for field in fields if field.location == "body"]
|
|
128
|
+
|
|
129
|
+
assert method in (
|
|
130
|
+
"GET",
|
|
131
|
+
"POST",
|
|
132
|
+
"PUT",
|
|
133
|
+
"PATCH",
|
|
134
|
+
"DELETE",
|
|
135
|
+
"OPTIONS",
|
|
136
|
+
"HEAD",
|
|
137
|
+
"TRACE",
|
|
138
|
+
)
|
|
139
|
+
assert len(body_fields) < 2
|
|
140
|
+
if body_fields:
|
|
141
|
+
assert encoding
|
|
142
|
+
for field in path_fields:
|
|
143
|
+
assert field.name in url_path_names
|
|
144
|
+
|
|
145
|
+
# Add in path fields for any "{param}" items that don't already have
|
|
146
|
+
# a corresponding path field.
|
|
147
|
+
for path_name in url_path_names:
|
|
148
|
+
if path_name not in [field.name for field in path_fields]:
|
|
149
|
+
fields += [Field(name=path_name, location="path", required=True)]
|
|
150
|
+
|
|
151
|
+
self.url = url
|
|
152
|
+
self.method = method
|
|
153
|
+
self.handler = handler
|
|
154
|
+
self.name = name if name else handler.__name__
|
|
155
|
+
self.encoding = encoding
|
|
156
|
+
self.response = response
|
|
157
|
+
self.title = title
|
|
158
|
+
self.description = description
|
|
159
|
+
self.fields = fields
|
|
160
|
+
|
|
161
|
+
def get_path_fields(self):
|
|
162
|
+
return [field for field in self.fields if field.location == "path"]
|
|
163
|
+
|
|
164
|
+
def get_query_fields(self):
|
|
165
|
+
return [field for field in self.fields if field.location == "query"]
|
|
166
|
+
|
|
167
|
+
def get_body_field(self):
|
|
168
|
+
for field in self.fields:
|
|
169
|
+
if field.location == "body":
|
|
170
|
+
return field
|
|
171
|
+
return None
|
|
172
|
+
|
|
173
|
+
def get_expanded_body(self):
|
|
174
|
+
field = self.get_body_field()
|
|
175
|
+
if field is None or not hasattr(field.schema, "properties"):
|
|
176
|
+
return None
|
|
177
|
+
return field.schema.properties
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
class Field:
|
|
181
|
+
def __init__(
|
|
182
|
+
self,
|
|
183
|
+
name: str,
|
|
184
|
+
location: str,
|
|
185
|
+
title: str = "",
|
|
186
|
+
description: str = "",
|
|
187
|
+
required: bool = None,
|
|
188
|
+
schema: typing.Any = None,
|
|
189
|
+
example: typing.Any = None,
|
|
190
|
+
):
|
|
191
|
+
assert location in ("path", "query", "body", "cookie", "header", "formData")
|
|
192
|
+
if required is None:
|
|
193
|
+
required = True if location in ("path", "body") else False
|
|
194
|
+
if location == "path":
|
|
195
|
+
assert required, "May not set 'required=False' on path fields."
|
|
196
|
+
|
|
197
|
+
self.name = name
|
|
198
|
+
self.title = title
|
|
199
|
+
self.description = description
|
|
200
|
+
self.location = location
|
|
201
|
+
self.required = required
|
|
202
|
+
self.schema = schema
|
|
203
|
+
self.example = example
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
class Response:
|
|
207
|
+
def __init__(
|
|
208
|
+
self, encoding: str, status_code: int = 200, schema: typing.Any = None
|
|
209
|
+
):
|
|
210
|
+
self.encoding = encoding
|
|
211
|
+
self.status_code = status_code
|
|
212
|
+
self.schema = schema
|
arkindex/exceptions.py
CHANGED
|
@@ -1,4 +1,77 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
|
+
from collections import namedtuple
|
|
3
|
+
|
|
4
|
+
Position = namedtuple("Position", ["line_no", "column_no", "index"])
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ErrorMessage:
|
|
8
|
+
def __init__(self, text, code, index=None, position=None):
|
|
9
|
+
self.text = text
|
|
10
|
+
self.code = code
|
|
11
|
+
self.index = index
|
|
12
|
+
self.position = position
|
|
13
|
+
|
|
14
|
+
def __eq__(self, other):
|
|
15
|
+
return (
|
|
16
|
+
self.text == other.text
|
|
17
|
+
and self.code == other.code
|
|
18
|
+
and self.index == other.index
|
|
19
|
+
and self.position == other.position
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
def __repr__(self):
|
|
23
|
+
return "%s(%s, code=%s, index=%s, position=%s)" % (
|
|
24
|
+
self.__class__.__name__,
|
|
25
|
+
repr(self.text),
|
|
26
|
+
repr(self.code),
|
|
27
|
+
repr(self.index),
|
|
28
|
+
repr(self.position),
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class ErrorResponse(Exception):
|
|
33
|
+
"""
|
|
34
|
+
Raised when a client request results in an error response being returned.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(self, title, status_code, content):
|
|
38
|
+
self.title = title
|
|
39
|
+
self.status_code = status_code
|
|
40
|
+
self.content = content
|
|
41
|
+
|
|
42
|
+
def __repr__(self):
|
|
43
|
+
return "%s(%s, status_code=%s, content=%s)" % (
|
|
44
|
+
self.__class__.__name__,
|
|
45
|
+
repr(self.title),
|
|
46
|
+
repr(self.status_code),
|
|
47
|
+
repr(self.content),
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
def __str__(self):
|
|
51
|
+
return repr(self.content)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class ClientError(Exception):
|
|
55
|
+
"""
|
|
56
|
+
Raised when a client is unable to fulfil an API request.
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
def __init__(self, messages):
|
|
60
|
+
self.messages = messages
|
|
61
|
+
super().__init__(messages)
|
|
62
|
+
|
|
63
|
+
def __repr__(self):
|
|
64
|
+
return "%s(messages=%s)" % (
|
|
65
|
+
self.__class__.__name__,
|
|
66
|
+
repr(self.messages),
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
def __str__(self):
|
|
70
|
+
if len(self.messages) == 1 and not self.messages[0].index:
|
|
71
|
+
return self.messages[0].text
|
|
72
|
+
return str(self.messages)
|
|
73
|
+
|
|
74
|
+
|
|
2
75
|
class SchemaError(Exception):
|
|
3
76
|
"""
|
|
4
77
|
Any error occurring during the acquisition and processing of the OpenAPI schema.
|
arkindex/mock.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import collections
|
|
3
3
|
import logging
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
from arkindex.exceptions import ErrorResponse
|
|
6
6
|
|
|
7
7
|
logger = logging.getLogger(__name__)
|
|
8
8
|
|
|
@@ -36,7 +36,7 @@ class MockApiClient(object):
|
|
|
36
36
|
):
|
|
37
37
|
"""Store a new mock error response for a request on an endpoint"""
|
|
38
38
|
request = MockRequest(operation_id, body, args, kwargs)
|
|
39
|
-
error =
|
|
39
|
+
error = ErrorResponse(
|
|
40
40
|
title=title,
|
|
41
41
|
status_code=status_code,
|
|
42
42
|
content=content,
|
|
@@ -74,7 +74,7 @@ class MockApiClient(object):
|
|
|
74
74
|
logger.error(
|
|
75
75
|
f"No mock response found for {operation_id} with body={body} args={args} kwargs={kwargs}"
|
|
76
76
|
)
|
|
77
|
-
raise
|
|
77
|
+
raise ErrorResponse(
|
|
78
78
|
title="No mock response found",
|
|
79
79
|
status_code=400,
|
|
80
80
|
content="No mock response found",
|
arkindex/pagination.py
CHANGED
|
@@ -7,9 +7,10 @@ from collections.abc import Iterator, Sized
|
|
|
7
7
|
from enum import Enum
|
|
8
8
|
from urllib.parse import parse_qs, urlsplit
|
|
9
9
|
|
|
10
|
-
import apistar
|
|
11
10
|
import requests
|
|
12
11
|
|
|
12
|
+
from arkindex.exceptions import ErrorResponse
|
|
13
|
+
|
|
13
14
|
logger = logging.getLogger(__name__)
|
|
14
15
|
|
|
15
16
|
|
|
@@ -28,12 +29,12 @@ class ResponsePaginator(Sized, Iterator):
|
|
|
28
29
|
|
|
29
30
|
def __init__(self, client, operation_id, *request_args, **request_kwargs):
|
|
30
31
|
r"""
|
|
31
|
-
:param client
|
|
32
|
-
:param \*request_args: Arguments to send to :meth:`
|
|
33
|
-
:param \**request_kwargs: Keyword arguments to send to :meth:`
|
|
32
|
+
:param client arkindex.ArkindexClient: An API client to use to perform requests for each page.
|
|
33
|
+
:param \*request_args: Arguments to send to :meth:`arkindex.ArkindexClient.request`.
|
|
34
|
+
:param \**request_kwargs: Keyword arguments to send to :meth:`arkindex.ArkindexClient.request`.
|
|
34
35
|
"""
|
|
35
36
|
self.client = client
|
|
36
|
-
"""The
|
|
37
|
+
"""The API client used to perform requests on each page."""
|
|
37
38
|
|
|
38
39
|
self.data = {}
|
|
39
40
|
"""Stored data from the last performed request."""
|
|
@@ -45,10 +46,10 @@ class ResponsePaginator(Sized, Iterator):
|
|
|
45
46
|
"""Client operation"""
|
|
46
47
|
|
|
47
48
|
self.request_args = request_args
|
|
48
|
-
"""Arguments to send to :meth:`
|
|
49
|
+
"""Arguments to send to :meth:`arkindex.ArkindexClient.request` with each request."""
|
|
49
50
|
|
|
50
51
|
self.request_kwargs = request_kwargs
|
|
51
|
-
"""Keyword arguments to send to :meth:`
|
|
52
|
+
"""Keyword arguments to send to :meth:`arkindex.ArkindexClient.request` with each request."""
|
|
52
53
|
|
|
53
54
|
self.mode = None
|
|
54
55
|
"""`page` for PageNumberPagination endpoints or `cursor` for CursorPagination endpoints."""
|
|
@@ -172,7 +173,7 @@ class ResponsePaginator(Sized, Iterator):
|
|
|
172
173
|
self.pages_loaded += 1
|
|
173
174
|
return True
|
|
174
175
|
|
|
175
|
-
except
|
|
176
|
+
except ErrorResponse as e:
|
|
176
177
|
logger.warning(f"API Error {e.status_code} on pagination: {e.content}")
|
|
177
178
|
|
|
178
179
|
# Decrement pages counter
|
|
File without changes
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import typesystem
|
|
3
|
+
|
|
4
|
+
definitions = typesystem.SchemaDefinitions()
|
|
5
|
+
|
|
6
|
+
JSON_SCHEMA = (
|
|
7
|
+
typesystem.Object(
|
|
8
|
+
properties={
|
|
9
|
+
"$ref": typesystem.String(),
|
|
10
|
+
"type": typesystem.String() | typesystem.Array(items=typesystem.String()),
|
|
11
|
+
"enum": typesystem.Array(unique_items=True, min_items=1),
|
|
12
|
+
"definitions": typesystem.Object(
|
|
13
|
+
additional_properties=typesystem.Reference(
|
|
14
|
+
"JSONSchema", definitions=definitions
|
|
15
|
+
)
|
|
16
|
+
),
|
|
17
|
+
# String
|
|
18
|
+
"minLength": typesystem.Integer(minimum=0),
|
|
19
|
+
"maxLength": typesystem.Integer(minimum=0),
|
|
20
|
+
"pattern": typesystem.String(format="regex"),
|
|
21
|
+
"format": typesystem.String(),
|
|
22
|
+
# Numeric
|
|
23
|
+
"minimum": typesystem.Number(),
|
|
24
|
+
"maximum": typesystem.Number(),
|
|
25
|
+
"exclusiveMinimum": typesystem.Number(),
|
|
26
|
+
"exclusiveMaximum": typesystem.Number(),
|
|
27
|
+
"multipleOf": typesystem.Number(exclusive_minimum=0),
|
|
28
|
+
# Object
|
|
29
|
+
"properties": typesystem.Object(
|
|
30
|
+
additional_properties=typesystem.Reference(
|
|
31
|
+
"JSONSchema", definitions=definitions
|
|
32
|
+
)
|
|
33
|
+
),
|
|
34
|
+
"minProperties": typesystem.Integer(minimum=0),
|
|
35
|
+
"maxProperties": typesystem.Integer(minimum=0),
|
|
36
|
+
"patternProperties": typesystem.Object(
|
|
37
|
+
additional_properties=typesystem.Reference(
|
|
38
|
+
"JSONSchema", definitions=definitions
|
|
39
|
+
)
|
|
40
|
+
),
|
|
41
|
+
"additionalProperties": (
|
|
42
|
+
typesystem.Reference("JSONSchema", definitions=definitions)
|
|
43
|
+
| typesystem.Boolean()
|
|
44
|
+
),
|
|
45
|
+
"required": typesystem.Array(items=typesystem.String(), unique_items=True),
|
|
46
|
+
# Array
|
|
47
|
+
"items": (
|
|
48
|
+
typesystem.Reference("JSONSchema", definitions=definitions)
|
|
49
|
+
| typesystem.Array(
|
|
50
|
+
items=typesystem.Reference("JSONSchema", definitions=definitions),
|
|
51
|
+
min_items=1,
|
|
52
|
+
)
|
|
53
|
+
),
|
|
54
|
+
"additionalItems": (
|
|
55
|
+
typesystem.Reference("JSONSchema", definitions=definitions)
|
|
56
|
+
| typesystem.Boolean()
|
|
57
|
+
),
|
|
58
|
+
"minItems": typesystem.Integer(minimum=0),
|
|
59
|
+
"maxItems": typesystem.Integer(minimum=0),
|
|
60
|
+
"uniqueItems": typesystem.Boolean(),
|
|
61
|
+
}
|
|
62
|
+
)
|
|
63
|
+
| typesystem.Boolean()
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
definitions["JSONSchema"] = JSON_SCHEMA
|