lsrestclient 3.3.2__py3-none-any.whl → 3.4.1__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.
- lsrestclient/__init__.py +4 -2
- lsrestclient/auth.py +2 -2
- lsrestclient/client.py +54 -38
- lsrestclient/contexts/bearer_token.py +6 -4
- lsrestclient/exceptions/ConnectionError.py +22 -0
- lsrestclient/exceptions/DownStreamError.py +12 -0
- lsrestclient/exceptions/__init__.py +0 -0
- lsrestclient/exceptions/raise_errors.py +44 -0
- lsrestclient/fixtures.py +4 -2
- lsrestclient/lsfastapi.py +11 -8
- lsrestclient/mock.py +16 -7
- lsrestclient/response.py +12 -5
- {lsrestclient-3.3.2.dist-info → lsrestclient-3.4.1.dist-info}/METADATA +13 -16
- lsrestclient-3.4.1.dist-info/RECORD +18 -0
- {lsrestclient-3.3.2.dist-info → lsrestclient-3.4.1.dist-info}/WHEEL +1 -1
- lsrestclient-3.4.1.dist-info/entry_points.txt +2 -0
- lsrestclient/exceptions.py +0 -73
- lsrestclient-3.3.2.dist-info/RECORD +0 -15
- lsrestclient-3.3.2.dist-info/entry_points.txt +0 -3
lsrestclient/__init__.py
CHANGED
lsrestclient/auth.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1
|
-
from lsrestclient import
|
1
|
+
from lsrestclient import DownStreamError, LsRestClient
|
2
2
|
|
3
3
|
|
4
|
-
def auth_am_login(**kwargs) -> str:
|
4
|
+
def auth_am_login(**kwargs: object) -> str:
|
5
5
|
am = LsRestClient.from_env("AM_API_URL", "am", True)
|
6
6
|
r = am.post("/auth/login", body=kwargs)
|
7
7
|
if r.status_code == 200:
|
lsrestclient/client.py
CHANGED
@@ -1,22 +1,20 @@
|
|
1
1
|
import os
|
2
|
+
import re
|
2
3
|
from enum import Enum
|
3
|
-
from typing import
|
4
|
+
from typing import Any, ClassVar, Dict, Optional
|
4
5
|
from unittest.mock import MagicMock
|
5
6
|
|
6
7
|
import lsjsonclasses
|
7
8
|
import requests
|
8
9
|
import requests_cache
|
9
10
|
from requests import Response, Session
|
10
|
-
|
11
|
-
import re
|
12
|
-
|
13
11
|
from requests_cache import CachedSession, SQLiteCache
|
14
12
|
|
15
|
-
|
13
|
+
import lsrestclient.exceptions.ConnectionError
|
14
|
+
from lsrestclient.contexts.bearer_token import bearer_token_context
|
15
|
+
from lsrestclient.response import (
|
16
16
|
LsRestClientResponse,
|
17
|
-
exceptions,
|
18
17
|
)
|
19
|
-
from lsrestclient.contexts.bearer_token import bearer_token_context
|
20
18
|
from lsrestclient.settings import LsRestClientSettings
|
21
19
|
|
22
20
|
find_parameters_regex = re.compile("{(.*?)}")
|
@@ -28,7 +26,7 @@ class LsRestClientBackendEnum(str, Enum):
|
|
28
26
|
|
29
27
|
|
30
28
|
class LsRestClient(object):
|
31
|
-
_clients = {}
|
29
|
+
_clients: ClassVar[dict[str, "LsRestClient"]] = {}
|
32
30
|
|
33
31
|
@classmethod
|
34
32
|
def from_env(
|
@@ -36,10 +34,10 @@ class LsRestClient(object):
|
|
36
34
|
env_name: str,
|
37
35
|
name: Optional[str] = None,
|
38
36
|
required: bool = True,
|
39
|
-
):
|
37
|
+
) -> "LsRestClient":
|
40
38
|
"""
|
41
39
|
Create an instance of the LsRestClient class using environment variables.
|
42
|
-
It gets globally saved under the given name
|
40
|
+
It gets globally saved under the given name so that it can be reused.
|
43
41
|
|
44
42
|
:param env_name: The name of the environment variable that holds the base URL.
|
45
43
|
:param name: An optional name for the client instance.
|
@@ -53,7 +51,7 @@ class LsRestClient(object):
|
|
53
51
|
return cls(base_url=base_url, name=name)
|
54
52
|
|
55
53
|
@classmethod
|
56
|
-
def client(cls, name: str):
|
54
|
+
def client(cls, name: str) -> "LsRestClient":
|
57
55
|
"""
|
58
56
|
Retrieves the LsRestClient instance with the specified name.
|
59
57
|
If a client with the given name does not exist, an exception is raised.
|
@@ -66,17 +64,19 @@ class LsRestClient(object):
|
|
66
64
|
except KeyError:
|
67
65
|
raise Exception(f"LsRestClient with name '{name}' not initialized.")
|
68
66
|
|
69
|
-
def __repr__(self):
|
67
|
+
def __repr__(self) -> str:
|
70
68
|
return f"<LsRestClient name:'{self.name}' base_url:'{self.base_url}'>"
|
71
69
|
|
72
70
|
def __init__(
|
73
71
|
self,
|
74
|
-
base_url: str = None,
|
72
|
+
base_url: str | None = None,
|
75
73
|
name: str = "default",
|
76
|
-
headers: dict = None,
|
74
|
+
headers: dict | None = None,
|
77
75
|
ignore_bearer_context: bool = False,
|
78
76
|
cache: bool = False,
|
79
77
|
cache_backend: LsRestClientBackendEnum = LsRestClientBackendEnum.sqlite,
|
78
|
+
cache_control: bool = True,
|
79
|
+
stale_while_revalidate: bool = True,
|
80
80
|
) -> None:
|
81
81
|
"""Class representing a REST client for JSON API."""
|
82
82
|
if cache:
|
@@ -93,7 +93,12 @@ class LsRestClient(object):
|
|
93
93
|
else:
|
94
94
|
backend = SQLiteCache(f".cache/{name}")
|
95
95
|
|
96
|
-
self._session = CachedSession(
|
96
|
+
self._session = CachedSession(
|
97
|
+
backend=backend,
|
98
|
+
cache_control=cache_control,
|
99
|
+
filter_fn=self.cache_filter,
|
100
|
+
stale_while_revalidate=stale_while_revalidate,
|
101
|
+
)
|
97
102
|
|
98
103
|
else:
|
99
104
|
self._session = Session()
|
@@ -104,7 +109,11 @@ class LsRestClient(object):
|
|
104
109
|
self.base_headers = {"content-type": "application/json"}
|
105
110
|
|
106
111
|
with bearer_token_context() as bearer_token:
|
107
|
-
bearer_headers =
|
112
|
+
bearer_headers = (
|
113
|
+
{"Authorization": f"Bearer {bearer_token}"}
|
114
|
+
if bearer_token is not None
|
115
|
+
else {}
|
116
|
+
)
|
108
117
|
self.base_headers.update(bearer_headers)
|
109
118
|
if headers is not None:
|
110
119
|
self.base_headers.update(headers)
|
@@ -113,7 +122,7 @@ class LsRestClient(object):
|
|
113
122
|
super().__init__()
|
114
123
|
self._clients[name] = self
|
115
124
|
|
116
|
-
def clear_cache(self):
|
125
|
+
def clear_cache(self) -> None:
|
117
126
|
if not isinstance(self._session, CachedSession):
|
118
127
|
raise Exception("Cache not enabled.")
|
119
128
|
self._session.cache.clear()
|
@@ -129,7 +138,7 @@ class LsRestClient(object):
|
|
129
138
|
def mock_name(client_name: str, method: str, url: str) -> str:
|
130
139
|
return f"{client_name}_{method.upper()}_{url}"
|
131
140
|
|
132
|
-
def url_parse_params(self, url: str, params: Optional[dict] = None):
|
141
|
+
def url_parse_params(self, url: str, params: Optional[dict] = None) -> str:
|
133
142
|
if params is None:
|
134
143
|
params = {}
|
135
144
|
|
@@ -157,7 +166,9 @@ class LsRestClient(object):
|
|
157
166
|
|
158
167
|
return f"{self.base_url}{url}"
|
159
168
|
|
160
|
-
def caller(
|
169
|
+
def caller(
|
170
|
+
self, method: str, url: str, *args: Any, **kwargs: Any
|
171
|
+
) -> LsRestClientResponse:
|
161
172
|
# check mocks
|
162
173
|
mock = self._mocks.get(self.mock_name(self.name, method, url), None)
|
163
174
|
func = mock if mock is not None else self.request
|
@@ -167,16 +178,17 @@ class LsRestClient(object):
|
|
167
178
|
# but only just before calling func, because of mock path
|
168
179
|
url = self.url_parse_params(url, kwargs.get("params", {}))
|
169
180
|
|
181
|
+
# noinspection PyTypeChecker
|
170
182
|
return func(method, url, *args, **kwargs)
|
171
183
|
|
172
184
|
def request(
|
173
185
|
self,
|
174
186
|
method: str,
|
175
187
|
url: str,
|
176
|
-
*args,
|
188
|
+
*args: list,
|
177
189
|
params: Optional[Dict[str, Any]] = None,
|
178
190
|
body: Optional[Dict[str, Any]] = None,
|
179
|
-
**kwargs,
|
191
|
+
**kwargs: dict,
|
180
192
|
) -> LsRestClientResponse: # pragma: no cover
|
181
193
|
"""
|
182
194
|
:param method: The HTTP method to be used for the request.
|
@@ -237,10 +249,10 @@ class LsRestClient(object):
|
|
237
249
|
self,
|
238
250
|
method: str,
|
239
251
|
url: str,
|
240
|
-
*args,
|
252
|
+
*args: list,
|
241
253
|
params: Optional[Dict[str, Any]] = None,
|
242
|
-
**kwargs,
|
243
|
-
):
|
254
|
+
**kwargs: dict,
|
255
|
+
) -> LsRestClientResponse:
|
244
256
|
full_url = self.full_url(url, params)
|
245
257
|
|
246
258
|
try:
|
@@ -263,9 +275,9 @@ class LsRestClient(object):
|
|
263
275
|
return response
|
264
276
|
|
265
277
|
except requests.ConnectionError:
|
266
|
-
raise exceptions.ConnectionError(url=full_url)
|
278
|
+
raise lsrestclient.exceptions.ConnectionError.ConnectionError(url=full_url)
|
267
279
|
|
268
|
-
def get(self, *args, **kwargs) -> LsRestClientResponse:
|
280
|
+
def get(self, *args: Any, **kwargs: Any) -> LsRestClientResponse:
|
269
281
|
"""
|
270
282
|
Send a GET request to the specified URL.
|
271
283
|
|
@@ -276,7 +288,7 @@ class LsRestClient(object):
|
|
276
288
|
|
277
289
|
return self.caller("GET", *args, **kwargs)
|
278
290
|
|
279
|
-
def post(self, *args, **kwargs) -> LsRestClientResponse:
|
291
|
+
def post(self, *args: Any, **kwargs: Any) -> LsRestClientResponse:
|
280
292
|
"""
|
281
293
|
This method is used to send a POST request using the LSRestClient class.
|
282
294
|
|
@@ -286,7 +298,7 @@ class LsRestClient(object):
|
|
286
298
|
"""
|
287
299
|
return self.caller("POST", *args, **kwargs)
|
288
300
|
|
289
|
-
def put(self, *args, **kwargs) -> LsRestClientResponse:
|
301
|
+
def put(self, *args: Any, **kwargs: Any) -> LsRestClientResponse:
|
290
302
|
"""
|
291
303
|
This method is used to send a PUT request using the LSRestClient class.
|
292
304
|
|
@@ -296,7 +308,7 @@ class LsRestClient(object):
|
|
296
308
|
"""
|
297
309
|
return self.caller("PUT", *args, **kwargs)
|
298
310
|
|
299
|
-
def patch(self, *args, **kwargs) -> LsRestClientResponse:
|
311
|
+
def patch(self, *args: Any, **kwargs: Any) -> LsRestClientResponse:
|
300
312
|
"""
|
301
313
|
Send a PATCH request to the specified URL with the provided arguments.
|
302
314
|
|
@@ -306,7 +318,7 @@ class LsRestClient(object):
|
|
306
318
|
"""
|
307
319
|
return self.caller("PATCH", *args, **kwargs)
|
308
320
|
|
309
|
-
def delete(self, *args, **kwargs) -> LsRestClientResponse:
|
321
|
+
def delete(self, *args: Any, **kwargs: Any) -> LsRestClientResponse:
|
310
322
|
"""
|
311
323
|
Deletes a resource using the DELETE method.
|
312
324
|
|
@@ -316,7 +328,7 @@ class LsRestClient(object):
|
|
316
328
|
"""
|
317
329
|
return self.caller("DELETE", *args, **kwargs)
|
318
330
|
|
319
|
-
def options(self, *args, **kwargs) -> LsRestClientResponse:
|
331
|
+
def options(self, *args: Any, **kwargs: Any) -> LsRestClientResponse:
|
320
332
|
"""
|
321
333
|
Send an OPTIONS request using the LsRestClient.
|
322
334
|
|
@@ -340,16 +352,16 @@ class LsRestClient(object):
|
|
340
352
|
"""
|
341
353
|
return self.request("HEAD", *args, **kwargs)
|
342
354
|
|
343
|
-
def mock(self, mock_name: str, mock: MagicMock):
|
355
|
+
def mock(self, mock_name: str, mock: MagicMock) -> None:
|
344
356
|
self._mocks[mock_name] = mock
|
345
357
|
|
346
|
-
def unmock(self, mock_name: str):
|
358
|
+
def unmock(self, mock_name: str) -> None:
|
347
359
|
if mock_name in self._mocks:
|
348
360
|
del self._mocks[mock_name]
|
349
361
|
|
350
362
|
|
351
363
|
class LsRestClientTestClient(LsRestClient):
|
352
|
-
def __init__(self, test_client: Any, name: str = "test-client") -> None:
|
364
|
+
def __init__(self, test_client: Any, name: str = "test-client") -> None: # noqa: ANN401
|
353
365
|
super().__init__(base_url=None, name=name)
|
354
366
|
self.test_client = test_client
|
355
367
|
|
@@ -357,9 +369,13 @@ class LsRestClientTestClient(LsRestClient):
|
|
357
369
|
self,
|
358
370
|
method: str,
|
359
371
|
url: str,
|
360
|
-
*args,
|
372
|
+
*args: Any,
|
361
373
|
params: Optional[Dict[str, Any]] = None,
|
362
|
-
**kwargs,
|
363
|
-
):
|
374
|
+
**kwargs: Any,
|
375
|
+
) -> LsRestClientResponse:
|
364
376
|
r = self.test_client.request(method, url, *args, params=params, **kwargs)
|
365
|
-
return LsRestClientResponse(
|
377
|
+
return LsRestClientResponse(
|
378
|
+
status_code=r.status_code,
|
379
|
+
content=r.content.decode("utf8"),
|
380
|
+
headers=r.headers,
|
381
|
+
)
|
@@ -1,12 +1,14 @@
|
|
1
1
|
import contextlib
|
2
2
|
import contextvars
|
3
|
-
from typing import Optional
|
3
|
+
from typing import Any, Generator, Optional
|
4
4
|
|
5
|
-
bearer_token_value: contextvars.ContextVar[Optional[str]] = contextvars.ContextVar(
|
5
|
+
bearer_token_value: contextvars.ContextVar[Optional[str]] = contextvars.ContextVar(
|
6
|
+
"bearer_token_value", default=None
|
7
|
+
)
|
6
8
|
|
7
9
|
|
8
10
|
@contextlib.contextmanager
|
9
|
-
def bearer_token_provider(bearer_token: str):
|
11
|
+
def bearer_token_provider(bearer_token: str) -> Generator[None, Any, None]:
|
10
12
|
"""
|
11
13
|
:param bearer_token: The bearer token to set.
|
12
14
|
:return: A context manager that sets the bearer token and resets it when the context is exited.
|
@@ -17,7 +19,7 @@ def bearer_token_provider(bearer_token: str):
|
|
17
19
|
|
18
20
|
|
19
21
|
@contextlib.contextmanager
|
20
|
-
def bearer_token_context():
|
22
|
+
def bearer_token_context() -> Generator[str | None, Any, None]:
|
21
23
|
"""
|
22
24
|
Context manager for handling bearer token.
|
23
25
|
|
@@ -0,0 +1,22 @@
|
|
1
|
+
from typing import Optional
|
2
|
+
|
3
|
+
|
4
|
+
class ConnectionError(Exception):
|
5
|
+
"""Exception class for connection errors.
|
6
|
+
|
7
|
+
Args:
|
8
|
+
url (Optional[str]): The URL that the connection could not be established to.
|
9
|
+
|
10
|
+
Attributes:
|
11
|
+
url (Optional[str]): The URL that the connection could not be established to.
|
12
|
+
|
13
|
+
Raises:
|
14
|
+
ConnectionError: If a connection could not be established to the given URL.
|
15
|
+
|
16
|
+
"""
|
17
|
+
|
18
|
+
url: str
|
19
|
+
|
20
|
+
def __init__(self, url: Optional[str] = None) -> None:
|
21
|
+
self.url = url
|
22
|
+
super().__init__(f"Connection could not be established to '{url}'")
|
@@ -0,0 +1,12 @@
|
|
1
|
+
class DownStreamError(Exception):
|
2
|
+
status_code: int
|
3
|
+
url: str
|
4
|
+
content: str
|
5
|
+
|
6
|
+
def __init__(self, url: str, status_code: int, content: str) -> None:
|
7
|
+
self.url = url
|
8
|
+
self.status_code = status_code
|
9
|
+
self.content = content
|
10
|
+
super().__init__(
|
11
|
+
f"Downstream error calling {self.url}. {self.status_code} {self.content}"
|
12
|
+
)
|
File without changes
|
@@ -0,0 +1,44 @@
|
|
1
|
+
import contextlib
|
2
|
+
import logging
|
3
|
+
from typing import List, Optional, Type
|
4
|
+
|
5
|
+
import pydash
|
6
|
+
from webexception.webexception import WebException
|
7
|
+
|
8
|
+
from lsrestclient.response import LsRestClientResponse
|
9
|
+
|
10
|
+
log = logging.getLogger(__name__)
|
11
|
+
|
12
|
+
|
13
|
+
@contextlib.contextmanager
|
14
|
+
def raise_errors( # noqa: ANN201
|
15
|
+
r: LsRestClientResponse, exceptions: Optional[List[Type[Exception]]] = None
|
16
|
+
):
|
17
|
+
if exceptions is None:
|
18
|
+
exceptions_by_class = {}
|
19
|
+
else:
|
20
|
+
exceptions_by_class = {e.__name__: e for e in exceptions}
|
21
|
+
|
22
|
+
if r.status_code < 399:
|
23
|
+
yield r
|
24
|
+
else:
|
25
|
+
try:
|
26
|
+
json = r.json()
|
27
|
+
except Exception:
|
28
|
+
log.error(r.content)
|
29
|
+
raise WebException(status_code=r.status_code, detail=r.content)
|
30
|
+
|
31
|
+
detail = pydash.get(json, "detail", json)
|
32
|
+
error_class = pydash.get(detail, "error_class", None)
|
33
|
+
if error_class is not None:
|
34
|
+
payload = pydash.get(detail, "error_payload", {})
|
35
|
+
else:
|
36
|
+
error_class = pydash.get(detail, "ERROR_CLASS", None)
|
37
|
+
payload = {}
|
38
|
+
|
39
|
+
if error_class in exceptions_by_class:
|
40
|
+
# noinspection PyArgumentList
|
41
|
+
e = exceptions_by_class[error_class](**payload)
|
42
|
+
raise e
|
43
|
+
# backend errors
|
44
|
+
raise WebException(status_code=r.status_code, detail=detail)
|
lsrestclient/fixtures.py
CHANGED
@@ -1,10 +1,12 @@
|
|
1
|
-
from
|
1
|
+
from typing import Any, Generator
|
2
|
+
|
3
|
+
from lsrestclient.mock import LsRestClientMocker, lsrestclient_mock_context
|
2
4
|
|
3
5
|
try:
|
4
6
|
import pytest
|
5
7
|
|
6
8
|
@pytest.fixture
|
7
|
-
def lsrestclient_mocker():
|
9
|
+
def lsrestclient_mocker() -> Generator[LsRestClientMocker, Any, None]:
|
8
10
|
with lsrestclient_mock_context() as mocker:
|
9
11
|
yield mocker
|
10
12
|
|
lsrestclient/lsfastapi.py
CHANGED
@@ -1,7 +1,8 @@
|
|
1
|
+
import logging
|
1
2
|
from abc import ABC, abstractmethod
|
3
|
+
from typing import Any
|
2
4
|
|
3
|
-
from lsrestclient import LsRestClient
|
4
|
-
import logging
|
5
|
+
from lsrestclient import LsRestClient, LsRestClientResponse
|
5
6
|
|
6
7
|
log = logging.getLogger(__name__)
|
7
8
|
|
@@ -10,7 +11,7 @@ class LsFastApiClientBase(ABC):
|
|
10
11
|
_client = None
|
11
12
|
client_name = None
|
12
13
|
|
13
|
-
def __init__(self):
|
14
|
+
def __init__(self) -> None:
|
14
15
|
pass
|
15
16
|
|
16
17
|
@classmethod
|
@@ -18,23 +19,25 @@ class LsFastApiClientBase(ABC):
|
|
18
19
|
# noinspection PyBroadException
|
19
20
|
try:
|
20
21
|
cls._client = LsRestClient.client(cls.client_name)
|
21
|
-
except Exception
|
22
|
+
except Exception: # pragma: no cover
|
22
23
|
# noinspection PyArgumentList
|
23
24
|
cls._client = cls.register()
|
24
25
|
return cls._client
|
25
26
|
|
26
27
|
@classmethod
|
27
|
-
def register(cls, base_url: str = None, **kwargs) -> LsRestClient:
|
28
|
+
def register(cls, base_url: str | None = None, **kwargs: Any) -> LsRestClient:
|
28
29
|
# noinspection PyArgumentList
|
29
30
|
log.debug(f"Registering {cls.client_name} API client at {base_url}")
|
30
|
-
cls._client = LsRestClient(
|
31
|
+
cls._client = LsRestClient(
|
32
|
+
name=cls.client_name, base_url=base_url or cls.base_url(), **kwargs
|
33
|
+
)
|
31
34
|
return cls._client
|
32
35
|
|
33
36
|
@classmethod
|
34
|
-
def health(cls):
|
37
|
+
def health(cls) -> LsRestClientResponse:
|
35
38
|
return cls.client().get("/healthz")
|
36
39
|
|
37
40
|
@classmethod
|
38
41
|
@abstractmethod
|
39
|
-
def base_url(cls):
|
42
|
+
def base_url(cls) -> str:
|
40
43
|
raise NotImplementedError
|
lsrestclient/mock.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
import dataclasses
|
2
2
|
from contextlib import contextmanager
|
3
|
-
from typing import Union
|
3
|
+
from typing import Any, Generator, Union
|
4
4
|
from unittest.mock import MagicMock
|
5
5
|
|
6
6
|
from lsrestclient import LsRestClient
|
@@ -14,26 +14,35 @@ class LsRestClientMockModel:
|
|
14
14
|
|
15
15
|
|
16
16
|
class LsRestClientMocker(object):
|
17
|
-
def __init__(self):
|
17
|
+
def __init__(self) -> None:
|
18
18
|
super().__init__()
|
19
19
|
self.mocks = {}
|
20
20
|
|
21
|
-
def mock(
|
21
|
+
def mock(
|
22
|
+
self,
|
23
|
+
client: Union[LsRestClient, str],
|
24
|
+
method: str,
|
25
|
+
url: str,
|
26
|
+
*args: Any,
|
27
|
+
**kwargs: Any,
|
28
|
+
) -> MagicMock:
|
22
29
|
if isinstance(client, str):
|
23
30
|
client = LsRestClient.client(client)
|
24
31
|
|
25
32
|
mock_name = LsRestClient.mock_name(client.name, method, url)
|
26
33
|
mock = MagicMock(*args, **kwargs)
|
27
34
|
client.mock(mock_name, mock)
|
28
|
-
self.mocks[mock_name] = LsRestClientMockModel(
|
35
|
+
self.mocks[mock_name] = LsRestClientMockModel(
|
36
|
+
client=client, mock_name=mock_name, mock=mock
|
37
|
+
)
|
29
38
|
return mock
|
30
39
|
|
31
|
-
def unmock_all(self):
|
40
|
+
def unmock_all(self) -> None:
|
32
41
|
for mock_name, mock_model in self.mocks.items():
|
33
42
|
mock_model.client.unmock(mock_name)
|
34
43
|
self.mocks = {}
|
35
44
|
|
36
|
-
def unmock(self, client: LsRestClient, method: str, url: str):
|
45
|
+
def unmock(self, client: LsRestClient, method: str, url: str) -> None:
|
37
46
|
mock_name = LsRestClient.mock_name(client.name, method, url)
|
38
47
|
mock_model = self.mocks[mock_name]
|
39
48
|
mock_model.client.unmock(mock_name)
|
@@ -41,7 +50,7 @@ class LsRestClientMocker(object):
|
|
41
50
|
|
42
51
|
|
43
52
|
@contextmanager
|
44
|
-
def lsrestclient_mock_context():
|
53
|
+
def lsrestclient_mock_context() -> Generator[LsRestClientMocker, Any, None]:
|
45
54
|
mocker = LsRestClientMocker()
|
46
55
|
yield mocker
|
47
56
|
mocker.unmock_all()
|
lsrestclient/response.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
import datetime
|
2
2
|
from dataclasses import dataclass
|
3
|
-
from typing import Optional
|
3
|
+
from typing import Any, Optional
|
4
4
|
|
5
5
|
import lsjsonclasses
|
6
6
|
import pydash
|
@@ -26,13 +26,13 @@ class LsRestClientResponse:
|
|
26
26
|
|
27
27
|
_json: Optional[dict] = None
|
28
28
|
|
29
|
-
def json(self):
|
29
|
+
def json(self) -> dict[str, Any]:
|
30
30
|
if self._json is None and self.content != "":
|
31
31
|
self._json = lsjsonclasses.LSoftJSONDecoder.loads(self.content)
|
32
32
|
return self._json
|
33
33
|
|
34
34
|
@classmethod
|
35
|
-
def from_requests_response(cls, response: Response):
|
35
|
+
def from_requests_response(cls, response: Response) -> "LsRestClientResponse":
|
36
36
|
"""
|
37
37
|
Create an instance of LsRestClientResponse from a requests Response object.
|
38
38
|
|
@@ -50,7 +50,9 @@ class LsRestClientResponse:
|
|
50
50
|
else:
|
51
51
|
content = response.content.decode("utf8" if encoding is None else encoding)
|
52
52
|
|
53
|
-
ret = cls(
|
53
|
+
ret = cls(
|
54
|
+
status_code=response.status_code, content=content, headers=response.headers
|
55
|
+
)
|
54
56
|
if isinstance(response, CachedResponse):
|
55
57
|
ret.created_at = response.created_at
|
56
58
|
ret.expires = response.expires
|
@@ -60,7 +62,12 @@ class LsRestClientResponse:
|
|
60
62
|
return ret
|
61
63
|
|
62
64
|
@classmethod
|
63
|
-
def from_dict(
|
65
|
+
def from_dict(
|
66
|
+
cls,
|
67
|
+
status_code: int = 200,
|
68
|
+
data: dict | None = None,
|
69
|
+
headers: CaseInsensitiveDict = None,
|
70
|
+
) -> "LsRestClientResponse":
|
64
71
|
"""
|
65
72
|
Converts a dictionary into an instance of the LsRestClientResponse class.
|
66
73
|
|
@@ -1,21 +1,19 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.4
|
2
2
|
Name: lsrestclient
|
3
|
-
Version: 3.
|
3
|
+
Version: 3.4.1
|
4
4
|
Summary: REST Api Client
|
5
|
-
Author: mba
|
6
|
-
|
7
|
-
Requires-
|
8
|
-
|
5
|
+
Author-email: mba <bartel@electronic-shop.lu>
|
6
|
+
Requires-Python: >=3.13
|
7
|
+
Requires-Dist: lsjsonclasses<3.0.0,>=2.0.1
|
8
|
+
Requires-Dist: pydantic-settings<3.0.0,>=2.10.1
|
9
|
+
Requires-Dist: pydantic<3.0.0,>=2.11.7
|
10
|
+
Requires-Dist: pydash>4.0.0
|
11
|
+
Requires-Dist: redis<7.0.0,>=6.4.0
|
12
|
+
Requires-Dist: requests-cache<2.0.0,>=1.2.1
|
13
|
+
Requires-Dist: requests<3.0.0,>=2.31.0
|
14
|
+
Requires-Dist: webexception<2.0.0,>=1.0.5
|
9
15
|
Provides-Extra: redis
|
10
|
-
Requires-Dist:
|
11
|
-
Requires-Dist: lsjsonclasses (>=2.0.1,<3.0.0)
|
12
|
-
Requires-Dist: pydantic (>=2.11.7,<3.0.0)
|
13
|
-
Requires-Dist: pydantic-settings (>=2.10.1,<3.0.0)
|
14
|
-
Requires-Dist: pydash (>4.0.0)
|
15
|
-
Requires-Dist: redis (>=6.4.0,<7.0.0) ; extra == "redis"
|
16
|
-
Requires-Dist: requests (>=2.31.0,<3.0.0)
|
17
|
-
Requires-Dist: requests-cache (>=1.2.1,<2.0.0)
|
18
|
-
Requires-Dist: webexception (>=1.0.5,<2.0.0)
|
16
|
+
Requires-Dist: redis; extra == 'redis'
|
19
17
|
Description-Content-Type: text/markdown
|
20
18
|
|
21
19
|
# lsrestclient
|
@@ -110,4 +108,3 @@ For open source projects, say how it is licensed.
|
|
110
108
|
|
111
109
|
## Project status
|
112
110
|
If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers.
|
113
|
-
|
@@ -0,0 +1,18 @@
|
|
1
|
+
lsrestclient/__init__.py,sha256=J09z2822ZVQWCfJw9Dz3DrpJY9kvMfZThHIVhEPvovU,169
|
2
|
+
lsrestclient/auth.py,sha256=E87G8PXi5mnj_fY5NuILRvQdV_zLVTyBw0ZjpmZDWrI,475
|
3
|
+
lsrestclient/client.py,sha256=tng4clgRM9uPpvNZVHfOUpq7Bb6jcPyRsRMDEzs0iik,14353
|
4
|
+
lsrestclient/fixtures.py,sha256=huiw8-mnemFHeQGypK9n-OWqutAWZI7sBWWtwQqbYLs,338
|
5
|
+
lsrestclient/lsfastapi.py,sha256=7Bnrt8pbojJ7FbBbpHTgf56xdywgfbkYcOU6WkBjGU0,1197
|
6
|
+
lsrestclient/mock.py,sha256=ebyI5bD7n0Txzi6FkS7gaAVUPcXHRcy9kDNkivHY0zQ,1560
|
7
|
+
lsrestclient/response.py,sha256=qAbCMHi-XX19S7Rc71rl4b31CUqFbK5UpQqiUh8M3M4,2847
|
8
|
+
lsrestclient/settings.py,sha256=qRhQYFCOA8sKhejrd7r47Q4Vo6mQjLbXko7jv3_889A,350
|
9
|
+
lsrestclient/contexts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
10
|
+
lsrestclient/contexts/bearer_token.py,sha256=hmYmr5hmeliBTrP2n9AmKf1E4Vk61lcSyDrvNTkF6HE,841
|
11
|
+
lsrestclient/exceptions/ConnectionError.py,sha256=W0Ys80aElrDAbv-xf4wiMlU-4iJaji5I63V92HUEJnk,601
|
12
|
+
lsrestclient/exceptions/DownStreamError.py,sha256=AvxQ8uu9XodRf5njnies9MHFVpzpCdJmNOEe8Z95WVk,375
|
13
|
+
lsrestclient/exceptions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
14
|
+
lsrestclient/exceptions/raise_errors.py,sha256=LPemwueHxOQwf3WDaM0apI5aRADxNuPxSxAztC8pblc,1323
|
15
|
+
lsrestclient-3.4.1.dist-info/METADATA,sha256=zIDRJw9VEcEjeRAYzALXCJWkzCOaCZ53kc1iVi7ZMcg,6752
|
16
|
+
lsrestclient-3.4.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
17
|
+
lsrestclient-3.4.1.dist-info/entry_points.txt,sha256=6lmLua-CwRxXMA6iG5veLwIFbtUBhhypD5RI_2lv5Ms,48
|
18
|
+
lsrestclient-3.4.1.dist-info/RECORD,,
|
lsrestclient/exceptions.py
DELETED
@@ -1,73 +0,0 @@
|
|
1
|
-
import contextlib
|
2
|
-
import logging
|
3
|
-
from typing import Optional, List, Type
|
4
|
-
|
5
|
-
import pydash
|
6
|
-
from webexception.webexception import WebException
|
7
|
-
|
8
|
-
log = logging.getLogger(__name__)
|
9
|
-
|
10
|
-
|
11
|
-
# noinspection PyShadowingBuiltins
|
12
|
-
class ConnectionError(Exception):
|
13
|
-
"""Exception class for connection errors.
|
14
|
-
|
15
|
-
Args:
|
16
|
-
url (Optional[str]): The URL that the connection could not be established to.
|
17
|
-
|
18
|
-
Attributes:
|
19
|
-
url (Optional[str]): The URL that the connection could not be established to.
|
20
|
-
|
21
|
-
Raises:
|
22
|
-
ConnectionError: If a connection could not be established to the given URL.
|
23
|
-
|
24
|
-
"""
|
25
|
-
|
26
|
-
url: str
|
27
|
-
|
28
|
-
def __init__(self, url: Optional[str] = None) -> None:
|
29
|
-
self.url = url
|
30
|
-
super().__init__(f"Connection could not be established to '{url}'")
|
31
|
-
|
32
|
-
|
33
|
-
class DownStreamError(Exception):
|
34
|
-
status_code: int
|
35
|
-
url: str
|
36
|
-
content: str
|
37
|
-
|
38
|
-
def __init__(self, url: str, status_code: int, content: str) -> None:
|
39
|
-
self.url = url
|
40
|
-
self.status_code = status_code
|
41
|
-
self.content = content
|
42
|
-
super().__init__(f"Downstream error calling {self.url}. {self.status_code} {self.content}")
|
43
|
-
|
44
|
-
|
45
|
-
@contextlib.contextmanager
|
46
|
-
def raise_errors(r, exceptions: Optional[List[Type[Exception]]] = None):
|
47
|
-
if exceptions is None:
|
48
|
-
exceptions_by_class = {}
|
49
|
-
else:
|
50
|
-
exceptions_by_class = {e.__name__: e for e in exceptions}
|
51
|
-
|
52
|
-
if r.status_code < 399:
|
53
|
-
yield r
|
54
|
-
else:
|
55
|
-
try:
|
56
|
-
json = r.json()
|
57
|
-
except Exception as e:
|
58
|
-
log.error(r.content)
|
59
|
-
raise WebException(status_code=r.status_code, detail=r.content)
|
60
|
-
|
61
|
-
detail = pydash.get(json, "detail", json)
|
62
|
-
error_class = pydash.get(detail, "error_class", None)
|
63
|
-
if error_class is not None:
|
64
|
-
payload = pydash.get(detail, "error_payload", {})
|
65
|
-
else:
|
66
|
-
error_class = pydash.get(detail, "ERROR_CLASS", None)
|
67
|
-
payload = {}
|
68
|
-
|
69
|
-
if error_class in exceptions_by_class:
|
70
|
-
e = exceptions_by_class[error_class](**payload)
|
71
|
-
raise e
|
72
|
-
# backend errors
|
73
|
-
raise WebException(status_code=r.status_code, detail=detail)
|
@@ -1,15 +0,0 @@
|
|
1
|
-
lsrestclient/__init__.py,sha256=lI62SHmG0m-iukB5UEwdN5dO4cKnDasOt-TmR1rgaWI,72
|
2
|
-
lsrestclient/auth.py,sha256=IC6niEht-xB_wC7da0HIeM05Ha785qhls-Q39P6dMHQ,467
|
3
|
-
lsrestclient/client.py,sha256=Hy39JuNz3cxF0hvNhqD63HLPZSvhaHdUNtl5OrYqLzg,13606
|
4
|
-
lsrestclient/contexts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
5
|
-
lsrestclient/contexts/bearer_token.py,sha256=GZZOzAI2Ng_9DvFCbhv1Wwb8wJ1AYCca3fQeNtt2NaU,753
|
6
|
-
lsrestclient/exceptions.py,sha256=exJd1BfygNkkAqekmWepVXvXlMiOInVzPRnq7TmPURs,2149
|
7
|
-
lsrestclient/fixtures.py,sha256=dFkAYQXL6xqTOwRofb03Nsu_cIjq1sG10Rh8dxWfz9s,239
|
8
|
-
lsrestclient/lsfastapi.py,sha256=Hn-PgV8RKifa0BbNZzbuK-FCVbJpFmUIpItxZbivd0A,1084
|
9
|
-
lsrestclient/mock.py,sha256=Ya12F0t5sXHTSt-X4jDDj5ILJx6y6QaBAMXutQMsIRI,1376
|
10
|
-
lsrestclient/response.py,sha256=4DYeN9e7phcrbm0QwwQS7uBI-sTDq7bKkP28BRWCzqA,2704
|
11
|
-
lsrestclient/settings.py,sha256=qRhQYFCOA8sKhejrd7r47Q4Vo6mQjLbXko7jv3_889A,350
|
12
|
-
lsrestclient-3.3.2.dist-info/METADATA,sha256=v8XJ4_P3hN9TgI9JSR_JxgBh-tU79vQ4SsvqVxQWGtI,6862
|
13
|
-
lsrestclient-3.3.2.dist-info/WHEEL,sha256=7Z8_27uaHI_UZAc4Uox4PpBhQ9Y5_modZXWMxtUi4NU,88
|
14
|
-
lsrestclient-3.3.2.dist-info/entry_points.txt,sha256=7lN1XN3lq5Jv5PlpOdIlFrLlFlwzE5MaEWSgMhKASOM,47
|
15
|
-
lsrestclient-3.3.2.dist-info/RECORD,,
|