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
@@ -0,0 +1,85 @@
1
+ # -*- coding: utf-8 -*-
2
+ # src/apytizer/base/model.py
3
+ """Base model class.
4
+
5
+ This module defines the implementation of a base model class.
6
+
7
+ """
8
+
9
+ # Standard Library Imports
10
+ from __future__ import annotations
11
+ from typing import Any
12
+ from typing import Generator
13
+ from typing import Hashable
14
+ from typing import Mapping
15
+ from typing import Tuple
16
+
17
+ # Local Imports
18
+ from .abstract_model import AbstractModel
19
+ from .. import states
20
+
21
+ __all__ = ["BaseModel"]
22
+
23
+
24
+ class BaseModel(AbstractModel):
25
+ """Implements a stateful model.
26
+
27
+ Args:
28
+ **kwargs: Data with which to set model state.
29
+
30
+ """
31
+
32
+ reference: Hashable
33
+
34
+ def __init__(self, **kwargs: Any):
35
+ self._state = states.LocalState(kwargs)
36
+
37
+ def __contains__(self, key: str) -> bool:
38
+ return key in self._state
39
+
40
+ def __eq__(self, other: object) -> bool:
41
+ return (
42
+ other.reference == self.reference
43
+ if isinstance(other, BaseModel)
44
+ else False
45
+ )
46
+
47
+ def __hash__(self) -> int:
48
+ return hash(self.reference)
49
+
50
+ def __getattr__(self, name: str) -> Any:
51
+ attr = self._state.get(name)
52
+ if not attr:
53
+ cls = self.__class__.__name__
54
+ message = f"type object '{cls!s}' has no attribute '{name!s}'"
55
+ raise AttributeError(message)
56
+
57
+ return attr
58
+
59
+ def __getitem__(self, key: str) -> Any:
60
+ value = self._state[key]
61
+ return value
62
+
63
+ def __iter__(self) -> Generator[Tuple[str, Any], None, None]:
64
+ yield from self._state.items()
65
+
66
+ def __repr__(self) -> str:
67
+ return self.__class__.__name__
68
+
69
+ def update(self, __m: Mapping[str, Any], **kwargs: Any) -> None:
70
+ """Update local state with provided data.
71
+
72
+ Args:
73
+ __m (optional): Mapping with which to update local state.
74
+ **kwargs: Data with which to update local state.
75
+
76
+ """
77
+ self._state.update(__m, **kwargs)
78
+
79
+ def rollback(self) -> None:
80
+ """Rollback changes to local state."""
81
+ self._state.rollback()
82
+
83
+ def save(self) -> None:
84
+ """Save changes to local state."""
85
+ self._state.save()
apytizer/protocols.py ADDED
@@ -0,0 +1,38 @@
1
+ # -*- coding: utf-8 -*-
2
+ # src/apytizer/protocols.py
3
+
4
+ # Standard Library Imports
5
+ import enum
6
+ import re
7
+ from typing import Optional
8
+
9
+ __all__ = ["Protocol", "get_protocol"]
10
+
11
+
12
+ @enum.unique
13
+ class Protocol(str, enum.Enum):
14
+ """Implements standard application layer protocols."""
15
+
16
+ HTTP = "http"
17
+ HTTPS = "https"
18
+
19
+
20
+ # ----------------------------------------------------------------------------
21
+ # Selectors
22
+ # ----------------------------------------------------------------------------
23
+ def get_protocol(
24
+ url: str, default: Optional[Protocol] = None
25
+ ) -> Optional[Protocol]:
26
+ """Get protocol from URL.
27
+
28
+ Args:
29
+ url: URL from which to get protocol.
30
+ default (optional): Default protocol. Default ``None``.
31
+
32
+ Returns:
33
+ Protocol.
34
+
35
+ """
36
+ match = re.match(r"\w+(?=://)", url, re.I)
37
+ result = Protocol(match.group(0)) if match else default
38
+ return result
@@ -0,0 +1,6 @@
1
+ # -*- coding: utf-8 -*-
2
+ # src/apytizer/repositories/__init__.py
3
+
4
+ # Local Imports
5
+ from .abstract_repository import *
6
+ from .managed_repository import *
@@ -0,0 +1,81 @@
1
+ # -*- coding: utf-8 -*-
2
+ # src/apytizer/repositories/abstract_repository.py
3
+ """Abstract repository class interface.
4
+
5
+ This module defines an abstract repository 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 List
14
+ from typing import TYPE_CHECKING
15
+
16
+ # Local Imports
17
+ if TYPE_CHECKING:
18
+ from ..models import AbstractModel
19
+
20
+ __all__ = ["AbstractRepository"]
21
+
22
+
23
+ class AbstractRepository(abc.ABC):
24
+ """Represents an abstract repository."""
25
+
26
+ @property
27
+ def objects(self) -> List[AbstractModel]:
28
+ """Objects in repository."""
29
+ raise NotImplementedError
30
+
31
+ @abc.abstractmethod
32
+ def add(self, obj: AbstractModel, /) -> None:
33
+ """Abstract method to add an object to repository.
34
+
35
+ This method only adds an object to the local state of the repository.
36
+ New objects are not sent to the associated endpoint until the client
37
+ commits changes to the repository.
38
+
39
+ Args:
40
+ obj: Instance of an abstract model subclass.
41
+
42
+ """
43
+ raise NotImplementedError
44
+
45
+ @abc.abstractmethod
46
+ def get(self, ref: Any, *args: Any, **kwargs: Any) -> AbstractModel:
47
+ """Abstract method to get an object from the repository.
48
+
49
+ If the object is not found in local state, a request is send to the
50
+ associated endpoint.
51
+
52
+ Args:
53
+ ref: Reference to object.
54
+ *args: Positional arguments.
55
+ **kwargs: Keyword arguments.
56
+
57
+ Returns:
58
+ Instance of an abstract model subclass.
59
+
60
+ """
61
+ raise NotImplementedError
62
+
63
+ @abc.abstractmethod
64
+ def remove(self, obj: AbstractModel, /) -> None:
65
+ """Abstract method to remove an object from repository.
66
+
67
+ Args:
68
+ obj: Instance of an abstract model subclass.
69
+
70
+ """
71
+ raise NotImplementedError
72
+
73
+ @abc.abstractmethod
74
+ def commit(self) -> None:
75
+ """Abstract method for committing changes to objects."""
76
+ raise NotImplementedError
77
+
78
+ @abc.abstractmethod
79
+ def rollback(self) -> None:
80
+ """Abstract method for rolling back changes to objects."""
81
+ raise NotImplementedError
@@ -0,0 +1,92 @@
1
+ # -*- coding: utf-8 -*-
2
+ # src/apytizer/repositories/managed_repository.py
3
+ """Managed Repository Class.
4
+
5
+ This module defines a managed repository class.
6
+
7
+ """
8
+
9
+ # Standard Library Imports
10
+ from typing import Any
11
+ from typing import List
12
+ from typing import Optional
13
+ from typing import Set
14
+ from typing import TYPE_CHECKING
15
+
16
+ # Local Imports
17
+ from .abstract_repository import AbstractRepository
18
+
19
+ if TYPE_CHECKING:
20
+ from ..managers import AbstractManager
21
+ from ..models import AbstractModel
22
+
23
+ __all__ = ["ManagedRepository"]
24
+
25
+
26
+ class ManagedRepository(AbstractRepository):
27
+ """Implements a managed repository.
28
+
29
+ Args:
30
+ manager: Manager.
31
+ objects (optional): Objects to include in repository.
32
+
33
+ """
34
+
35
+ def __init__(
36
+ self,
37
+ manager: AbstractManager,
38
+ objects: Optional[List[AbstractModel]] = None,
39
+ ) -> None:
40
+ self._manager = manager
41
+ self._objects: Set[AbstractModel] = set(objects or [])
42
+
43
+ @property
44
+ def objects(self) -> List[AbstractModel]:
45
+ """Objects in repository."""
46
+ result = list(self._objects)
47
+ return result
48
+
49
+ def add(self, obj: AbstractModel) -> None:
50
+ """Add an object to the repository.
51
+
52
+ Args:
53
+ obj: Object to add.
54
+
55
+ """
56
+ self._objects.add(obj)
57
+
58
+ def get(self, ref: Any) -> AbstractModel:
59
+ """Get an object from the repository.
60
+
61
+ Args:
62
+ ref: Reference to object.
63
+
64
+ Returns:
65
+ Object.
66
+
67
+ """
68
+ try:
69
+ result = next(obj for obj in self.objects if obj.reference == ref)
70
+ except StopIteration:
71
+ result = self._manager.read(ref)
72
+ self._objects.add(result)
73
+
74
+ return result
75
+
76
+ def remove(self, obj: AbstractModel) -> None:
77
+ """Remove an object from the repository.
78
+
79
+ Args:
80
+ obj: Object to remove.
81
+
82
+ """
83
+ self._manager.delete(obj)
84
+ self._objects.discard(obj)
85
+
86
+ def commit(self) -> None:
87
+ """Commit changes to objects in the repository."""
88
+ raise NotImplementedError
89
+
90
+ def rollback(self) -> None:
91
+ """Rollback changes to objects in the repository."""
92
+ raise NotImplementedError
@@ -0,0 +1,6 @@
1
+ # -*- coding: utf-8 -*-
2
+ # src/apytizer/routes/__init__.py
3
+
4
+ # Local Imports
5
+ from .abstract_route import *
6
+ from .base_route import *
@@ -0,0 +1,32 @@
1
+ # -*- coding: utf-8 -*-
2
+ # src/apytizer/routes/abstract_route.py
3
+
4
+ # Standard Library Imports
5
+ from __future__ import annotations
6
+ import abc
7
+
8
+ __all__ = ["AbstractRoute"]
9
+
10
+
11
+ class AbstractRoute(abc.ABC):
12
+ """Represents an abstract route."""
13
+
14
+ @abc.abstractmethod
15
+ def __add__(self, other: object) -> AbstractRoute:
16
+ raise NotImplementedError
17
+
18
+ @abc.abstractmethod
19
+ def __eq__(self, other: object) -> bool:
20
+ raise NotImplementedError
21
+
22
+ @abc.abstractmethod
23
+ def __hash__(self) -> int:
24
+ raise NotImplementedError
25
+
26
+ @abc.abstractmethod
27
+ def __str__(self) -> str:
28
+ raise NotImplementedError
29
+
30
+ @abc.abstractmethod
31
+ def __truediv__(self, other: object) -> AbstractRoute:
32
+ raise NotImplementedError
@@ -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