apytizer 0.0.1a0__py3-none-any.whl → 0.0.1b1__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.
- apytizer/__init__.py +2 -12
- apytizer/adapters/__init__.py +2 -3
- apytizer/adapters/transport_adapter.py +91 -0
- apytizer/apis/__init__.py +6 -0
- apytizer/apis/abstract_api.py +36 -0
- apytizer/apis/web_api.py +461 -0
- apytizer/connections/__init__.py +6 -0
- apytizer/connections/abstract_connection.py +28 -0
- apytizer/connections/http_connection.py +431 -0
- apytizer/decorators/__init__.py +5 -5
- apytizer/decorators/caching.py +60 -9
- apytizer/decorators/chunking.py +105 -0
- apytizer/decorators/connection.py +55 -20
- apytizer/decorators/json_response.py +93 -0
- apytizer/decorators/pagination.py +50 -32
- apytizer/endpoints/__init__.py +6 -0
- apytizer/endpoints/abstract_endpoint.py +38 -0
- apytizer/endpoints/web_endpoint.py +519 -0
- apytizer/engines/__init__.py +6 -0
- apytizer/engines/abstract_engine.py +45 -0
- apytizer/engines/http_engine.py +129 -0
- apytizer/errors.py +34 -0
- apytizer/factories/__init__.py +5 -0
- apytizer/factories/abstract_factory.py +17 -0
- apytizer/http_methods.py +34 -0
- apytizer/managers/__init__.py +12 -0
- apytizer/managers/abstract_manager.py +80 -0
- apytizer/managers/base_manager.py +116 -0
- apytizer/mappers/__init__.py +6 -0
- apytizer/mappers/abstract_mapper.py +48 -0
- apytizer/mappers/base_mapper.py +78 -0
- apytizer/media_types.py +118 -0
- apytizer/models/__init__.py +6 -0
- apytizer/models/abstract_model.py +119 -0
- apytizer/models/base_model.py +85 -0
- apytizer/protocols.py +38 -0
- apytizer/repositories/__init__.py +6 -0
- apytizer/repositories/abstract_repository.py +81 -0
- apytizer/repositories/managed_repository.py +92 -0
- apytizer/routes/__init__.py +6 -0
- apytizer/routes/abstract_route.py +32 -0
- apytizer/routes/base_route.py +138 -0
- apytizer/sessions/__init__.py +33 -0
- apytizer/sessions/abstract_session.py +63 -0
- apytizer/sessions/requests_session.py +125 -0
- apytizer/states/__init__.py +6 -0
- apytizer/states/abstract_state.py +71 -0
- apytizer/states/local_state.py +99 -0
- apytizer/utils/__init__.py +9 -4
- apytizer/utils/caching.py +39 -0
- apytizer/utils/dictionaries.py +375 -0
- apytizer/utils/errors.py +104 -0
- apytizer/utils/iterables.py +91 -0
- apytizer/utils/objects.py +145 -0
- apytizer/utils/strings.py +69 -0
- apytizer/utils/typing.py +29 -0
- apytizer-0.0.1b1.dist-info/METADATA +41 -0
- apytizer-0.0.1b1.dist-info/RECORD +60 -0
- {apytizer-0.0.1a0.dist-info → apytizer-0.0.1b1.dist-info}/WHEEL +1 -2
- apytizer/abstracts/__init__.py +0 -8
- apytizer/abstracts/api.py +0 -147
- apytizer/abstracts/endpoint.py +0 -177
- apytizer/abstracts/model.py +0 -50
- apytizer/abstracts/session.py +0 -39
- apytizer/adapters/transport.py +0 -40
- apytizer/base/__init__.py +0 -8
- apytizer/base/api.py +0 -510
- apytizer/base/endpoint.py +0 -443
- apytizer/base/model.py +0 -119
- apytizer/decorators/json.py +0 -35
- apytizer/utils/generate_key.py +0 -18
- apytizer/utils/merge.py +0 -19
- apytizer-0.0.1a0.dist-info/METADATA +0 -27
- apytizer-0.0.1a0.dist-info/RECORD +0 -25
- apytizer-0.0.1a0.dist-info/top_level.txt +0 -1
- {apytizer-0.0.1a0.dist-info → apytizer-0.0.1b1.dist-info/licenses}/LICENSE +0 -0
|
@@ -0,0 +1,519 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# src/apytizer/endpoints/base_endpoint.py
|
|
3
|
+
"""WEb Endpoint Class.
|
|
4
|
+
|
|
5
|
+
This module defines the web endpoint class implementation.
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
# Standard Library Imports
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
import logging
|
|
12
|
+
from typing import Any
|
|
13
|
+
from typing import Collection
|
|
14
|
+
from typing import Dict
|
|
15
|
+
from typing import MutableMapping
|
|
16
|
+
from typing import Optional
|
|
17
|
+
from typing import Set
|
|
18
|
+
from typing import Union
|
|
19
|
+
from typing import TYPE_CHECKING
|
|
20
|
+
from urllib.parse import urljoin
|
|
21
|
+
|
|
22
|
+
# Third-Party Imports
|
|
23
|
+
from requests import Response
|
|
24
|
+
|
|
25
|
+
# Local Imports
|
|
26
|
+
from .abstract_endpoint import AbstractEndpoint
|
|
27
|
+
from ..connections import HttpConnection
|
|
28
|
+
from ..decorators import cache_response
|
|
29
|
+
from ..http_methods import HTTPMethod
|
|
30
|
+
from ..routes import Route
|
|
31
|
+
from .. import errors
|
|
32
|
+
from .. import utils
|
|
33
|
+
|
|
34
|
+
if TYPE_CHECKING:
|
|
35
|
+
from ..apis import WebAPI
|
|
36
|
+
|
|
37
|
+
__all__ = ["WebEndpoint"]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
log = logging.getLogger("apytizer")
|
|
41
|
+
|
|
42
|
+
# Define constants.
|
|
43
|
+
DEFAULT_METHODS = (
|
|
44
|
+
HTTPMethod.HEAD,
|
|
45
|
+
HTTPMethod.GET,
|
|
46
|
+
HTTPMethod.POST,
|
|
47
|
+
HTTPMethod.PUT,
|
|
48
|
+
HTTPMethod.PATCH,
|
|
49
|
+
HTTPMethod.DELETE,
|
|
50
|
+
HTTPMethod.OPTIONS,
|
|
51
|
+
HTTPMethod.TRACE,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class WebEndpoint(AbstractEndpoint):
|
|
56
|
+
"""Implements an endpoint for web APIs.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
__api: API instance.
|
|
60
|
+
path: Relative path to endpoint.
|
|
61
|
+
methods (optional): List of HTTP methods accepted by endpoint.
|
|
62
|
+
headers (optional): Headers to set globally for endpoint.
|
|
63
|
+
params (optional): Parameters to set globally for endpoint.
|
|
64
|
+
cache (optional): Mutable mapping for caching responses.
|
|
65
|
+
|
|
66
|
+
Attributes:
|
|
67
|
+
connection: Connection with which to make requests.
|
|
68
|
+
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
__slots__ = (
|
|
72
|
+
"_api",
|
|
73
|
+
"_path",
|
|
74
|
+
"_methods",
|
|
75
|
+
"_headers",
|
|
76
|
+
"_params",
|
|
77
|
+
"_cache",
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
def __init__(
|
|
81
|
+
self,
|
|
82
|
+
__api: "WebAPI",
|
|
83
|
+
/,
|
|
84
|
+
path: Union[int, Route, str],
|
|
85
|
+
*,
|
|
86
|
+
methods: Collection[HTTPMethod] = DEFAULT_METHODS,
|
|
87
|
+
headers: Optional[Dict[str, str]] = None,
|
|
88
|
+
params: Optional[Dict[str, Any]] = None,
|
|
89
|
+
cache: Optional[MutableMapping[str, Any]] = None,
|
|
90
|
+
) -> None:
|
|
91
|
+
self._api = __api
|
|
92
|
+
self._path = str(path).strip("/")
|
|
93
|
+
self._methods = set(methods)
|
|
94
|
+
self._headers = headers
|
|
95
|
+
self._params = params
|
|
96
|
+
self._cache = cache
|
|
97
|
+
|
|
98
|
+
@property
|
|
99
|
+
def api(self) -> "WebAPI":
|
|
100
|
+
"""API."""
|
|
101
|
+
return self._api
|
|
102
|
+
|
|
103
|
+
@property
|
|
104
|
+
def connection(self) -> Optional[HttpConnection]:
|
|
105
|
+
"""Connection with which to make requests."""
|
|
106
|
+
return self._api.connection
|
|
107
|
+
|
|
108
|
+
@property
|
|
109
|
+
def path(self) -> str:
|
|
110
|
+
"""Endpoint path."""
|
|
111
|
+
return self._path
|
|
112
|
+
|
|
113
|
+
@property
|
|
114
|
+
def url(self) -> str:
|
|
115
|
+
"""Endpoint URL."""
|
|
116
|
+
result = urljoin(self.api.url, self.path)
|
|
117
|
+
return result
|
|
118
|
+
|
|
119
|
+
@property
|
|
120
|
+
def methods(self) -> Set[HTTPMethod]:
|
|
121
|
+
"""Endpoint methods."""
|
|
122
|
+
return self._methods
|
|
123
|
+
|
|
124
|
+
@property
|
|
125
|
+
def headers(self) -> Optional[Dict[str, str]]:
|
|
126
|
+
"""Headers."""
|
|
127
|
+
return self._headers
|
|
128
|
+
|
|
129
|
+
@property
|
|
130
|
+
def params(self) -> Optional[Dict[str, Any]]:
|
|
131
|
+
"""Parameters."""
|
|
132
|
+
return self._params
|
|
133
|
+
|
|
134
|
+
@property
|
|
135
|
+
def cache(self) -> Optional[MutableMapping[str, Any]]:
|
|
136
|
+
"""Cache."""
|
|
137
|
+
return self._cache
|
|
138
|
+
|
|
139
|
+
def __eq__(self, other: object) -> bool:
|
|
140
|
+
result = (
|
|
141
|
+
other.path.lower() == self.path.lower()
|
|
142
|
+
if isinstance(other, WebEndpoint)
|
|
143
|
+
else False
|
|
144
|
+
)
|
|
145
|
+
return result
|
|
146
|
+
|
|
147
|
+
def __hash__(self) -> int:
|
|
148
|
+
return hash(self.path)
|
|
149
|
+
|
|
150
|
+
def __repr__(self) -> str:
|
|
151
|
+
result = f"<{self.__class__.__name__!s} (path={self.path!s})>"
|
|
152
|
+
return result
|
|
153
|
+
|
|
154
|
+
def __str__(self) -> str:
|
|
155
|
+
return self.path
|
|
156
|
+
|
|
157
|
+
def __getitem__(self, path: str) -> AbstractEndpoint:
|
|
158
|
+
"""Get endpoint.
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
path: Relative path of endpoint.
|
|
162
|
+
|
|
163
|
+
Returns:
|
|
164
|
+
Endpoint.
|
|
165
|
+
|
|
166
|
+
"""
|
|
167
|
+
route = "/".join([self._path, path])
|
|
168
|
+
result = self._api[route]
|
|
169
|
+
return result
|
|
170
|
+
|
|
171
|
+
def __truediv__(self, path: str) -> AbstractEndpoint:
|
|
172
|
+
"""Get endpoint.
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
path: Relative path of endpoint.
|
|
176
|
+
|
|
177
|
+
Returns:
|
|
178
|
+
Endpoint.
|
|
179
|
+
|
|
180
|
+
"""
|
|
181
|
+
route = "/".join([self._path, path])
|
|
182
|
+
result = self._api[route]
|
|
183
|
+
return result
|
|
184
|
+
|
|
185
|
+
@cache_response
|
|
186
|
+
def head(
|
|
187
|
+
self,
|
|
188
|
+
*,
|
|
189
|
+
headers: Optional[Dict[str, str]] = None,
|
|
190
|
+
params: Optional[Dict[str, Any]] = None,
|
|
191
|
+
**kwargs: Any,
|
|
192
|
+
) -> Optional[Response]:
|
|
193
|
+
"""Sends an HTTP HEAD request to the endpoint.
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
headers (optional): Request headers (overrides global headers).
|
|
197
|
+
params (optional): Request parameters (overrides global parameters).
|
|
198
|
+
**kwargs: Keyword arguments to include in request.
|
|
199
|
+
|
|
200
|
+
Returns:
|
|
201
|
+
Response object.
|
|
202
|
+
|
|
203
|
+
Raises:
|
|
204
|
+
ConnectionNotStarted: when connection not started.
|
|
205
|
+
MethodNotAllowed: when HTTP method not allowed on endpoint.
|
|
206
|
+
|
|
207
|
+
.. _MDN Web Docs:
|
|
208
|
+
https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/HEAD
|
|
209
|
+
|
|
210
|
+
"""
|
|
211
|
+
if self.connection is None:
|
|
212
|
+
message = "connection not started before making HEAD request"
|
|
213
|
+
raise errors.ConnectionNotStarted(message)
|
|
214
|
+
|
|
215
|
+
if HTTPMethod.HEAD not in self.methods:
|
|
216
|
+
message = f"HEAD method not allowed at {self.path!s} endpoint"
|
|
217
|
+
raise errors.MethodNotAllowed(message)
|
|
218
|
+
|
|
219
|
+
response = self.connection.head(
|
|
220
|
+
self.path,
|
|
221
|
+
headers=utils.merge(self.headers, headers),
|
|
222
|
+
params=utils.merge(self.params, params),
|
|
223
|
+
**kwargs,
|
|
224
|
+
)
|
|
225
|
+
return response
|
|
226
|
+
|
|
227
|
+
@cache_response
|
|
228
|
+
def get(
|
|
229
|
+
self,
|
|
230
|
+
*,
|
|
231
|
+
headers: Optional[Dict[str, str]] = None,
|
|
232
|
+
params: Optional[Dict[str, Any]] = None,
|
|
233
|
+
**kwargs: Any,
|
|
234
|
+
) -> Optional[Response]:
|
|
235
|
+
"""Sends an HTTP GET request to the endpoint.
|
|
236
|
+
|
|
237
|
+
Args:
|
|
238
|
+
headers (optional): Request headers (overrides global headers).
|
|
239
|
+
params (optional): Request parameters (overrides global parameters).
|
|
240
|
+
**kwargs: Keyword arguments to include in request.
|
|
241
|
+
|
|
242
|
+
Returns:
|
|
243
|
+
Response object.
|
|
244
|
+
|
|
245
|
+
Raises:
|
|
246
|
+
ConnectionNotStarted: when connection not started.
|
|
247
|
+
MethodNotAllowed: when HTTP method not allowed on endpoint.
|
|
248
|
+
|
|
249
|
+
.. _MDN Web Docs:
|
|
250
|
+
https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/GET
|
|
251
|
+
|
|
252
|
+
"""
|
|
253
|
+
if self.connection is None:
|
|
254
|
+
message = "connection not started before making GET request"
|
|
255
|
+
raise errors.ConnectionNotStarted(message)
|
|
256
|
+
|
|
257
|
+
if HTTPMethod.GET not in self.methods:
|
|
258
|
+
message = f"GET method not allowed at {self.path!s} endpoint"
|
|
259
|
+
raise errors.MethodNotAllowed(message)
|
|
260
|
+
|
|
261
|
+
response = self.connection.get(
|
|
262
|
+
self.path,
|
|
263
|
+
headers=utils.merge(self.headers, headers),
|
|
264
|
+
params=utils.merge(self.params, params),
|
|
265
|
+
**kwargs,
|
|
266
|
+
)
|
|
267
|
+
return response
|
|
268
|
+
|
|
269
|
+
@cache_response
|
|
270
|
+
def post(
|
|
271
|
+
self,
|
|
272
|
+
*,
|
|
273
|
+
headers: Optional[Dict[str, str]] = None,
|
|
274
|
+
params: Optional[Dict[str, Any]] = None,
|
|
275
|
+
**kwargs: Any,
|
|
276
|
+
) -> Optional[Response]:
|
|
277
|
+
"""Sends an HTTP POST request to the endpoint.
|
|
278
|
+
|
|
279
|
+
Args:
|
|
280
|
+
headers (optional): Request headers (overrides global headers).
|
|
281
|
+
params (optional): Request parameters (overrides global parameters).
|
|
282
|
+
**kwargs: Keyword arguments to include in request.
|
|
283
|
+
|
|
284
|
+
Returns:
|
|
285
|
+
Response object.
|
|
286
|
+
|
|
287
|
+
Raises:
|
|
288
|
+
ConnectionNotStarted: when connection not started.
|
|
289
|
+
MethodNotAllowed: when HTTP method not allowed on endpoint.
|
|
290
|
+
|
|
291
|
+
.. _MDN Web Docs:
|
|
292
|
+
https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST
|
|
293
|
+
|
|
294
|
+
"""
|
|
295
|
+
if self.connection is None:
|
|
296
|
+
message = "connection not started before making POST request"
|
|
297
|
+
raise errors.ConnectionNotStarted(message)
|
|
298
|
+
|
|
299
|
+
if HTTPMethod.POST not in self.methods:
|
|
300
|
+
message = f"POST method not allowed at {self.path!s} endpoint"
|
|
301
|
+
raise errors.MethodNotAllowed(message)
|
|
302
|
+
|
|
303
|
+
response = self.connection.post(
|
|
304
|
+
self.path,
|
|
305
|
+
headers=utils.merge(self.headers, headers),
|
|
306
|
+
params=utils.merge(self.params, params),
|
|
307
|
+
**kwargs,
|
|
308
|
+
)
|
|
309
|
+
return response
|
|
310
|
+
|
|
311
|
+
@cache_response
|
|
312
|
+
def put(
|
|
313
|
+
self,
|
|
314
|
+
*,
|
|
315
|
+
headers: Optional[Dict[str, str]] = None,
|
|
316
|
+
params: Optional[Dict[str, Any]] = None,
|
|
317
|
+
**kwargs: Any,
|
|
318
|
+
) -> Optional[Response]:
|
|
319
|
+
"""Sends an HTTP PUT request to the endpoint.
|
|
320
|
+
|
|
321
|
+
Args:
|
|
322
|
+
headers (optional): Request headers (overrides global headers).
|
|
323
|
+
params (optional): Request parameters (overrides global parameters).
|
|
324
|
+
**kwargs: Keyword arguments to include in request.
|
|
325
|
+
|
|
326
|
+
Returns:
|
|
327
|
+
Response object.
|
|
328
|
+
|
|
329
|
+
Raises:
|
|
330
|
+
ConnectionNotStarted: when connection not started.
|
|
331
|
+
MethodNotAllowed: when HTTP method not allowed on endpoint.
|
|
332
|
+
|
|
333
|
+
.. _MDN Web Docs:
|
|
334
|
+
https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PUT
|
|
335
|
+
|
|
336
|
+
"""
|
|
337
|
+
if self.connection is None:
|
|
338
|
+
message = "connection not started before making PUT request"
|
|
339
|
+
raise errors.ConnectionNotStarted(message)
|
|
340
|
+
|
|
341
|
+
if HTTPMethod.PUT not in self.methods:
|
|
342
|
+
message = f"PUT method not allowed at {self.path!s} endpoint"
|
|
343
|
+
raise errors.MethodNotAllowed(message)
|
|
344
|
+
|
|
345
|
+
response = self.connection.put(
|
|
346
|
+
self.path,
|
|
347
|
+
headers=utils.merge(self.headers, headers),
|
|
348
|
+
params=utils.merge(self.params, params),
|
|
349
|
+
**kwargs,
|
|
350
|
+
)
|
|
351
|
+
return response
|
|
352
|
+
|
|
353
|
+
@cache_response
|
|
354
|
+
def patch(
|
|
355
|
+
self,
|
|
356
|
+
*,
|
|
357
|
+
headers: Optional[Dict[str, str]] = None,
|
|
358
|
+
params: Optional[Dict[str, Any]] = None,
|
|
359
|
+
**kwargs: Any,
|
|
360
|
+
) -> Optional[Response]:
|
|
361
|
+
"""Sends an HTTP PATCH request to the endpoint.
|
|
362
|
+
|
|
363
|
+
Args:
|
|
364
|
+
headers (optional): Request headers (overrides global headers).
|
|
365
|
+
params (optional): Request parameters (overrides global parameters).
|
|
366
|
+
**kwargs: Keyword arguments to include in request.
|
|
367
|
+
|
|
368
|
+
Returns:
|
|
369
|
+
Response object.
|
|
370
|
+
|
|
371
|
+
Raises:
|
|
372
|
+
ConnectionNotStarted: when connection not started.
|
|
373
|
+
MethodNotAllowed: when HTTP method not allowed on endpoint.
|
|
374
|
+
|
|
375
|
+
.. _MDN Web Docs:
|
|
376
|
+
https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PATCH
|
|
377
|
+
|
|
378
|
+
"""
|
|
379
|
+
if self.connection is None:
|
|
380
|
+
message = "connection not started before making PATCH request"
|
|
381
|
+
raise errors.ConnectionNotStarted(message)
|
|
382
|
+
|
|
383
|
+
if HTTPMethod.PATCH not in self.methods:
|
|
384
|
+
message = f"PATCH method not allowed at {self.path!s} endpoint"
|
|
385
|
+
raise errors.MethodNotAllowed(message)
|
|
386
|
+
|
|
387
|
+
response = self.connection.patch(
|
|
388
|
+
self.path,
|
|
389
|
+
headers=utils.merge(self.headers, headers),
|
|
390
|
+
params=utils.merge(self.params, params),
|
|
391
|
+
**kwargs,
|
|
392
|
+
)
|
|
393
|
+
return response
|
|
394
|
+
|
|
395
|
+
@cache_response
|
|
396
|
+
def delete(
|
|
397
|
+
self,
|
|
398
|
+
*,
|
|
399
|
+
headers: Optional[Dict[str, str]] = None,
|
|
400
|
+
params: Optional[Dict[str, Any]] = None,
|
|
401
|
+
**kwargs: Any,
|
|
402
|
+
) -> Optional[Response]:
|
|
403
|
+
"""Sends an HTTP DELETE request to the endpoint.
|
|
404
|
+
|
|
405
|
+
Args:
|
|
406
|
+
headers (optional): Request headers (overrides global headers).
|
|
407
|
+
params (optional): Request parameters (overrides global parameters).
|
|
408
|
+
**kwargs: Keyword arguments to include in request.
|
|
409
|
+
|
|
410
|
+
Returns:
|
|
411
|
+
Response object.
|
|
412
|
+
|
|
413
|
+
Raises:
|
|
414
|
+
ConnectionNotStarted: when connection not started.
|
|
415
|
+
MethodNotAllowed: when HTTP method not allowed on endpoint.
|
|
416
|
+
|
|
417
|
+
.. _MDN Web Docs:
|
|
418
|
+
https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/DELETE
|
|
419
|
+
|
|
420
|
+
"""
|
|
421
|
+
if self.connection is None:
|
|
422
|
+
message = "connection not started before making DELETE request"
|
|
423
|
+
raise errors.ConnectionNotStarted(message)
|
|
424
|
+
|
|
425
|
+
if HTTPMethod.DELETE not in self.methods:
|
|
426
|
+
message = f"DELETE method not allowed at {self.path!s} endpoint"
|
|
427
|
+
raise errors.MethodNotAllowed(message)
|
|
428
|
+
|
|
429
|
+
response = self.connection.delete(
|
|
430
|
+
self.path,
|
|
431
|
+
headers=utils.merge(self.headers, headers),
|
|
432
|
+
params=utils.merge(self.params, params),
|
|
433
|
+
**kwargs,
|
|
434
|
+
)
|
|
435
|
+
return response
|
|
436
|
+
|
|
437
|
+
@cache_response
|
|
438
|
+
def options(
|
|
439
|
+
self,
|
|
440
|
+
*,
|
|
441
|
+
headers: Optional[Dict[str, str]] = None,
|
|
442
|
+
params: Optional[Dict[str, Any]] = None,
|
|
443
|
+
**kwargs: Any,
|
|
444
|
+
) -> Optional[Response]:
|
|
445
|
+
"""Sends an HTTP OPTIONS request to the endpoint.
|
|
446
|
+
|
|
447
|
+
Args:
|
|
448
|
+
headers (optional): Request headers (overrides global headers).
|
|
449
|
+
params (optional): Request parameters (overrides global parameters).
|
|
450
|
+
**kwargs: Keyword arguments to include in request.
|
|
451
|
+
|
|
452
|
+
Returns:
|
|
453
|
+
Response object.
|
|
454
|
+
|
|
455
|
+
Raises:
|
|
456
|
+
ConnectionNotStarted: when connection not started.
|
|
457
|
+
MethodNotAllowed: when HTTP method not allowed on endpoint.
|
|
458
|
+
|
|
459
|
+
.. _MDN Web Docs:
|
|
460
|
+
https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/OPTIONS
|
|
461
|
+
|
|
462
|
+
"""
|
|
463
|
+
if self.connection is None:
|
|
464
|
+
message = "connection not started before making OPTIONS request"
|
|
465
|
+
raise errors.ConnectionNotStarted(message)
|
|
466
|
+
|
|
467
|
+
if HTTPMethod.OPTIONS not in self.methods:
|
|
468
|
+
message = f"OPTIONS method not allowed at {self.path!s} endpoint"
|
|
469
|
+
raise errors.MethodNotAllowed(message)
|
|
470
|
+
|
|
471
|
+
response = self.connection.options(
|
|
472
|
+
self.path,
|
|
473
|
+
headers=utils.merge(self.headers, headers),
|
|
474
|
+
params=utils.merge(self.params, params),
|
|
475
|
+
**kwargs,
|
|
476
|
+
)
|
|
477
|
+
return response
|
|
478
|
+
|
|
479
|
+
@cache_response
|
|
480
|
+
def trace(
|
|
481
|
+
self,
|
|
482
|
+
*,
|
|
483
|
+
headers: Optional[Dict[str, str]] = None,
|
|
484
|
+
params: Optional[Dict[str, Any]] = None,
|
|
485
|
+
**kwargs: Any,
|
|
486
|
+
) -> Optional[Response]:
|
|
487
|
+
"""Sends an HTTP TRACE request to the endpoint.
|
|
488
|
+
|
|
489
|
+
Args:
|
|
490
|
+
headers (optional): Request headers (overrides global headers).
|
|
491
|
+
params (optional): Request parameters (overrides global parameters).
|
|
492
|
+
**kwargs: Keyword arguments to include in request.
|
|
493
|
+
|
|
494
|
+
Returns:
|
|
495
|
+
Response object.
|
|
496
|
+
|
|
497
|
+
Raises:
|
|
498
|
+
ConnectionNotStarted: when connection not started.
|
|
499
|
+
MethodNotAllowed: when HTTP method not allowed on endpoint.
|
|
500
|
+
|
|
501
|
+
.. _MDN Web Docs:
|
|
502
|
+
https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/TRACE
|
|
503
|
+
|
|
504
|
+
"""
|
|
505
|
+
if self.connection is None:
|
|
506
|
+
message = "connection not started before making TRACE request"
|
|
507
|
+
raise errors.ConnectionNotStarted(message)
|
|
508
|
+
|
|
509
|
+
if HTTPMethod.TRACE not in self.methods:
|
|
510
|
+
message = f"TRACE method not allowed at {self.path!s} endpoint"
|
|
511
|
+
raise errors.MethodNotAllowed(message)
|
|
512
|
+
|
|
513
|
+
response = self.connection.trace(
|
|
514
|
+
self.path,
|
|
515
|
+
headers=utils.merge(self.headers, headers),
|
|
516
|
+
params=utils.merge(self.params, params),
|
|
517
|
+
**kwargs,
|
|
518
|
+
)
|
|
519
|
+
return response
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# src/apytizer/engines/abstract_engine.py
|
|
3
|
+
"""Abstract Engine Class.
|
|
4
|
+
|
|
5
|
+
This module defines an abstract engine class which provides an interface
|
|
6
|
+
for subclasses to implement.
|
|
7
|
+
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
# Standard Library Imports
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
import abc
|
|
13
|
+
from typing import Optional
|
|
14
|
+
|
|
15
|
+
# Local Imports
|
|
16
|
+
from ..connections import AbstractConnection
|
|
17
|
+
from ..protocols import Protocol
|
|
18
|
+
|
|
19
|
+
__all__ = ["AbstractEngine"]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class AbstractEngine(abc.ABC):
|
|
23
|
+
"""Represents an abstract engine."""
|
|
24
|
+
|
|
25
|
+
@property
|
|
26
|
+
@abc.abstractmethod
|
|
27
|
+
def protocol(self) -> Optional[Protocol]:
|
|
28
|
+
"""Protocol."""
|
|
29
|
+
raise NotImplementedError
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
@abc.abstractmethod
|
|
33
|
+
def url(self) -> str:
|
|
34
|
+
"""Base URL."""
|
|
35
|
+
raise NotImplementedError
|
|
36
|
+
|
|
37
|
+
@abc.abstractmethod
|
|
38
|
+
def connect(self) -> AbstractConnection:
|
|
39
|
+
"""Establish connection.
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
Connection.
|
|
43
|
+
|
|
44
|
+
"""
|
|
45
|
+
raise NotImplementedError
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# src/apytizer/engines/http_engine.py
|
|
3
|
+
"""HTTP Engine Class.
|
|
4
|
+
|
|
5
|
+
This module defines the HTTP engine class implementation.
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
# Standard Library Imports
|
|
10
|
+
from typing import Any
|
|
11
|
+
from typing import Dict
|
|
12
|
+
from typing import MutableMapping
|
|
13
|
+
from typing import Optional
|
|
14
|
+
from typing import Tuple
|
|
15
|
+
from typing import Type
|
|
16
|
+
from typing import TypeVar
|
|
17
|
+
from typing import Union
|
|
18
|
+
|
|
19
|
+
# Third-Party Imports
|
|
20
|
+
from requests.auth import AuthBase
|
|
21
|
+
from requests.adapters import HTTPAdapter
|
|
22
|
+
|
|
23
|
+
# Local Imports
|
|
24
|
+
from .abstract_engine import AbstractEngine
|
|
25
|
+
from ..connections import HttpConnection
|
|
26
|
+
from ..protocols import Protocol
|
|
27
|
+
from ..protocols import get_protocol
|
|
28
|
+
from ..utils import errors
|
|
29
|
+
|
|
30
|
+
__all__ = ["HTTPEngine"]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# Custom types:
|
|
34
|
+
T = TypeVar("T")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class HTTPEngine(AbstractEngine):
|
|
38
|
+
"""Implements an HTTP engine.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
url: Base URL.
|
|
42
|
+
adapters (optional): Connection adapters. Default ``None``.
|
|
43
|
+
uth (optional): Authentication header. default ``None``.
|
|
44
|
+
cert (optional): Client certificate. Default ``None``.
|
|
45
|
+
headers (optional): Headers to set globally. Default ``None``.
|
|
46
|
+
params (optional): Parameters to set globally. Default ``None``.
|
|
47
|
+
proxies (optional): Protocols mapped to proxy URLs. Default ``None``.
|
|
48
|
+
stream (optional): Whether to stream response content. Default ``False``.
|
|
49
|
+
timeout (optional): How long to wait before timing out. Default ``None``.
|
|
50
|
+
verify (optional): Whether to verify certificate. Default ``True``.
|
|
51
|
+
|
|
52
|
+
Raises:
|
|
53
|
+
TypeError: when URL of type other than `str`.
|
|
54
|
+
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
_connection_cls: Type[HttpConnection] = HttpConnection
|
|
58
|
+
|
|
59
|
+
def __init__(
|
|
60
|
+
self,
|
|
61
|
+
url: str,
|
|
62
|
+
*,
|
|
63
|
+
adapters: Optional[Dict[Protocol, HTTPAdapter]] = None,
|
|
64
|
+
auth: Optional[Union[AuthBase, Tuple[str, str]]] = None,
|
|
65
|
+
cert: Optional[Union[str, Tuple[str, str]]] = None,
|
|
66
|
+
headers: Optional[Dict[str, str]] = None,
|
|
67
|
+
params: Optional[Dict[str, Any]] = None,
|
|
68
|
+
proxies: Optional[MutableMapping[str, str]] = None,
|
|
69
|
+
stream: Optional[bool] = False,
|
|
70
|
+
timeout: Optional[Union[float, Tuple[float, float]]] = None,
|
|
71
|
+
verify: Optional[bool] = True,
|
|
72
|
+
) -> None:
|
|
73
|
+
self.url = url
|
|
74
|
+
self.adapters = adapters or {}
|
|
75
|
+
self.auth = auth
|
|
76
|
+
self.cert = cert
|
|
77
|
+
self.headers = headers
|
|
78
|
+
self.params = params
|
|
79
|
+
self.proxies = proxies
|
|
80
|
+
self.stream = stream
|
|
81
|
+
self.timeout = timeout
|
|
82
|
+
self.verify = verify
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
def protocol(self) -> Optional[Protocol]:
|
|
86
|
+
"""Protocol."""
|
|
87
|
+
result = get_protocol(self.url)
|
|
88
|
+
return result
|
|
89
|
+
|
|
90
|
+
@property
|
|
91
|
+
def url(self) -> str:
|
|
92
|
+
"""Base URL."""
|
|
93
|
+
return self._url
|
|
94
|
+
|
|
95
|
+
@url.setter
|
|
96
|
+
def url(self, value: str) -> None:
|
|
97
|
+
errors.raise_for_instance(value, str)
|
|
98
|
+
self._url = standardize_url(value)
|
|
99
|
+
|
|
100
|
+
def __repr__(self) -> str:
|
|
101
|
+
return f"Engine({self.url!r})"
|
|
102
|
+
|
|
103
|
+
def connect(self) -> HttpConnection:
|
|
104
|
+
"""Establish connection.
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
Connection instance.
|
|
108
|
+
|
|
109
|
+
"""
|
|
110
|
+
result = self._connection_cls(self)
|
|
111
|
+
return result
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def standardize_url(__url: Any, /) -> str:
|
|
115
|
+
"""Standardize URL.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
__url: URL.
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
URL.
|
|
122
|
+
|
|
123
|
+
"""
|
|
124
|
+
if not isinstance(__url, str):
|
|
125
|
+
message = f"expected type 'str', got {type(__url)} instead"
|
|
126
|
+
raise TypeError(message)
|
|
127
|
+
|
|
128
|
+
result = __url if __url.endswith("/") else __url + "/"
|
|
129
|
+
return result
|