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.
Files changed (76) 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 +461 -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_response.py +93 -0
  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 +129 -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 +375 -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.1b1.dist-info/METADATA +41 -0
  58. apytizer-0.0.1b1.dist-info/RECORD +60 -0
  59. {apytizer-0.0.1a0.dist-info → apytizer-0.0.1b1.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/decorators/json.py +0 -35
  71. apytizer/utils/generate_key.py +0 -18
  72. apytizer/utils/merge.py +0 -19
  73. apytizer-0.0.1a0.dist-info/METADATA +0 -27
  74. apytizer-0.0.1a0.dist-info/RECORD +0 -25
  75. apytizer-0.0.1a0.dist-info/top_level.txt +0 -1
  76. {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 requests
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
- def confirm_connection(func) -> Callable:
16
- """
17
- Confirms successful connection to API.
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 requests.exceptions.ConnectionError as error:
32
- log.critical("Failed to establish a connection")
33
- log.debug(f"Error message: {error}")
34
- return error
50
+ except ConnectionError as error:
51
+ handle_connection_error(error)
52
+ return error.response
35
53
 
36
- except requests.exceptions.Timeout as error:
37
- log.critical("request timed out")
38
- log.debug(f"Error message: {error}")
39
- return error
54
+ except Timeout as error:
55
+ handle_timeout_error(error)
56
+ return error.response
40
57
 
41
- else:
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 logging
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
- Returns:
18
- Function wrapper.
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
- @functools.wraps(func)
22
- def wrapper(*args, reducer: Callable, callback: Callable, **kwargs):
23
- """
24
- Wrapper applied to decorated function.
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
- reducer (Callable): Function to update state with pagination metadata.
28
- callback (Callable): Function which returns True once request is completed.
29
- Stop condition must depend on state or pagination metadata.
30
- *args
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
- while not completed:
41
- if state.get('params'):
42
- kwargs.update({'params': state.get('params')})
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
- if state.get('data'):
45
- kwargs.update({'data': state.get('data')})
59
+ """
60
+ completed = False
61
+ state: Dict[str, Any] = {"args": args, "kwargs": kwargs}
46
62
 
47
- response = func(*args, **kwargs)
48
- yield response
63
+ while not completed:
64
+ response = func(*state["args"], **state["kwargs"])
65
+ yield response
49
66
 
50
- state = reducer(state, response)
51
- completed = callback(state, response)
67
+ state = self._reducer(state, response)
68
+ completed = self._callback(state, response)
52
69
 
53
- return wrapper
70
+ functools.update_wrapper(wrapper, func)
71
+ return wrapper
@@ -0,0 +1,6 @@
1
+ # -*- coding: utf-8 -*-
2
+ # src/apytizer/endpoints/__init__.py
3
+
4
+ # Local Imports
5
+ from .abstract_endpoint import *
6
+ from .web_endpoint import *
@@ -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