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
|
@@ -1,20 +1,28 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
|
+
# src/apytizer/decorators/connection.py
|
|
2
3
|
|
|
3
4
|
# Standard Library Imports
|
|
4
5
|
import functools
|
|
5
6
|
import logging
|
|
7
|
+
from typing import Any
|
|
6
8
|
from typing import Callable
|
|
9
|
+
from typing import Optional
|
|
7
10
|
|
|
8
11
|
# Third-Party Imports
|
|
9
|
-
import
|
|
12
|
+
from requests import Response
|
|
13
|
+
from requests.exceptions import ConnectionError
|
|
14
|
+
from requests.exceptions import Timeout
|
|
10
15
|
|
|
16
|
+
__all__ = ["confirm_connection"]
|
|
11
17
|
|
|
12
|
-
log = logging.getLogger(__name__)
|
|
13
18
|
|
|
19
|
+
log = logging.getLogger("apytizer")
|
|
14
20
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
21
|
+
|
|
22
|
+
def confirm_connection(
|
|
23
|
+
func: Callable[..., Optional[Response]],
|
|
24
|
+
) -> Callable[..., Optional[Response]]:
|
|
25
|
+
"""Confirms successful connection to API.
|
|
18
26
|
|
|
19
27
|
Args:
|
|
20
28
|
func: Function to decorate.
|
|
@@ -23,26 +31,53 @@ def confirm_connection(func) -> Callable:
|
|
|
23
31
|
Wrapped function.
|
|
24
32
|
|
|
25
33
|
"""
|
|
34
|
+
|
|
26
35
|
@functools.wraps(func)
|
|
27
|
-
def wrapper(self, *args, **kwargs):
|
|
36
|
+
def wrapper(self: Any, *args: Any, **kwargs: Any) -> Optional[Response]:
|
|
37
|
+
"""Wrapper applied to decorated function.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
*args: Positional arguments to pass to wrapped function.
|
|
41
|
+
**kwargs: Keyword arguments to pass to wrapped function.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
Response.
|
|
45
|
+
|
|
46
|
+
"""
|
|
28
47
|
try:
|
|
29
48
|
response = func(self, *args, **kwargs)
|
|
30
49
|
|
|
31
|
-
except
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
return error
|
|
50
|
+
except ConnectionError as error:
|
|
51
|
+
handle_connection_error(error)
|
|
52
|
+
return error.response
|
|
35
53
|
|
|
36
|
-
except
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
return error
|
|
54
|
+
except Timeout as error:
|
|
55
|
+
handle_timeout_error(error)
|
|
56
|
+
return error.response
|
|
40
57
|
|
|
41
|
-
|
|
42
|
-
log.debug(
|
|
43
|
-
"Response received with status code %(status)",
|
|
44
|
-
{'status': response.status_code}
|
|
45
|
-
)
|
|
46
|
-
return response
|
|
58
|
+
return response
|
|
47
59
|
|
|
60
|
+
functools.update_wrapper(wrapper, func)
|
|
48
61
|
return wrapper
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def handle_connection_error(error: ConnectionError) -> None:
|
|
65
|
+
"""Handle connection errors.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
error: Connection error.
|
|
69
|
+
|
|
70
|
+
"""
|
|
71
|
+
log.critical("failed to establish a connection")
|
|
72
|
+
log.error(error)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def handle_timeout_error(error: Timeout) -> None:
|
|
76
|
+
"""Handle timeout errors.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
error: Timeout error.
|
|
80
|
+
|
|
81
|
+
"""
|
|
82
|
+
log.critical("request timed out")
|
|
83
|
+
log.error(error)
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# src/apytizer/decorators/json.py
|
|
3
|
+
|
|
4
|
+
# Standard Library Imports
|
|
5
|
+
import functools
|
|
6
|
+
from http import HTTPStatus
|
|
7
|
+
import json
|
|
8
|
+
import logging
|
|
9
|
+
from typing import Any
|
|
10
|
+
from typing import Callable
|
|
11
|
+
|
|
12
|
+
# Third-Party Imports
|
|
13
|
+
from requests import Response
|
|
14
|
+
|
|
15
|
+
# Local Imports
|
|
16
|
+
from ..media_types import MediaType
|
|
17
|
+
|
|
18
|
+
__all__ = ["json_response"]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
log = logging.getLogger("apytizer")
|
|
22
|
+
|
|
23
|
+
# Constants
|
|
24
|
+
CONTENT_TYPE = "Content-Type"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def json_response(func: Callable[..., Response]) -> Callable[..., Any]:
|
|
28
|
+
"""Automatically parses a JSON response.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
func: Function to decorate.
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
Wrapped function.
|
|
35
|
+
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
@functools.wraps(func)
|
|
39
|
+
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
40
|
+
"""Wrapper applied to decorated function.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
*args: Positional arguments to pass to wrapped function.
|
|
44
|
+
**kwargs: Keyword arguments to pass to wrapped function.
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
Parsed JSON or Response.
|
|
48
|
+
|
|
49
|
+
"""
|
|
50
|
+
response: Response = func(*args, **kwargs)
|
|
51
|
+
if response.status_code == HTTPStatus.NO_CONTENT:
|
|
52
|
+
return response
|
|
53
|
+
|
|
54
|
+
content_type = response.headers.get(CONTENT_TYPE)
|
|
55
|
+
if not content_type or MediaType.APPLICATION_JSON not in content_type:
|
|
56
|
+
return response
|
|
57
|
+
|
|
58
|
+
result = parse_json_response(response)
|
|
59
|
+
return result
|
|
60
|
+
|
|
61
|
+
functools.update_wrapper(wrapper, func)
|
|
62
|
+
return wrapper
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def parse_json_response(response: Response) -> Any:
|
|
66
|
+
"""Parse JSON response.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
response: JSON response.
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
Parsed data.
|
|
73
|
+
|
|
74
|
+
"""
|
|
75
|
+
try:
|
|
76
|
+
log.debug("Parsing JSON response...")
|
|
77
|
+
result = response.json()
|
|
78
|
+
|
|
79
|
+
except json.JSONDecodeError as error:
|
|
80
|
+
handle_decode_error(error)
|
|
81
|
+
return response
|
|
82
|
+
|
|
83
|
+
return result
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def handle_decode_error(error: json.JSONDecodeError) -> None:
|
|
87
|
+
"""Handle decode errors.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
error: JSONDecodeError error.
|
|
91
|
+
|
|
92
|
+
"""
|
|
93
|
+
log.error(error)
|
|
@@ -1,53 +1,71 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
|
+
# src/apytizer/decorators/pagination.py
|
|
2
3
|
|
|
3
4
|
# Standard Library Imports
|
|
4
5
|
import functools
|
|
5
|
-
import
|
|
6
|
+
from typing import Any
|
|
6
7
|
from typing import Callable
|
|
8
|
+
from typing import Dict
|
|
9
|
+
from typing import Generator
|
|
10
|
+
from typing import TypeVar
|
|
7
11
|
|
|
12
|
+
__all__ = ["pagination"]
|
|
8
13
|
|
|
9
|
-
log = logging.getLogger(__name__)
|
|
10
14
|
|
|
15
|
+
# Custom types
|
|
16
|
+
T = TypeVar("T")
|
|
11
17
|
|
|
12
|
-
def pagination(func) -> Callable:
|
|
13
|
-
"""
|
|
14
|
-
Args:
|
|
15
|
-
func: Decorated function.
|
|
16
18
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
+
class pagination:
|
|
20
|
+
"""Implements pagination for requests to an API endpoint.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
reducer (Callable): Function to update state from response.
|
|
24
|
+
callback (Callable): Function which returns 'True' when request is
|
|
25
|
+
complete, otherwise returns 'False'. Stop condition must depend
|
|
26
|
+
on either state or response.
|
|
19
27
|
|
|
20
28
|
"""
|
|
21
|
-
|
|
22
|
-
def
|
|
23
|
-
|
|
24
|
-
|
|
29
|
+
|
|
30
|
+
def __init__(
|
|
31
|
+
self,
|
|
32
|
+
reducer: Callable[[Dict[str, Any], Any], Dict[str, Any]],
|
|
33
|
+
callback: Callable[[Dict[str, Any], Any], bool],
|
|
34
|
+
) -> None:
|
|
35
|
+
self._reducer = reducer
|
|
36
|
+
self._callback = callback
|
|
37
|
+
|
|
38
|
+
def __call__(
|
|
39
|
+
self, func: Callable[..., T]
|
|
40
|
+
) -> Callable[..., Generator[T, None, None]]:
|
|
41
|
+
"""Wrap function to handle paginated results.
|
|
25
42
|
|
|
26
43
|
Args:
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
**kwargs
|
|
44
|
+
func: Decorated function.
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
Function wrapper.
|
|
32
48
|
|
|
33
49
|
"""
|
|
34
|
-
completed = False
|
|
35
|
-
state = {
|
|
36
|
-
'params': kwargs.pop('params', None),
|
|
37
|
-
'data': kwargs.pop('data', None)
|
|
38
|
-
}
|
|
39
50
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
51
|
+
@functools.wraps(func)
|
|
52
|
+
def wrapper(*args: Any, **kwargs: Any) -> Generator[T, None, None]:
|
|
53
|
+
"""Wrapper applied to decorated function.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
*args: Positional arguments to pass to wrapped function.
|
|
57
|
+
**kwargs: Keyword arguments to pass to wrapped function.
|
|
43
58
|
|
|
44
|
-
|
|
45
|
-
|
|
59
|
+
"""
|
|
60
|
+
completed = False
|
|
61
|
+
state: Dict[str, Any] = {"args": args, "kwargs": kwargs}
|
|
46
62
|
|
|
47
|
-
|
|
48
|
-
|
|
63
|
+
while not completed:
|
|
64
|
+
response = func(*state["args"], **state["kwargs"])
|
|
65
|
+
yield response
|
|
49
66
|
|
|
50
|
-
|
|
51
|
-
|
|
67
|
+
state = self._reducer(state, response)
|
|
68
|
+
completed = self._callback(state, response)
|
|
52
69
|
|
|
53
|
-
|
|
70
|
+
functools.update_wrapper(wrapper, func)
|
|
71
|
+
return wrapper
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# src/apytizer/endpoints/abstract_endpoint.py
|
|
3
|
+
"""Abstract endpoint class.
|
|
4
|
+
|
|
5
|
+
This module defines an abstract endpoint class which provides an interface
|
|
6
|
+
for subclasses to implement. Each of the abstract methods represents
|
|
7
|
+
a standard HTTP request method.
|
|
8
|
+
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
# Standard Library Imports
|
|
12
|
+
import abc
|
|
13
|
+
from typing import Optional
|
|
14
|
+
from typing import TYPE_CHECKING
|
|
15
|
+
|
|
16
|
+
# Local Imports
|
|
17
|
+
from ..connections import AbstractConnection
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
from ..apis import AbstractAPI
|
|
21
|
+
|
|
22
|
+
__all__ = ["AbstractEndpoint"]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class AbstractEndpoint(abc.ABC):
|
|
26
|
+
"""Represents an abstract endpoint."""
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
@abc.abstractmethod
|
|
30
|
+
def api(self) -> "AbstractAPI":
|
|
31
|
+
"""API."""
|
|
32
|
+
raise NotImplementedError
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
@abc.abstractmethod
|
|
36
|
+
def connection(self) -> Optional[AbstractConnection]:
|
|
37
|
+
"""Connection with which to make requests."""
|
|
38
|
+
raise NotImplementedError
|