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.
- 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 +460 -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.py +70 -12
- 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 +171 -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 +376 -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.1b2.dist-info/METADATA +41 -0
- apytizer-0.0.1b2.dist-info/RECORD +60 -0
- {apytizer-0.0.1a0.dist-info → apytizer-0.0.1b2.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/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.1b2.dist-info/licenses}/LICENSE +0 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# src/apytizer/routes/base_route.py
|
|
3
|
+
|
|
4
|
+
# Standard Library Imports
|
|
5
|
+
from typing import List
|
|
6
|
+
|
|
7
|
+
# Local Imports
|
|
8
|
+
from .abstract_route import AbstractRoute
|
|
9
|
+
|
|
10
|
+
__all__ = ["Route"]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Route(AbstractRoute):
|
|
14
|
+
"""Implements a route.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
__value: Value.
|
|
18
|
+
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(self, __value: str = "/", /) -> None:
|
|
22
|
+
if not isinstance(__value, (AbstractRoute, str)): # type: ignore
|
|
23
|
+
message = f"expected type 'str', got {type(__value)} instead"
|
|
24
|
+
raise TypeError(message)
|
|
25
|
+
|
|
26
|
+
self._value = str(__value).strip("/") + "/"
|
|
27
|
+
|
|
28
|
+
def __eq__(self, other: object) -> bool:
|
|
29
|
+
if not isinstance(other, (AbstractRoute, str)):
|
|
30
|
+
return False
|
|
31
|
+
|
|
32
|
+
other_segments = get_segments(str(other))
|
|
33
|
+
self_segments = get_segments(self._value)
|
|
34
|
+
result = segments_are_equal(other_segments, self_segments)
|
|
35
|
+
return result
|
|
36
|
+
|
|
37
|
+
def __hash__(self) -> int:
|
|
38
|
+
return hash(self._value.lower())
|
|
39
|
+
|
|
40
|
+
def __len__(self) -> int:
|
|
41
|
+
return len(get_segments(self._value))
|
|
42
|
+
|
|
43
|
+
def __str__(self) -> str:
|
|
44
|
+
return self._value
|
|
45
|
+
|
|
46
|
+
def __gt__(self, other: object) -> bool:
|
|
47
|
+
if not isinstance(other, (AbstractRoute, str)):
|
|
48
|
+
return False
|
|
49
|
+
|
|
50
|
+
self_segments = get_segments(self._value)
|
|
51
|
+
other_segments = get_segments(str(other))
|
|
52
|
+
result = tuple(self_segments) > tuple(other_segments)
|
|
53
|
+
return result
|
|
54
|
+
|
|
55
|
+
def __lt__(self, other: object) -> bool:
|
|
56
|
+
if not isinstance(other, (AbstractRoute, str)):
|
|
57
|
+
return False
|
|
58
|
+
|
|
59
|
+
self_segments = get_segments(self._value)
|
|
60
|
+
other_segments = get_segments(str(other))
|
|
61
|
+
result = tuple(self_segments) < tuple(other_segments)
|
|
62
|
+
return result
|
|
63
|
+
|
|
64
|
+
def __add__(self, other: object) -> AbstractRoute:
|
|
65
|
+
if not isinstance(other, str):
|
|
66
|
+
message = f"expected type 'str', got {type(other)} instead"
|
|
67
|
+
raise TypeError(message)
|
|
68
|
+
|
|
69
|
+
result = Route(self._value + other.strip("/"))
|
|
70
|
+
return result
|
|
71
|
+
|
|
72
|
+
def __truediv__(self, other: object) -> AbstractRoute:
|
|
73
|
+
if not isinstance(other, str):
|
|
74
|
+
message = f"expected type 'str', got {type(other)} instead"
|
|
75
|
+
raise TypeError(message)
|
|
76
|
+
|
|
77
|
+
result = Route(self._value + other.strip("/"))
|
|
78
|
+
return result
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
# ----------------------------------------------------------------------------
|
|
82
|
+
# Helpers
|
|
83
|
+
# ----------------------------------------------------------------------------
|
|
84
|
+
def count_segments(__value: str, /) -> int:
|
|
85
|
+
"""Get number of segments.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
__value: Value for which to get number of segments.
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
Number of segments.
|
|
92
|
+
|
|
93
|
+
"""
|
|
94
|
+
result = len(get_segments(__value))
|
|
95
|
+
return result
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def get_segments(__value: str, /) -> List[str]:
|
|
99
|
+
"""Get segments of string seperated by slashes.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
__value: Value for which to get segments.
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
Segments.
|
|
106
|
+
|
|
107
|
+
"""
|
|
108
|
+
if not isinstance(__value, str): # type: ignore
|
|
109
|
+
message = f"expected type 'str', got {type(__value)} instead"
|
|
110
|
+
raise TypeError(message)
|
|
111
|
+
|
|
112
|
+
result = __value.strip("/").split("/")
|
|
113
|
+
return result
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def segments_are_equal(
|
|
117
|
+
first: List[str], second: List[str], placeholder: str = r"{}"
|
|
118
|
+
) -> bool:
|
|
119
|
+
"""Check whether segments are equal.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
first: First list of segments.
|
|
123
|
+
second: Second list of segments.
|
|
124
|
+
placeholder: Placeholder. Default ``{}``.
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
Whether segments are equal.
|
|
128
|
+
|
|
129
|
+
"""
|
|
130
|
+
if len(first) != len(second):
|
|
131
|
+
return False
|
|
132
|
+
|
|
133
|
+
result = all(
|
|
134
|
+
left.lower() == right.lower()
|
|
135
|
+
for left, right in zip(first, second)
|
|
136
|
+
if right != placeholder
|
|
137
|
+
)
|
|
138
|
+
return result
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# src/apytizer/sessions/__init__.py
|
|
3
|
+
|
|
4
|
+
# Standard Library Imports
|
|
5
|
+
from typing import Type
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
# Local Imports
|
|
9
|
+
from .abstract_session import *
|
|
10
|
+
from .requests_session import *
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from ..engines import AbstractEngine
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class sessionmaker:
|
|
17
|
+
"""Implements a sessionmaker."""
|
|
18
|
+
|
|
19
|
+
def __init__(
|
|
20
|
+
self, class_: Type[RequestsSession] = RequestsSession
|
|
21
|
+
) -> None:
|
|
22
|
+
self._session_type = class_
|
|
23
|
+
|
|
24
|
+
def __call__(self, __engine: "AbstractEngine", /) -> RequestsSession:
|
|
25
|
+
result = self._session_type(
|
|
26
|
+
adapters=getattr(__engine, "adapters", None),
|
|
27
|
+
auth=getattr(__engine, "auth", None),
|
|
28
|
+
cert=getattr(__engine, "cert", None),
|
|
29
|
+
proxies=getattr(__engine, "proxies", None),
|
|
30
|
+
stream=getattr(__engine, "stream", False),
|
|
31
|
+
verify=getattr(__engine, "verify", True),
|
|
32
|
+
)
|
|
33
|
+
return result
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# src/apytizer/sessions/abstract_session.py
|
|
3
|
+
"""Abstract Session Class.
|
|
4
|
+
|
|
5
|
+
This module defines an abstract session class which provides an interface
|
|
6
|
+
for subclasses to implement.
|
|
7
|
+
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
# Standard Library Imports
|
|
11
|
+
import abc
|
|
12
|
+
from typing import Any
|
|
13
|
+
from typing import TYPE_CHECKING
|
|
14
|
+
|
|
15
|
+
# Third-Party Imports
|
|
16
|
+
import requests
|
|
17
|
+
from requests.adapters import HTTPAdapter
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
from ..protocols import Protocol
|
|
21
|
+
|
|
22
|
+
__all__ = ["AbstractSession"]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class AbstractSession(abc.ABC):
|
|
26
|
+
"""Represents an abstract session."""
|
|
27
|
+
|
|
28
|
+
@abc.abstractmethod
|
|
29
|
+
def start(self) -> None:
|
|
30
|
+
"""Starts session."""
|
|
31
|
+
raise NotImplementedError
|
|
32
|
+
|
|
33
|
+
@abc.abstractmethod
|
|
34
|
+
def close(self) -> None:
|
|
35
|
+
"""Destroys session."""
|
|
36
|
+
raise NotImplementedError
|
|
37
|
+
|
|
38
|
+
@abc.abstractmethod
|
|
39
|
+
def mount(self, protocol: "Protocol", adapter: HTTPAdapter) -> None:
|
|
40
|
+
"""Registers a connection adapter to a protocol prefix.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
protocol: Protocol prefix on which to mount adapter.
|
|
44
|
+
adapter: HTTP adapter to apply to connection.
|
|
45
|
+
|
|
46
|
+
"""
|
|
47
|
+
raise NotImplementedError
|
|
48
|
+
|
|
49
|
+
@abc.abstractmethod
|
|
50
|
+
def send(
|
|
51
|
+
self, __request: requests.Request, /, **kwargs: Any
|
|
52
|
+
) -> requests.Response:
|
|
53
|
+
"""Send an HTTP request.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
__request: Request to send.
|
|
57
|
+
**kwargs: Keyword arguments.
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
Response.
|
|
61
|
+
|
|
62
|
+
"""
|
|
63
|
+
raise NotImplementedError
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# src/apytizer/sessions/requests_session.py
|
|
3
|
+
"""Requests Session Class.
|
|
4
|
+
|
|
5
|
+
This module defines the requests session class implementation.
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
# Standard Library Imports
|
|
10
|
+
import logging
|
|
11
|
+
from typing import Any
|
|
12
|
+
from typing import Dict
|
|
13
|
+
from typing import MutableMapping
|
|
14
|
+
from typing import Optional
|
|
15
|
+
from typing import Tuple
|
|
16
|
+
from typing import Union
|
|
17
|
+
from typing import final
|
|
18
|
+
|
|
19
|
+
# Third-Party Imports
|
|
20
|
+
import requests
|
|
21
|
+
from requests.auth import AuthBase
|
|
22
|
+
from requests.adapters import HTTPAdapter
|
|
23
|
+
|
|
24
|
+
# Local Imports
|
|
25
|
+
from .abstract_session import AbstractSession
|
|
26
|
+
from ..protocols import Protocol
|
|
27
|
+
|
|
28
|
+
__all__ = ["RequestsSession"]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
log = logging.getLogger("apytizer")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class RequestsSession(AbstractSession):
|
|
35
|
+
"""Implements a requests session.
|
|
36
|
+
|
|
37
|
+
The `RequestsSession` class provides `start` and `close` methods for manual
|
|
38
|
+
control of the session.
|
|
39
|
+
|
|
40
|
+
A session instance can also be used as a context manager.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
adapters (optional): Connection adapters. Default ``None``.
|
|
44
|
+
auth (optional): Authentication header. default ``None``.
|
|
45
|
+
cert (optional): Client certificate. Default ``None``.
|
|
46
|
+
proxies (optional): Protocols mapped to proxy URLs. Default ``None``.
|
|
47
|
+
stream (optional): Whether to stream response content. Default ``False``.
|
|
48
|
+
verify (optional): Whether to verify certificate. Default ``True``.
|
|
49
|
+
|
|
50
|
+
.. Requests Documentation:
|
|
51
|
+
https://docs.python-requests.org/en/latest/api/#request-sessions
|
|
52
|
+
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
def __init__(
|
|
56
|
+
self,
|
|
57
|
+
*,
|
|
58
|
+
adapters: Optional[Dict[Protocol, HTTPAdapter]] = None,
|
|
59
|
+
auth: Optional[Union[AuthBase, Tuple[str, str]]] = None,
|
|
60
|
+
cert: Optional[Union[str, Tuple[str, str]]] = None,
|
|
61
|
+
proxies: Optional[MutableMapping[str, str]] = None,
|
|
62
|
+
stream: Optional[bool] = False,
|
|
63
|
+
verify: Optional[bool] = True,
|
|
64
|
+
) -> None:
|
|
65
|
+
self._session = requests.Session()
|
|
66
|
+
self._session.auth = auth
|
|
67
|
+
self._session.cert = cert
|
|
68
|
+
self._session.proxies = proxies # type: ignore
|
|
69
|
+
self._session.stream = stream # type: ignore
|
|
70
|
+
self._session.verify = verify # type: ignore
|
|
71
|
+
|
|
72
|
+
if adapters is not None:
|
|
73
|
+
for protocol, adapter in adapters.items():
|
|
74
|
+
self._session.mount(protocol, adapter)
|
|
75
|
+
|
|
76
|
+
@final
|
|
77
|
+
def __enter__(self) -> AbstractSession:
|
|
78
|
+
"""Starts session as context manager."""
|
|
79
|
+
self.start()
|
|
80
|
+
return self
|
|
81
|
+
|
|
82
|
+
@final
|
|
83
|
+
def __exit__(self, *_) -> None:
|
|
84
|
+
"""Stops session as context manager."""
|
|
85
|
+
self.close()
|
|
86
|
+
|
|
87
|
+
def start(self) -> None:
|
|
88
|
+
"""Starts session."""
|
|
89
|
+
log.debug("Starting session...")
|
|
90
|
+
|
|
91
|
+
def close(self, *_: Any) -> None:
|
|
92
|
+
"""Destroys session."""
|
|
93
|
+
self._session.close()
|
|
94
|
+
log.debug("Session closed")
|
|
95
|
+
|
|
96
|
+
def mount(self, protocol: Protocol, adapter: HTTPAdapter) -> None:
|
|
97
|
+
"""Registers connection adapter to protocol prefix.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
protocol: Protocol on which to mount adapter.
|
|
101
|
+
adapter: HTTP adapter to apply to connection.
|
|
102
|
+
|
|
103
|
+
"""
|
|
104
|
+
self._session.mount(f"{protocol.value!s}://", adapter)
|
|
105
|
+
log.debug(
|
|
106
|
+
"Mounted %(adapter)s adapter to %(protocol)s protocol",
|
|
107
|
+
{"adapter": adapter, "protocol": protocol.value},
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
def send(
|
|
111
|
+
self, __request: requests.Request, /, **kwargs: Any
|
|
112
|
+
) -> requests.Response:
|
|
113
|
+
"""Send an HTTP request.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
__request: Request to send.
|
|
117
|
+
**kwargs (optional): Keyword arguments.
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
Response object.
|
|
121
|
+
|
|
122
|
+
"""
|
|
123
|
+
request = self._session.prepare_request(__request)
|
|
124
|
+
response = self._session.send(request, **kwargs)
|
|
125
|
+
return response
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# src/apytizer/states/abstract_state.py
|
|
3
|
+
"""Abstract State Class Interface.
|
|
4
|
+
|
|
5
|
+
This module defines an abstract state 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 Any
|
|
14
|
+
from typing import Generator
|
|
15
|
+
from typing import Mapping
|
|
16
|
+
from typing import Optional
|
|
17
|
+
|
|
18
|
+
__all__ = ["AbstractState"]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class AbstractState(abc.ABC):
|
|
22
|
+
"""Represents an abstract state."""
|
|
23
|
+
|
|
24
|
+
@abc.abstractmethod
|
|
25
|
+
def __contains__(self, key: str) -> bool:
|
|
26
|
+
raise NotImplementedError
|
|
27
|
+
|
|
28
|
+
@abc.abstractmethod
|
|
29
|
+
def __eq__(self, other: object) -> bool:
|
|
30
|
+
raise NotImplementedError
|
|
31
|
+
|
|
32
|
+
@abc.abstractmethod
|
|
33
|
+
def __getitem__(self, key: str) -> Any:
|
|
34
|
+
raise NotImplementedError
|
|
35
|
+
|
|
36
|
+
@abc.abstractmethod
|
|
37
|
+
def __setitem__(self, key: str, value: Any) -> None:
|
|
38
|
+
raise NotImplementedError
|
|
39
|
+
|
|
40
|
+
@abc.abstractmethod
|
|
41
|
+
def __iter__(self) -> Generator[Any, None, None]:
|
|
42
|
+
raise NotImplementedError
|
|
43
|
+
|
|
44
|
+
@abc.abstractmethod
|
|
45
|
+
def get(self, key: str) -> Any:
|
|
46
|
+
"""Abstract method for getting an item from state."""
|
|
47
|
+
raise NotImplementedError
|
|
48
|
+
|
|
49
|
+
@abc.abstractmethod
|
|
50
|
+
def items(self) -> Any:
|
|
51
|
+
"""Abstract method for getting items from state."""
|
|
52
|
+
raise NotImplementedError
|
|
53
|
+
|
|
54
|
+
@abc.abstractmethod
|
|
55
|
+
def update(
|
|
56
|
+
self,
|
|
57
|
+
__m: Optional[Mapping[str, Any]] = None,
|
|
58
|
+
**kwargs: Any,
|
|
59
|
+
) -> None:
|
|
60
|
+
"""Abstract method for updating state."""
|
|
61
|
+
raise NotImplementedError
|
|
62
|
+
|
|
63
|
+
@abc.abstractmethod
|
|
64
|
+
def rollback(self) -> None:
|
|
65
|
+
"""Abstract method for rolling back changes to state."""
|
|
66
|
+
raise NotImplementedError
|
|
67
|
+
|
|
68
|
+
@abc.abstractmethod
|
|
69
|
+
def save(self) -> None:
|
|
70
|
+
"""Abstract method for saving changes to state."""
|
|
71
|
+
raise NotImplementedError
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# src/apytizer/states/base_state.py
|
|
3
|
+
"""Base State Class.
|
|
4
|
+
|
|
5
|
+
This module defines the implementation of a base state class.
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
# Standard Library Imports
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
import collections
|
|
12
|
+
from typing import Any
|
|
13
|
+
from typing import Dict
|
|
14
|
+
from typing import Generator
|
|
15
|
+
from typing import Mapping
|
|
16
|
+
from typing import Optional
|
|
17
|
+
from typing import Tuple
|
|
18
|
+
|
|
19
|
+
# Local Imports
|
|
20
|
+
from . import AbstractState
|
|
21
|
+
from .. import utils
|
|
22
|
+
|
|
23
|
+
__all__ = ["LocalState"]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class LocalState(AbstractState):
|
|
27
|
+
"""Class implements a local state."""
|
|
28
|
+
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
base: Optional[Dict[str, Any]] = None,
|
|
32
|
+
default: Optional[Dict[str, Any]] = None,
|
|
33
|
+
) -> None:
|
|
34
|
+
self._state = collections.ChainMap(base or {}, default or {})
|
|
35
|
+
|
|
36
|
+
def __contains__(self, key: str) -> bool:
|
|
37
|
+
return key in self._state
|
|
38
|
+
|
|
39
|
+
def __eq__(self, other: object) -> bool:
|
|
40
|
+
return (
|
|
41
|
+
dict(other) == dict(self)
|
|
42
|
+
if isinstance(other, AbstractState)
|
|
43
|
+
else False
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
def __getitem__(self, key: str) -> Any:
|
|
47
|
+
result = self.get(key)
|
|
48
|
+
return result
|
|
49
|
+
|
|
50
|
+
def __setitem__(self, key: str, value: Any) -> None:
|
|
51
|
+
self._state = utils.deep_set(self._state, key, value)
|
|
52
|
+
|
|
53
|
+
def __iter__(self) -> Generator[Tuple[str, Any], None, None]:
|
|
54
|
+
yield from self._state.items()
|
|
55
|
+
|
|
56
|
+
def get(self, key: str) -> Any:
|
|
57
|
+
"""Get an item from state.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
key: Key.
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
Value of key in state.
|
|
64
|
+
|
|
65
|
+
"""
|
|
66
|
+
if not isinstance(key, str): # type: ignore
|
|
67
|
+
message = f"expected type 'str', got {type(key)} instead"
|
|
68
|
+
raise TypeError(message)
|
|
69
|
+
|
|
70
|
+
result = utils.deep_get(self._state, key)
|
|
71
|
+
return result
|
|
72
|
+
|
|
73
|
+
def items(self) -> Any:
|
|
74
|
+
"""Get items from state."""
|
|
75
|
+
results = self._state.items()
|
|
76
|
+
return results
|
|
77
|
+
|
|
78
|
+
def update(
|
|
79
|
+
self,
|
|
80
|
+
__m: Optional[Mapping[str, Any]] = None,
|
|
81
|
+
**kwargs: Any,
|
|
82
|
+
) -> None:
|
|
83
|
+
"""Update state.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
__m (optional): Mapping. Default ``None``.
|
|
87
|
+
**kwargs: Keyword arguments.
|
|
88
|
+
|
|
89
|
+
"""
|
|
90
|
+
self._state.update(__m or {}, **kwargs)
|
|
91
|
+
|
|
92
|
+
def rollback(self) -> None:
|
|
93
|
+
"""Roll back changes to state."""
|
|
94
|
+
self._state.clear()
|
|
95
|
+
|
|
96
|
+
def save(self) -> None:
|
|
97
|
+
"""Save changes to state."""
|
|
98
|
+
if self._state.maps[0]: # type: ignore
|
|
99
|
+
self._state = self._state.new_child() # type: ignore
|
apytizer/utils/__init__.py
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
|
+
# src/apytizer/utils/__init__.py
|
|
2
3
|
|
|
3
|
-
#
|
|
4
|
-
|
|
5
|
-
from .
|
|
6
|
-
from .
|
|
4
|
+
# Local Imports
|
|
5
|
+
from .caching import *
|
|
6
|
+
from .dictionaries import *
|
|
7
|
+
from .errors import *
|
|
8
|
+
from .iterables import *
|
|
9
|
+
from .objects import *
|
|
10
|
+
from .strings import *
|
|
11
|
+
from .typing import *
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
# Standard Library Import
|
|
4
|
+
from typing import Any
|
|
5
|
+
from typing import Callable
|
|
6
|
+
from typing import Hashable
|
|
7
|
+
from typing import Tuple
|
|
8
|
+
|
|
9
|
+
# Third-Party Imports
|
|
10
|
+
from cachetools.keys import hashkey
|
|
11
|
+
|
|
12
|
+
__all__ = ["generate_key"]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def generate_key(*tags: str) -> Callable[..., Tuple[Hashable, ...]]:
|
|
16
|
+
"""Generates a hashable key for caching values.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
*tags: Tags.
|
|
20
|
+
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def hash_parameters(*args: Any, **kwargs: Any) -> Tuple[Hashable, ...]:
|
|
24
|
+
"""Hashes function parameters.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
*args: Positional arguments.
|
|
28
|
+
**kwargs: Keyword arguments.
|
|
29
|
+
|
|
30
|
+
Return:
|
|
31
|
+
Cache Key.
|
|
32
|
+
|
|
33
|
+
"""
|
|
34
|
+
result = hashkey(
|
|
35
|
+
*tags, *args, *[f"{k!s}={v!s}" for k, v in sorted(kwargs.items())]
|
|
36
|
+
)
|
|
37
|
+
return result
|
|
38
|
+
|
|
39
|
+
return hash_parameters
|