lsrestclient 3.4.0__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 +46 -37
- 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.4.0.dist-info → lsrestclient-3.4.1.dist-info}/METADATA +13 -16
- lsrestclient-3.4.1.dist-info/RECORD +18 -0
- {lsrestclient-3.4.0.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.4.0.dist-info/RECORD +0 -15
- lsrestclient-3.4.0.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,14 +64,14 @@ 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,
|
@@ -111,7 +109,11 @@ class LsRestClient(object):
|
|
111
109
|
self.base_headers = {"content-type": "application/json"}
|
112
110
|
|
113
111
|
with bearer_token_context() as bearer_token:
|
114
|
-
bearer_headers =
|
112
|
+
bearer_headers = (
|
113
|
+
{"Authorization": f"Bearer {bearer_token}"}
|
114
|
+
if bearer_token is not None
|
115
|
+
else {}
|
116
|
+
)
|
115
117
|
self.base_headers.update(bearer_headers)
|
116
118
|
if headers is not None:
|
117
119
|
self.base_headers.update(headers)
|
@@ -120,7 +122,7 @@ class LsRestClient(object):
|
|
120
122
|
super().__init__()
|
121
123
|
self._clients[name] = self
|
122
124
|
|
123
|
-
def clear_cache(self):
|
125
|
+
def clear_cache(self) -> None:
|
124
126
|
if not isinstance(self._session, CachedSession):
|
125
127
|
raise Exception("Cache not enabled.")
|
126
128
|
self._session.cache.clear()
|
@@ -136,7 +138,7 @@ class LsRestClient(object):
|
|
136
138
|
def mock_name(client_name: str, method: str, url: str) -> str:
|
137
139
|
return f"{client_name}_{method.upper()}_{url}"
|
138
140
|
|
139
|
-
def url_parse_params(self, url: str, params: Optional[dict] = None):
|
141
|
+
def url_parse_params(self, url: str, params: Optional[dict] = None) -> str:
|
140
142
|
if params is None:
|
141
143
|
params = {}
|
142
144
|
|
@@ -164,7 +166,9 @@ class LsRestClient(object):
|
|
164
166
|
|
165
167
|
return f"{self.base_url}{url}"
|
166
168
|
|
167
|
-
def caller(
|
169
|
+
def caller(
|
170
|
+
self, method: str, url: str, *args: Any, **kwargs: Any
|
171
|
+
) -> LsRestClientResponse:
|
168
172
|
# check mocks
|
169
173
|
mock = self._mocks.get(self.mock_name(self.name, method, url), None)
|
170
174
|
func = mock if mock is not None else self.request
|
@@ -174,16 +178,17 @@ class LsRestClient(object):
|
|
174
178
|
# but only just before calling func, because of mock path
|
175
179
|
url = self.url_parse_params(url, kwargs.get("params", {}))
|
176
180
|
|
181
|
+
# noinspection PyTypeChecker
|
177
182
|
return func(method, url, *args, **kwargs)
|
178
183
|
|
179
184
|
def request(
|
180
185
|
self,
|
181
186
|
method: str,
|
182
187
|
url: str,
|
183
|
-
*args,
|
188
|
+
*args: list,
|
184
189
|
params: Optional[Dict[str, Any]] = None,
|
185
190
|
body: Optional[Dict[str, Any]] = None,
|
186
|
-
**kwargs,
|
191
|
+
**kwargs: dict,
|
187
192
|
) -> LsRestClientResponse: # pragma: no cover
|
188
193
|
"""
|
189
194
|
:param method: The HTTP method to be used for the request.
|
@@ -244,10 +249,10 @@ class LsRestClient(object):
|
|
244
249
|
self,
|
245
250
|
method: str,
|
246
251
|
url: str,
|
247
|
-
*args,
|
252
|
+
*args: list,
|
248
253
|
params: Optional[Dict[str, Any]] = None,
|
249
|
-
**kwargs,
|
250
|
-
):
|
254
|
+
**kwargs: dict,
|
255
|
+
) -> LsRestClientResponse:
|
251
256
|
full_url = self.full_url(url, params)
|
252
257
|
|
253
258
|
try:
|
@@ -270,9 +275,9 @@ class LsRestClient(object):
|
|
270
275
|
return response
|
271
276
|
|
272
277
|
except requests.ConnectionError:
|
273
|
-
raise exceptions.ConnectionError(url=full_url)
|
278
|
+
raise lsrestclient.exceptions.ConnectionError.ConnectionError(url=full_url)
|
274
279
|
|
275
|
-
def get(self, *args, **kwargs) -> LsRestClientResponse:
|
280
|
+
def get(self, *args: Any, **kwargs: Any) -> LsRestClientResponse:
|
276
281
|
"""
|
277
282
|
Send a GET request to the specified URL.
|
278
283
|
|
@@ -283,7 +288,7 @@ class LsRestClient(object):
|
|
283
288
|
|
284
289
|
return self.caller("GET", *args, **kwargs)
|
285
290
|
|
286
|
-
def post(self, *args, **kwargs) -> LsRestClientResponse:
|
291
|
+
def post(self, *args: Any, **kwargs: Any) -> LsRestClientResponse:
|
287
292
|
"""
|
288
293
|
This method is used to send a POST request using the LSRestClient class.
|
289
294
|
|
@@ -293,7 +298,7 @@ class LsRestClient(object):
|
|
293
298
|
"""
|
294
299
|
return self.caller("POST", *args, **kwargs)
|
295
300
|
|
296
|
-
def put(self, *args, **kwargs) -> LsRestClientResponse:
|
301
|
+
def put(self, *args: Any, **kwargs: Any) -> LsRestClientResponse:
|
297
302
|
"""
|
298
303
|
This method is used to send a PUT request using the LSRestClient class.
|
299
304
|
|
@@ -303,7 +308,7 @@ class LsRestClient(object):
|
|
303
308
|
"""
|
304
309
|
return self.caller("PUT", *args, **kwargs)
|
305
310
|
|
306
|
-
def patch(self, *args, **kwargs) -> LsRestClientResponse:
|
311
|
+
def patch(self, *args: Any, **kwargs: Any) -> LsRestClientResponse:
|
307
312
|
"""
|
308
313
|
Send a PATCH request to the specified URL with the provided arguments.
|
309
314
|
|
@@ -313,7 +318,7 @@ class LsRestClient(object):
|
|
313
318
|
"""
|
314
319
|
return self.caller("PATCH", *args, **kwargs)
|
315
320
|
|
316
|
-
def delete(self, *args, **kwargs) -> LsRestClientResponse:
|
321
|
+
def delete(self, *args: Any, **kwargs: Any) -> LsRestClientResponse:
|
317
322
|
"""
|
318
323
|
Deletes a resource using the DELETE method.
|
319
324
|
|
@@ -323,7 +328,7 @@ class LsRestClient(object):
|
|
323
328
|
"""
|
324
329
|
return self.caller("DELETE", *args, **kwargs)
|
325
330
|
|
326
|
-
def options(self, *args, **kwargs) -> LsRestClientResponse:
|
331
|
+
def options(self, *args: Any, **kwargs: Any) -> LsRestClientResponse:
|
327
332
|
"""
|
328
333
|
Send an OPTIONS request using the LsRestClient.
|
329
334
|
|
@@ -347,16 +352,16 @@ class LsRestClient(object):
|
|
347
352
|
"""
|
348
353
|
return self.request("HEAD", *args, **kwargs)
|
349
354
|
|
350
|
-
def mock(self, mock_name: str, mock: MagicMock):
|
355
|
+
def mock(self, mock_name: str, mock: MagicMock) -> None:
|
351
356
|
self._mocks[mock_name] = mock
|
352
357
|
|
353
|
-
def unmock(self, mock_name: str):
|
358
|
+
def unmock(self, mock_name: str) -> None:
|
354
359
|
if mock_name in self._mocks:
|
355
360
|
del self._mocks[mock_name]
|
356
361
|
|
357
362
|
|
358
363
|
class LsRestClientTestClient(LsRestClient):
|
359
|
-
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
|
360
365
|
super().__init__(base_url=None, name=name)
|
361
366
|
self.test_client = test_client
|
362
367
|
|
@@ -364,9 +369,13 @@ class LsRestClientTestClient(LsRestClient):
|
|
364
369
|
self,
|
365
370
|
method: str,
|
366
371
|
url: str,
|
367
|
-
*args,
|
372
|
+
*args: Any,
|
368
373
|
params: Optional[Dict[str, Any]] = None,
|
369
|
-
**kwargs,
|
370
|
-
):
|
374
|
+
**kwargs: Any,
|
375
|
+
) -> LsRestClientResponse:
|
371
376
|
r = self.test_client.request(method, url, *args, params=params, **kwargs)
|
372
|
-
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.4.
|
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=oBrzclu96e9hAMXLcJBAOfKb0qsy55TVvOZ6S48XTas,13822
|
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.4.0.dist-info/METADATA,sha256=g4m0vVv1vbO0oSFjmXQwr4yWGyX-Ghb7oj8gdxMdoU8,6862
|
13
|
-
lsrestclient-3.4.0.dist-info/WHEEL,sha256=7Z8_27uaHI_UZAc4Uox4PpBhQ9Y5_modZXWMxtUi4NU,88
|
14
|
-
lsrestclient-3.4.0.dist-info/entry_points.txt,sha256=7lN1XN3lq5Jv5PlpOdIlFrLlFlwzE5MaEWSgMhKASOM,47
|
15
|
-
lsrestclient-3.4.0.dist-info/RECORD,,
|