apytizer 0.0.1a0__py3-none-any.whl → 0.0.1b2__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.
Files changed (75) hide show
  1. apytizer/__init__.py +2 -12
  2. apytizer/adapters/__init__.py +2 -3
  3. apytizer/adapters/transport_adapter.py +91 -0
  4. apytizer/apis/__init__.py +6 -0
  5. apytizer/apis/abstract_api.py +36 -0
  6. apytizer/apis/web_api.py +460 -0
  7. apytizer/connections/__init__.py +6 -0
  8. apytizer/connections/abstract_connection.py +28 -0
  9. apytizer/connections/http_connection.py +431 -0
  10. apytizer/decorators/__init__.py +5 -5
  11. apytizer/decorators/caching.py +60 -9
  12. apytizer/decorators/chunking.py +105 -0
  13. apytizer/decorators/connection.py +55 -20
  14. apytizer/decorators/json.py +70 -12
  15. apytizer/decorators/pagination.py +50 -32
  16. apytizer/endpoints/__init__.py +6 -0
  17. apytizer/endpoints/abstract_endpoint.py +38 -0
  18. apytizer/endpoints/web_endpoint.py +519 -0
  19. apytizer/engines/__init__.py +6 -0
  20. apytizer/engines/abstract_engine.py +45 -0
  21. apytizer/engines/http_engine.py +171 -0
  22. apytizer/errors.py +34 -0
  23. apytizer/factories/__init__.py +5 -0
  24. apytizer/factories/abstract_factory.py +17 -0
  25. apytizer/http_methods.py +34 -0
  26. apytizer/managers/__init__.py +12 -0
  27. apytizer/managers/abstract_manager.py +80 -0
  28. apytizer/managers/base_manager.py +116 -0
  29. apytizer/mappers/__init__.py +6 -0
  30. apytizer/mappers/abstract_mapper.py +48 -0
  31. apytizer/mappers/base_mapper.py +78 -0
  32. apytizer/media_types.py +118 -0
  33. apytizer/models/__init__.py +6 -0
  34. apytizer/models/abstract_model.py +119 -0
  35. apytizer/models/base_model.py +85 -0
  36. apytizer/protocols.py +38 -0
  37. apytizer/repositories/__init__.py +6 -0
  38. apytizer/repositories/abstract_repository.py +81 -0
  39. apytizer/repositories/managed_repository.py +92 -0
  40. apytizer/routes/__init__.py +6 -0
  41. apytizer/routes/abstract_route.py +32 -0
  42. apytizer/routes/base_route.py +138 -0
  43. apytizer/sessions/__init__.py +33 -0
  44. apytizer/sessions/abstract_session.py +63 -0
  45. apytizer/sessions/requests_session.py +125 -0
  46. apytizer/states/__init__.py +6 -0
  47. apytizer/states/abstract_state.py +71 -0
  48. apytizer/states/local_state.py +99 -0
  49. apytizer/utils/__init__.py +9 -4
  50. apytizer/utils/caching.py +39 -0
  51. apytizer/utils/dictionaries.py +376 -0
  52. apytizer/utils/errors.py +104 -0
  53. apytizer/utils/iterables.py +91 -0
  54. apytizer/utils/objects.py +145 -0
  55. apytizer/utils/strings.py +69 -0
  56. apytizer/utils/typing.py +29 -0
  57. apytizer-0.0.1b2.dist-info/METADATA +41 -0
  58. apytizer-0.0.1b2.dist-info/RECORD +60 -0
  59. {apytizer-0.0.1a0.dist-info → apytizer-0.0.1b2.dist-info}/WHEEL +1 -2
  60. apytizer/abstracts/__init__.py +0 -8
  61. apytizer/abstracts/api.py +0 -147
  62. apytizer/abstracts/endpoint.py +0 -177
  63. apytizer/abstracts/model.py +0 -50
  64. apytizer/abstracts/session.py +0 -39
  65. apytizer/adapters/transport.py +0 -40
  66. apytizer/base/__init__.py +0 -8
  67. apytizer/base/api.py +0 -510
  68. apytizer/base/endpoint.py +0 -443
  69. apytizer/base/model.py +0 -119
  70. apytizer/utils/generate_key.py +0 -18
  71. apytizer/utils/merge.py +0 -19
  72. apytizer-0.0.1a0.dist-info/METADATA +0 -27
  73. apytizer-0.0.1a0.dist-info/RECORD +0 -25
  74. apytizer-0.0.1a0.dist-info/top_level.txt +0 -1
  75. {apytizer-0.0.1a0.dist-info → apytizer-0.0.1b2.dist-info/licenses}/LICENSE +0 -0
@@ -0,0 +1,519 @@
1
+ # -*- coding: utf-8 -*-
2
+ # src/apytizer/endpoints/web_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
+ from collections import ChainMap
12
+ import logging
13
+ from typing import Any
14
+ from typing import Collection
15
+ from typing import Dict
16
+ from typing import MutableMapping
17
+ from typing import Optional
18
+ from typing import Set
19
+ from typing import Union
20
+ from typing import TYPE_CHECKING
21
+ from urllib.parse import urljoin
22
+
23
+ # Third-Party Imports
24
+ from requests import Response
25
+
26
+ # Local Imports
27
+ from .abstract_endpoint import AbstractEndpoint
28
+ from ..connections import HttpConnection
29
+ from ..decorators import cache_response
30
+ from ..http_methods import HTTPMethod
31
+ from ..routes import Route
32
+ from .. import errors
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: Optional[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[HTTPMethod] = set(methods or [])
94
+ self._headers = ChainMap(headers or {})
95
+ self._params = ChainMap(params or {})
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) -> ChainMap[str, str]:
126
+ """Headers."""
127
+ return self._headers
128
+
129
+ @property
130
+ def params(self) -> ChainMap[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) -> WebEndpoint:
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) -> WebEndpoint:
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=self.headers.new_child(headers),
222
+ params=self.params.new_child(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=self.headers.new_child(headers),
264
+ params=self.params.new_child(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=self.headers.new_child(headers),
306
+ params=self.params.new_child(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=self.headers.new_child(headers),
348
+ params=self.params.new_child(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=self.headers.new_child(headers),
390
+ params=self.params.new_child(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=self.headers.new_child(headers),
432
+ params=self.params.new_child(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=self.headers.new_child(headers),
474
+ params=self.params.new_child(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=self.headers.new_child(headers),
516
+ params=self.params.new_child(params),
517
+ **kwargs,
518
+ )
519
+ return response
@@ -0,0 +1,6 @@
1
+ # -*- coding: utf-8 -*-
2
+ # src/apytizer/engines/__init__.py
3
+
4
+ # Local Imports
5
+ from .abstract_engine import *
6
+ from .http_engine import *
@@ -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