arkindex-client 1.1.1__py3-none-any.whl → 1.1.3__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.
- arkindex/client/client.py +187 -79
- arkindex/client/decoders.py +5 -4
- arkindex/document.py +21 -105
- arkindex/pagination.py +79 -37
- arkindex/schema/openapi.py +44 -292
- arkindex/schema/validator.py +3 -35
- {arkindex_client-1.1.1.dist-info → arkindex_client-1.1.3.dist-info}/METADATA +2 -3
- arkindex_client-1.1.3.dist-info/RECORD +20 -0
- {arkindex_client-1.1.1.dist-info → arkindex_client-1.1.3.dist-info}/WHEEL +1 -1
- arkindex/client/base.py +0 -98
- arkindex/client/transports.py +0 -132
- arkindex/schema/jsonschema.py +0 -66
- arkindex_client-1.1.1.dist-info/RECORD +0 -23
- {arkindex_client-1.1.1.dist-info → arkindex_client-1.1.3.dist-info}/LICENSE +0 -0
- {arkindex_client-1.1.1.dist-info → arkindex_client-1.1.3.dist-info}/top_level.txt +0 -0
arkindex/pagination.py
CHANGED
|
@@ -51,9 +51,6 @@ class ResponsePaginator(Sized, Iterator):
|
|
|
51
51
|
self.request_kwargs = request_kwargs
|
|
52
52
|
"""Keyword arguments to send to :meth:`arkindex.ArkindexClient.request` with each request."""
|
|
53
53
|
|
|
54
|
-
self.mode = None
|
|
55
|
-
"""`page` for PageNumberPagination endpoints or `cursor` for CursorPagination endpoints."""
|
|
56
|
-
|
|
57
54
|
self.count = None
|
|
58
55
|
"""Total results count."""
|
|
59
56
|
|
|
@@ -69,8 +66,26 @@ class ResponsePaginator(Sized, Iterator):
|
|
|
69
66
|
), "retries must be a positive integer"
|
|
70
67
|
"""Max number of retries per API request"""
|
|
71
68
|
|
|
72
|
-
#
|
|
73
|
-
self.
|
|
69
|
+
# Detect and store the pagination mode
|
|
70
|
+
self.mode = None
|
|
71
|
+
if any(
|
|
72
|
+
field.name == "cursor"
|
|
73
|
+
for field in self.client.lookup_operation(self.operation_id).fields
|
|
74
|
+
):
|
|
75
|
+
self.mode = PaginationMode.Cursor
|
|
76
|
+
elif any(
|
|
77
|
+
field.name == "page"
|
|
78
|
+
for field in self.client.lookup_operation(self.operation_id).fields
|
|
79
|
+
):
|
|
80
|
+
self.mode = PaginationMode.PageNumber
|
|
81
|
+
if not self.mode:
|
|
82
|
+
raise NotImplementedError(
|
|
83
|
+
"Pagination only implements page and cursor modes."
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
# First page key is an empty string by default (to stay coherent with page or cursor modes)
|
|
87
|
+
self.initial_page = request_kwargs.get(self.mode.value, "")
|
|
88
|
+
|
|
74
89
|
# Store retrieved pages remaining retries
|
|
75
90
|
self.pages = {self.initial_page: self.retries}
|
|
76
91
|
|
|
@@ -90,9 +105,8 @@ class ResponsePaginator(Sized, Iterator):
|
|
|
90
105
|
Returns False in case the page returned an empty result
|
|
91
106
|
Raises a StopIteration in case there are no pages left to iterate on
|
|
92
107
|
"""
|
|
93
|
-
# Filter out pages with no retries
|
|
94
108
|
# Transform as a list of tuples for simpler output
|
|
95
|
-
remaining =
|
|
109
|
+
remaining = [(m, v) for m, v in self.pages.items()]
|
|
96
110
|
|
|
97
111
|
# No remaining pages, end of iteration
|
|
98
112
|
if not remaining:
|
|
@@ -101,28 +115,53 @@ class ResponsePaginator(Sized, Iterator):
|
|
|
101
115
|
# Get next page to load
|
|
102
116
|
index, retry = remaining[0]
|
|
103
117
|
|
|
104
|
-
if
|
|
118
|
+
if index:
|
|
105
119
|
self.request_kwargs[self.mode.value] = index
|
|
106
120
|
|
|
107
121
|
try:
|
|
108
122
|
extra_kwargs = {}
|
|
109
123
|
if not self.pages_loaded:
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
124
|
+
if (
|
|
125
|
+
self.mode == PaginationMode.PageNumber
|
|
126
|
+
and self.initial_page
|
|
127
|
+
and int(self.initial_page) > 1
|
|
128
|
+
) or (self.mode == PaginationMode.Cursor and self.initial_page):
|
|
129
|
+
logger.info(
|
|
130
|
+
f"Loading page {self.initial_page} on try {self.retries - retry + 1}/{self.retries}"
|
|
131
|
+
)
|
|
132
|
+
else:
|
|
133
|
+
logger.info(
|
|
134
|
+
f"Loading first page on try {self.retries - retry + 1}/{self.retries}"
|
|
135
|
+
)
|
|
113
136
|
operation_fields = [
|
|
114
137
|
f.name
|
|
115
138
|
for f in self.client.lookup_operation(self.operation_id).fields
|
|
116
139
|
]
|
|
117
|
-
# Ask to count results if the operation handle it
|
|
140
|
+
# Ask to count results if the operation handle it (this is usually the case with cursors)
|
|
118
141
|
if "with_count" in operation_fields:
|
|
119
|
-
extra_kwargs
|
|
142
|
+
extra_kwargs = {
|
|
143
|
+
"with_count": "true",
|
|
144
|
+
**extra_kwargs,
|
|
145
|
+
}
|
|
120
146
|
else:
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
147
|
+
message = f"Loading {self.mode.value} {index} on try {self.retries - retry + 1}/{self.retries}"
|
|
148
|
+
if self.pages_count is not None:
|
|
149
|
+
if self.mode is PaginationMode.Cursor and self.initial_page:
|
|
150
|
+
# The number of remaining pages is unknown when an initial cursor is set
|
|
151
|
+
max_pages = self.pages_count - self.pages_loaded
|
|
152
|
+
message = message + (
|
|
153
|
+
f" - remains a maximum of {max_pages} page{'s' if max_pages > 1 else ''} to load."
|
|
154
|
+
)
|
|
155
|
+
else:
|
|
156
|
+
initial = int(self.initial_page) if self.initial_page else 1
|
|
157
|
+
remaining_count = (
|
|
158
|
+
self.pages_count - self.pages_loaded - (initial - 1)
|
|
159
|
+
)
|
|
160
|
+
message = message + (
|
|
161
|
+
f" - remains {remaining_count} page{'s' if remaining_count > 1 else ''} to load."
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
logger.info(message)
|
|
126
165
|
|
|
127
166
|
# Fetch the next page
|
|
128
167
|
self.data = self.client.single_request(
|
|
@@ -133,33 +172,32 @@ class ResponsePaginator(Sized, Iterator):
|
|
|
133
172
|
)
|
|
134
173
|
self.results = self.data.get("results", [])
|
|
135
174
|
|
|
136
|
-
|
|
137
|
-
# Autodetect if this endpoint uses page or cursor pagination
|
|
138
|
-
if self.data.get("number"):
|
|
139
|
-
self.mode = PaginationMode.PageNumber
|
|
140
|
-
else:
|
|
141
|
-
self.mode = PaginationMode.Cursor
|
|
142
|
-
|
|
175
|
+
# Retrieve information on the first page with results count
|
|
143
176
|
if self.count is None and "count" in self.data:
|
|
144
|
-
# Retrieve information on first page with results count
|
|
145
177
|
self.count = self.data["count"]
|
|
146
178
|
if self.count == 0:
|
|
147
179
|
# Pagination has retrieved 0 results
|
|
148
180
|
self.pages = {}
|
|
149
181
|
return False
|
|
150
182
|
self.pages_count = math.ceil(self.count / len(self.results))
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
183
|
+
if self.mode == PaginationMode.Cursor:
|
|
184
|
+
logger.info(
|
|
185
|
+
f"Pagination will load a {'maximum' if self.initial_page else 'total'} "
|
|
186
|
+
f"of {self.pages_count} page{'s' if self.pages_count > 1 else ''}"
|
|
187
|
+
)
|
|
188
|
+
elif self.mode == PaginationMode.PageNumber:
|
|
189
|
+
initial = int(self.initial_page) if self.initial_page else 1
|
|
190
|
+
total = self.pages_count - initial + 1
|
|
191
|
+
logger.info(
|
|
192
|
+
f"Pagination will load a total of {total} page{'s' if total > 1 else ''}."
|
|
193
|
+
)
|
|
155
194
|
# Initialize all pages once
|
|
156
|
-
self.pages
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
195
|
+
self.pages.update(
|
|
196
|
+
{
|
|
197
|
+
i: self.retries
|
|
198
|
+
for i in range(initial + 1, self.pages_count + 1)
|
|
199
|
+
}
|
|
200
|
+
)
|
|
163
201
|
if self.mode == PaginationMode.Cursor:
|
|
164
202
|
# Parse next URL to retrieve the cursor of the next page
|
|
165
203
|
query = urlsplit(self.data["next"]).query
|
|
@@ -168,6 +206,9 @@ class ResponsePaginator(Sized, Iterator):
|
|
|
168
206
|
self.pages = {}
|
|
169
207
|
else:
|
|
170
208
|
self.pages = {cursor_query[0]: self.retries}
|
|
209
|
+
elif self.mode == PaginationMode.PageNumber:
|
|
210
|
+
# Mark the current page as loaded
|
|
211
|
+
del self.pages[index]
|
|
171
212
|
|
|
172
213
|
# Stop happy path here, we don't need to process errors
|
|
173
214
|
self.pages_loaded += 1
|
|
@@ -201,6 +242,7 @@ class ResponsePaginator(Sized, Iterator):
|
|
|
201
242
|
logger.warning(error_text)
|
|
202
243
|
if self.allow_missing_data:
|
|
203
244
|
self.missing.add(index)
|
|
245
|
+
del self.pages[index]
|
|
204
246
|
else:
|
|
205
247
|
raise Exception("Stopping pagination as data will be incomplete")
|
|
206
248
|
|
arkindex/schema/openapi.py
CHANGED
|
@@ -3,9 +3,9 @@ import re
|
|
|
3
3
|
from urllib.parse import urljoin
|
|
4
4
|
|
|
5
5
|
import typesystem
|
|
6
|
+
from typesystem.json_schema import JSONSchema
|
|
6
7
|
|
|
7
|
-
from arkindex.document import Document, Field, Link
|
|
8
|
-
from arkindex.schema.jsonschema import JSON_SCHEMA
|
|
8
|
+
from arkindex.document import Document, Field, Link
|
|
9
9
|
|
|
10
10
|
SCHEMA_REF = typesystem.Object(
|
|
11
11
|
properties={"$ref": typesystem.String(pattern="^#/components/schemas/")}
|
|
@@ -13,92 +13,27 @@ SCHEMA_REF = typesystem.Object(
|
|
|
13
13
|
REQUESTBODY_REF = typesystem.Object(
|
|
14
14
|
properties={"$ref": typesystem.String(pattern="^#/components/requestBodies/")}
|
|
15
15
|
)
|
|
16
|
-
RESPONSE_REF = typesystem.Object(
|
|
17
|
-
properties={"$ref": typesystem.String(pattern="^#/components/responses/")}
|
|
18
|
-
)
|
|
19
16
|
|
|
20
|
-
definitions = typesystem.
|
|
17
|
+
definitions = typesystem.Definitions()
|
|
21
18
|
|
|
22
|
-
OPEN_API = typesystem.
|
|
23
|
-
|
|
24
|
-
properties={
|
|
19
|
+
OPEN_API = typesystem.Schema(
|
|
20
|
+
fields={
|
|
25
21
|
"openapi": typesystem.String(),
|
|
26
|
-
"info": typesystem.Reference("Info", definitions=definitions),
|
|
27
22
|
"servers": typesystem.Array(
|
|
28
|
-
items=typesystem.Reference("Server", definitions=definitions)
|
|
23
|
+
items=typesystem.Reference("Server", definitions=definitions),
|
|
24
|
+
default=[],
|
|
29
25
|
),
|
|
30
26
|
"paths": typesystem.Reference("Paths", definitions=definitions),
|
|
31
|
-
"components": typesystem.Reference(
|
|
32
|
-
|
|
33
|
-
items=typesystem.Reference("SecurityRequirement", definitions=definitions)
|
|
34
|
-
),
|
|
35
|
-
"tags": typesystem.Array(
|
|
36
|
-
items=typesystem.Reference("Tag", definitions=definitions)
|
|
27
|
+
"components": typesystem.Reference(
|
|
28
|
+
"Components", default=None, definitions=definitions
|
|
37
29
|
),
|
|
38
|
-
"externalDocs": typesystem.Reference(
|
|
39
|
-
"ExternalDocumentation", definitions=definitions
|
|
40
|
-
),
|
|
41
|
-
},
|
|
42
|
-
pattern_properties={"^x-": typesystem.Any()},
|
|
43
|
-
additional_properties=False,
|
|
44
|
-
required=["openapi", "info", "paths"],
|
|
45
|
-
)
|
|
46
|
-
|
|
47
|
-
definitions["Info"] = typesystem.Object(
|
|
48
|
-
properties={
|
|
49
|
-
"title": typesystem.String(allow_blank=True),
|
|
50
|
-
"description": typesystem.Text(allow_blank=True),
|
|
51
|
-
"termsOfService": typesystem.String(format="url"),
|
|
52
|
-
"contact": typesystem.Reference("Contact", definitions=definitions),
|
|
53
|
-
"license": typesystem.Reference("License", definitions=definitions),
|
|
54
|
-
"version": typesystem.String(allow_blank=True),
|
|
55
|
-
},
|
|
56
|
-
pattern_properties={"^x-": typesystem.Any()},
|
|
57
|
-
additional_properties=False,
|
|
58
|
-
required=["title", "version"],
|
|
59
|
-
)
|
|
60
|
-
|
|
61
|
-
definitions["Contact"] = typesystem.Object(
|
|
62
|
-
properties={
|
|
63
|
-
"name": typesystem.String(allow_blank=True),
|
|
64
|
-
"url": typesystem.String(format="url"),
|
|
65
|
-
"email": typesystem.String(format="email"),
|
|
66
30
|
},
|
|
67
|
-
pattern_properties={"^x-": typesystem.Any()},
|
|
68
|
-
additional_properties=False,
|
|
69
31
|
)
|
|
70
32
|
|
|
71
|
-
definitions["
|
|
72
|
-
|
|
73
|
-
required=["name"],
|
|
74
|
-
pattern_properties={"^x-": typesystem.Any()},
|
|
75
|
-
additional_properties=False,
|
|
76
|
-
)
|
|
77
|
-
|
|
78
|
-
definitions["Server"] = typesystem.Object(
|
|
79
|
-
properties={
|
|
33
|
+
definitions["Server"] = typesystem.Schema(
|
|
34
|
+
fields={
|
|
80
35
|
"url": typesystem.String(),
|
|
81
|
-
"description": typesystem.Text(allow_blank=True),
|
|
82
|
-
"variables": typesystem.Object(
|
|
83
|
-
additional_properties=typesystem.Reference(
|
|
84
|
-
"ServerVariable", definitions=definitions
|
|
85
|
-
)
|
|
86
|
-
),
|
|
87
|
-
},
|
|
88
|
-
pattern_properties={"^x-": typesystem.Any()},
|
|
89
|
-
additional_properties=False,
|
|
90
|
-
required=["url"],
|
|
91
|
-
)
|
|
92
|
-
|
|
93
|
-
definitions["ServerVariable"] = typesystem.Object(
|
|
94
|
-
properties={
|
|
95
|
-
"enum": typesystem.Array(items=typesystem.String()),
|
|
96
|
-
"default": typesystem.String(),
|
|
97
|
-
"description": typesystem.Text(allow_blank=True),
|
|
98
36
|
},
|
|
99
|
-
pattern_properties={"^x-": typesystem.Any()},
|
|
100
|
-
additional_properties=False,
|
|
101
|
-
required=["default"],
|
|
102
37
|
)
|
|
103
38
|
|
|
104
39
|
definitions["Paths"] = typesystem.Object(
|
|
@@ -112,7 +47,6 @@ definitions["Paths"] = typesystem.Object(
|
|
|
112
47
|
definitions["Path"] = typesystem.Object(
|
|
113
48
|
properties={
|
|
114
49
|
"summary": typesystem.String(allow_blank=True),
|
|
115
|
-
"description": typesystem.Text(allow_blank=True),
|
|
116
50
|
"get": typesystem.Reference("Operation", definitions=definitions),
|
|
117
51
|
"put": typesystem.Reference("Operation", definitions=definitions),
|
|
118
52
|
"post": typesystem.Reference("Operation", definitions=definitions),
|
|
@@ -129,18 +63,11 @@ definitions["Path"] = typesystem.Object(
|
|
|
129
63
|
),
|
|
130
64
|
# TODO: | ReferenceObject
|
|
131
65
|
},
|
|
132
|
-
|
|
133
|
-
additional_properties=False,
|
|
66
|
+
additional_properties=True,
|
|
134
67
|
)
|
|
135
68
|
|
|
136
69
|
definitions["Operation"] = typesystem.Object(
|
|
137
70
|
properties={
|
|
138
|
-
"tags": typesystem.Array(items=typesystem.String()),
|
|
139
|
-
"summary": typesystem.String(allow_blank=True),
|
|
140
|
-
"description": typesystem.Text(allow_blank=True),
|
|
141
|
-
"externalDocs": typesystem.Reference(
|
|
142
|
-
"ExternalDocumentation", definitions=definitions
|
|
143
|
-
),
|
|
144
71
|
"operationId": typesystem.String(),
|
|
145
72
|
"parameters": typesystem.Array(
|
|
146
73
|
items=typesystem.Reference("Parameter", definitions=definitions)
|
|
@@ -149,61 +76,29 @@ definitions["Operation"] = typesystem.Object(
|
|
|
149
76
|
| typesystem.Reference(
|
|
150
77
|
"RequestBody", definitions=definitions
|
|
151
78
|
), # TODO: RequestBody | ReferenceObject
|
|
152
|
-
"responses": typesystem.Reference("Responses", definitions=definitions),
|
|
153
|
-
# TODO: 'callbacks'
|
|
154
79
|
"deprecated": typesystem.Boolean(),
|
|
155
|
-
"security": typesystem.Array(
|
|
156
|
-
typesystem.Reference("SecurityRequirement", definitions=definitions)
|
|
157
|
-
),
|
|
158
80
|
"servers": typesystem.Array(
|
|
159
81
|
items=typesystem.Reference("Server", definitions=definitions)
|
|
160
82
|
),
|
|
83
|
+
# A custom property added by Arkindex to provide a hint to the API client
|
|
84
|
+
# that this endpoint is paginated, which changes the behavior of ArkindexClient.paginate.
|
|
85
|
+
"x-paginated": typesystem.Boolean(default=None),
|
|
161
86
|
},
|
|
162
|
-
|
|
163
|
-
additional_properties=False,
|
|
164
|
-
)
|
|
165
|
-
|
|
166
|
-
definitions["ExternalDocumentation"] = typesystem.Object(
|
|
167
|
-
properties={
|
|
168
|
-
"description": typesystem.Text(allow_blank=True),
|
|
169
|
-
"url": typesystem.String(format="url"),
|
|
170
|
-
},
|
|
171
|
-
pattern_properties={"^x-": typesystem.Any()},
|
|
172
|
-
additional_properties=False,
|
|
173
|
-
required=["url"],
|
|
87
|
+
additional_properties=True,
|
|
174
88
|
)
|
|
175
89
|
|
|
176
|
-
definitions["Parameter"] = typesystem.
|
|
177
|
-
|
|
90
|
+
definitions["Parameter"] = typesystem.Schema(
|
|
91
|
+
fields={
|
|
178
92
|
"name": typesystem.String(),
|
|
179
93
|
"in": typesystem.Choice(choices=["query", "header", "path", "cookie"]),
|
|
180
|
-
"
|
|
181
|
-
"
|
|
182
|
-
"
|
|
183
|
-
"allowEmptyValue": typesystem.Boolean(),
|
|
184
|
-
"style": typesystem.Choice(
|
|
185
|
-
choices=[
|
|
186
|
-
"matrix",
|
|
187
|
-
"label",
|
|
188
|
-
"form",
|
|
189
|
-
"simple",
|
|
190
|
-
"spaceDelimited",
|
|
191
|
-
"pipeDelimited",
|
|
192
|
-
"deepObject",
|
|
193
|
-
]
|
|
194
|
-
),
|
|
195
|
-
"schema": JSON_SCHEMA | SCHEMA_REF,
|
|
196
|
-
"example": typesystem.Any(),
|
|
197
|
-
# TODO: Other fields
|
|
94
|
+
"required": typesystem.Boolean(default=False),
|
|
95
|
+
"deprecated": typesystem.Boolean(default=False),
|
|
96
|
+
"schema": JSONSchema | SCHEMA_REF,
|
|
198
97
|
},
|
|
199
|
-
pattern_properties={"^x-": typesystem.Any()},
|
|
200
|
-
additional_properties=False,
|
|
201
|
-
required=["name", "in"],
|
|
202
98
|
)
|
|
203
99
|
|
|
204
100
|
definitions["RequestBody"] = typesystem.Object(
|
|
205
101
|
properties={
|
|
206
|
-
"description": typesystem.String(allow_blank=True),
|
|
207
102
|
"content": typesystem.Object(
|
|
208
103
|
additional_properties=typesystem.Reference(
|
|
209
104
|
"MediaType", definitions=definitions
|
|
@@ -211,88 +106,19 @@ definitions["RequestBody"] = typesystem.Object(
|
|
|
211
106
|
),
|
|
212
107
|
"required": typesystem.Boolean(),
|
|
213
108
|
},
|
|
214
|
-
|
|
215
|
-
additional_properties=False,
|
|
216
|
-
)
|
|
217
|
-
|
|
218
|
-
definitions["Responses"] = typesystem.Object(
|
|
219
|
-
properties={
|
|
220
|
-
"default": typesystem.Reference("Response", definitions=definitions)
|
|
221
|
-
| RESPONSE_REF
|
|
222
|
-
},
|
|
223
|
-
pattern_properties={
|
|
224
|
-
"^([1-5][0-9][0-9]|[1-5]XX)$": typesystem.Reference(
|
|
225
|
-
"Response", definitions=definitions
|
|
226
|
-
)
|
|
227
|
-
| RESPONSE_REF,
|
|
228
|
-
"^x-": typesystem.Any(),
|
|
229
|
-
},
|
|
230
|
-
additional_properties=False,
|
|
231
|
-
)
|
|
232
|
-
|
|
233
|
-
definitions["Response"] = typesystem.Object(
|
|
234
|
-
properties={
|
|
235
|
-
"description": typesystem.String(allow_blank=True),
|
|
236
|
-
"content": typesystem.Object(
|
|
237
|
-
additional_properties=typesystem.Reference(
|
|
238
|
-
"MediaType", definitions=definitions
|
|
239
|
-
)
|
|
240
|
-
),
|
|
241
|
-
"headers": typesystem.Object(
|
|
242
|
-
additional_properties=typesystem.Reference(
|
|
243
|
-
"Header", definitions=definitions
|
|
244
|
-
)
|
|
245
|
-
),
|
|
246
|
-
# TODO: Header | ReferenceObject
|
|
247
|
-
# TODO: links
|
|
248
|
-
},
|
|
249
|
-
pattern_properties={"^x-": typesystem.Any()},
|
|
250
|
-
additional_properties=False,
|
|
109
|
+
additional_properties=True,
|
|
251
110
|
)
|
|
252
111
|
|
|
253
112
|
definitions["MediaType"] = typesystem.Object(
|
|
254
113
|
properties={
|
|
255
|
-
"schema":
|
|
256
|
-
"example": typesystem.Any(),
|
|
257
|
-
# TODO 'examples', 'encoding'
|
|
114
|
+
"schema": JSONSchema | SCHEMA_REF,
|
|
258
115
|
},
|
|
259
|
-
|
|
260
|
-
additional_properties=False,
|
|
261
|
-
)
|
|
262
|
-
|
|
263
|
-
definitions["Header"] = typesystem.Object(
|
|
264
|
-
properties={
|
|
265
|
-
"description": typesystem.Text(),
|
|
266
|
-
"required": typesystem.Boolean(),
|
|
267
|
-
"deprecated": typesystem.Boolean(),
|
|
268
|
-
"allowEmptyValue": typesystem.Boolean(),
|
|
269
|
-
"style": typesystem.Choice(
|
|
270
|
-
choices=[
|
|
271
|
-
"matrix",
|
|
272
|
-
"label",
|
|
273
|
-
"form",
|
|
274
|
-
"simple",
|
|
275
|
-
"spaceDelimited",
|
|
276
|
-
"pipeDelimited",
|
|
277
|
-
"deepObject",
|
|
278
|
-
]
|
|
279
|
-
),
|
|
280
|
-
"schema": JSON_SCHEMA | SCHEMA_REF,
|
|
281
|
-
"example": typesystem.Any(),
|
|
282
|
-
# TODO: Other fields
|
|
283
|
-
},
|
|
284
|
-
pattern_properties={"^x-": typesystem.Any()},
|
|
285
|
-
additional_properties=False,
|
|
116
|
+
additional_properties=True,
|
|
286
117
|
)
|
|
287
118
|
|
|
288
119
|
definitions["Components"] = typesystem.Object(
|
|
289
120
|
properties={
|
|
290
|
-
"schemas": typesystem.Object(additional_properties=
|
|
291
|
-
"responses": typesystem.Object(
|
|
292
|
-
additional_properties=typesystem.Reference(
|
|
293
|
-
"Response", definitions=definitions
|
|
294
|
-
)
|
|
295
|
-
),
|
|
121
|
+
"schemas": typesystem.Object(additional_properties=JSONSchema),
|
|
296
122
|
"parameters": typesystem.Object(
|
|
297
123
|
additional_properties=typesystem.Reference(
|
|
298
124
|
"Parameter", definitions=definitions
|
|
@@ -303,53 +129,10 @@ definitions["Components"] = typesystem.Object(
|
|
|
303
129
|
"RequestBody", definitions=definitions
|
|
304
130
|
)
|
|
305
131
|
),
|
|
306
|
-
"securitySchemes": typesystem.Object(
|
|
307
|
-
additional_properties=typesystem.Reference(
|
|
308
|
-
"SecurityScheme", definitions=definitions
|
|
309
|
-
)
|
|
310
|
-
),
|
|
311
|
-
# TODO: Other fields
|
|
312
|
-
},
|
|
313
|
-
pattern_properties={"^x-": typesystem.Any()},
|
|
314
|
-
additional_properties=False,
|
|
315
|
-
)
|
|
316
|
-
|
|
317
|
-
definitions["Tag"] = typesystem.Object(
|
|
318
|
-
properties={
|
|
319
|
-
"name": typesystem.String(),
|
|
320
|
-
"description": typesystem.Text(allow_blank=True),
|
|
321
|
-
"externalDocs": typesystem.Reference(
|
|
322
|
-
"ExternalDocumentation", definitions=definitions
|
|
323
|
-
),
|
|
324
|
-
},
|
|
325
|
-
pattern_properties={"^x-": typesystem.Any()},
|
|
326
|
-
additional_properties=False,
|
|
327
|
-
required=["name"],
|
|
328
|
-
)
|
|
329
|
-
|
|
330
|
-
definitions["SecurityRequirement"] = typesystem.Object(
|
|
331
|
-
additional_properties=typesystem.Array(items=typesystem.String())
|
|
332
|
-
)
|
|
333
|
-
|
|
334
|
-
definitions["SecurityScheme"] = typesystem.Object(
|
|
335
|
-
properties={
|
|
336
|
-
"type": typesystem.Choice(
|
|
337
|
-
choices=["apiKey", "http", "oauth2", "openIdConnect"]
|
|
338
|
-
),
|
|
339
|
-
"description": typesystem.Text(allow_blank=True),
|
|
340
|
-
"name": typesystem.String(),
|
|
341
|
-
"in": typesystem.Choice(choices=["query", "header", "cookie"]),
|
|
342
|
-
"scheme": typesystem.String(),
|
|
343
|
-
"bearerFormat": typesystem.String(),
|
|
344
|
-
"flows": typesystem.Any(), # TODO: OAuthFlows
|
|
345
|
-
"openIdConnectUrl": typesystem.String(format="url"),
|
|
346
132
|
},
|
|
347
|
-
|
|
348
|
-
additional_properties=False,
|
|
349
|
-
required=["type"],
|
|
133
|
+
additional_properties=True,
|
|
350
134
|
)
|
|
351
135
|
|
|
352
|
-
|
|
353
136
|
METHODS = ["get", "put", "post", "delete", "options", "head", "patch", "trace"]
|
|
354
137
|
|
|
355
138
|
|
|
@@ -373,23 +156,13 @@ def _simple_slugify(text):
|
|
|
373
156
|
|
|
374
157
|
class OpenAPI:
|
|
375
158
|
def load(self, data):
|
|
376
|
-
title = lookup(data, ["info", "title"])
|
|
377
|
-
description = lookup(data, ["info", "description"])
|
|
378
|
-
version = lookup(data, ["info", "version"])
|
|
379
|
-
base_url = lookup(data, ["servers", 0, "url"])
|
|
380
159
|
schema_definitions = self.get_schema_definitions(data)
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
return Document(
|
|
384
|
-
title=title,
|
|
385
|
-
description=description,
|
|
386
|
-
version=version,
|
|
387
|
-
url=base_url,
|
|
388
|
-
content=content,
|
|
389
|
-
)
|
|
160
|
+
links = self.get_links(data, schema_definitions)
|
|
161
|
+
|
|
162
|
+
return Document(links=links)
|
|
390
163
|
|
|
391
164
|
def get_schema_definitions(self, data):
|
|
392
|
-
definitions = typesystem.
|
|
165
|
+
definitions = typesystem.Definitions()
|
|
393
166
|
schemas = lookup(data, ["components", "schemas"], {})
|
|
394
167
|
for key, value in schemas.items():
|
|
395
168
|
ref = f"#/components/schemas/{key}"
|
|
@@ -398,19 +171,16 @@ class OpenAPI:
|
|
|
398
171
|
)
|
|
399
172
|
return definitions
|
|
400
173
|
|
|
401
|
-
def
|
|
174
|
+
def get_links(self, data, schema_definitions):
|
|
402
175
|
"""
|
|
403
|
-
Return all the links in the document, laid out by
|
|
176
|
+
Return all the links in the document, laid out by operationId.
|
|
404
177
|
"""
|
|
405
|
-
links_by_tag = {}
|
|
406
178
|
links = []
|
|
407
179
|
|
|
408
180
|
for path, path_info in data.get("paths", {}).items():
|
|
409
181
|
operations = {key: path_info[key] for key in path_info if key in METHODS}
|
|
410
182
|
for operation, operation_info in operations.items():
|
|
411
|
-
tag = lookup(operation_info, ["tags", 0])
|
|
412
183
|
link = self.get_link(
|
|
413
|
-
base_url,
|
|
414
184
|
path,
|
|
415
185
|
path_info,
|
|
416
186
|
operation,
|
|
@@ -420,36 +190,20 @@ class OpenAPI:
|
|
|
420
190
|
if link is None:
|
|
421
191
|
continue
|
|
422
192
|
|
|
423
|
-
|
|
424
|
-
links.append(link)
|
|
425
|
-
elif tag not in links_by_tag:
|
|
426
|
-
links_by_tag[tag] = [link]
|
|
427
|
-
else:
|
|
428
|
-
links_by_tag[tag].append(link)
|
|
193
|
+
links.append(link)
|
|
429
194
|
|
|
430
|
-
|
|
431
|
-
Section(name=_simple_slugify(tag), title=tag.title(), content=links)
|
|
432
|
-
for tag, links in links_by_tag.items()
|
|
433
|
-
]
|
|
434
|
-
return links + sections
|
|
195
|
+
return links
|
|
435
196
|
|
|
436
|
-
def get_link(
|
|
437
|
-
self, base_url, path, path_info, operation, operation_info, schema_definitions
|
|
438
|
-
):
|
|
197
|
+
def get_link(self, path, path_info, operation, operation_info, schema_definitions):
|
|
439
198
|
"""
|
|
440
199
|
Return a single link in the document.
|
|
441
200
|
"""
|
|
442
|
-
name = operation_info
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
if name is None:
|
|
447
|
-
name = _simple_slugify(title)
|
|
448
|
-
if not name:
|
|
449
|
-
return None
|
|
201
|
+
name = operation_info["operationId"]
|
|
202
|
+
deprecated = operation_info.get("deprecated", False)
|
|
203
|
+
paginated = operation_info.get("x-paginated")
|
|
450
204
|
|
|
451
|
-
# Allow path info and operation info to override the base
|
|
452
|
-
base_url = lookup(path_info, ["servers", 0, "url"], default=
|
|
205
|
+
# Allow path info and operation info to override the base URL, which is normally managed by the ArkindexClient.
|
|
206
|
+
base_url = lookup(path_info, ["servers", 0, "url"], default=None)
|
|
453
207
|
base_url = lookup(operation_info, ["servers", 0, "url"], default=base_url)
|
|
454
208
|
|
|
455
209
|
# Parameters are taken both from the path info, and from the operation.
|
|
@@ -487,10 +241,10 @@ class OpenAPI:
|
|
|
487
241
|
name=name,
|
|
488
242
|
url=urljoin(base_url, path),
|
|
489
243
|
method=operation,
|
|
490
|
-
title=title,
|
|
491
|
-
description=description,
|
|
492
244
|
fields=fields,
|
|
493
245
|
encoding=encoding,
|
|
246
|
+
deprecated=deprecated,
|
|
247
|
+
paginated=paginated,
|
|
494
248
|
)
|
|
495
249
|
|
|
496
250
|
def get_field(self, parameter, schema_definitions):
|
|
@@ -499,10 +253,9 @@ class OpenAPI:
|
|
|
499
253
|
"""
|
|
500
254
|
name = parameter.get("name")
|
|
501
255
|
location = parameter.get("in")
|
|
502
|
-
description = parameter.get("description")
|
|
503
256
|
required = parameter.get("required", False)
|
|
504
257
|
schema = parameter.get("schema")
|
|
505
|
-
|
|
258
|
+
deprecated = parameter.get("deprecated", False)
|
|
506
259
|
|
|
507
260
|
if schema is not None:
|
|
508
261
|
if "$ref" in schema:
|
|
@@ -516,8 +269,7 @@ class OpenAPI:
|
|
|
516
269
|
return Field(
|
|
517
270
|
name=name,
|
|
518
271
|
location=location,
|
|
519
|
-
description=description,
|
|
520
272
|
required=required,
|
|
521
273
|
schema=schema,
|
|
522
|
-
|
|
274
|
+
deprecated=deprecated,
|
|
523
275
|
)
|