Skip to content

Commit 53c8f27

Browse files
author
Raphael Krupinski
committed
♻️ Extract AuthRegistry; move bulk of request and response processing to OperationModel.
1 parent fed7e51 commit 53c8f27

File tree

9 files changed

+315
-298
lines changed

9 files changed

+315
-298
lines changed

src/lapidary/runtime/client_base.py

Lines changed: 8 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,14 @@
11
import abc
22
import logging
3-
from collections.abc import Awaitable, Callable, Iterable, Mapping, MutableMapping
3+
from collections.abc import Iterable
44
from importlib.metadata import version
5-
from typing import Any, Optional, cast
5+
from typing import Optional
66

77
import httpx
88
from typing_extensions import Self
99

10-
from ._httpx import AuthType
11-
from .model.op import OperationModel
12-
from .model.request import RequestFactory
13-
from .operation import get_operation_model
14-
from .request import build_request
15-
from .types_ import MultiAuth, NamedAuth, SecurityRequirements
10+
from .model.auth import AuthRegistry
11+
from .types_ import NamedAuth, SecurityRequirements
1612

1713
logger = logging.getLogger(__name__)
1814

@@ -36,112 +32,25 @@ def __init__(
3632
headers['User-Agent'] = user_agent
3733

3834
self._client = _http_client or httpx.AsyncClient(base_url=base_url, headers=headers, **httpx_kwargs)
39-
self._security = security
40-
self._lapidary_operations: MutableMapping[str, OperationModel] = {}
41-
self._auth: MutableMapping[str, httpx.Auth] = {}
42-
self._auth_cache: MutableMapping[str, httpx.Auth] = {}
35+
self._auth_registry = AuthRegistry(security)
4336

4437
async def __aenter__(self: Self) -> Self:
4538
await self._client.__aenter__()
4639
return self
4740

4841
async def __aexit__(self, __exc_type=None, __exc_value=None, __traceback=None) -> None:
49-
await self._client.__aexit__(__exc_type, __exc_value, __traceback)
50-
51-
async def _request(
52-
self,
53-
method: str,
54-
path: str,
55-
fn: Callable[..., Awaitable],
56-
security: Optional[Iterable[SecurityRequirements]],
57-
actual_params: Mapping[str, Any],
58-
):
59-
if fn.__name__ not in self._lapidary_operations:
60-
operation = get_operation_model(method, path, fn)
61-
self._lapidary_operations[fn.__name__] = operation
62-
else:
63-
operation = self._lapidary_operations[fn.__name__]
64-
65-
auth = self._resolve_auth(fn, security)
66-
67-
request = build_request(
68-
operation,
69-
actual_params,
70-
cast(RequestFactory, self._client.build_request),
71-
)
72-
73-
logger.debug('%s %s %s', request.method, request.url, request.headers)
74-
75-
response = await self._client.send(request, auth=auth)
76-
await response.aread()
77-
78-
return operation.handle_response(response)
79-
80-
def _resolve_auth(self, fn: Callable, security: Optional[Iterable[SecurityRequirements]]) -> AuthType:
81-
if security:
82-
sec_name = fn.__name__
83-
sec_source = security
84-
elif self._security:
85-
sec_name = '*'
86-
sec_source = self._security
87-
else:
88-
sec_name = None
89-
sec_source = None
90-
91-
if sec_source:
92-
assert sec_name
93-
if sec_name not in self._auth_cache:
94-
auth = self._mk_auth(sec_source)
95-
self._auth_cache[sec_name] = auth
96-
else:
97-
auth = self._auth_cache[sec_name]
98-
return auth
99-
else:
100-
return None
42+
return await self._client.__aexit__(__exc_type, __exc_value, __traceback)
10143

10244
def lapidary_authenticate(self, *auth_args: NamedAuth, **auth_kwargs: httpx.Auth) -> None:
10345
"""Register named Auth instances for future use with methods that require authentication."""
10446
if auth_args:
10547
# make python complain about duplicate names
10648
self.lapidary_authenticate(**dict(auth_args), **auth_kwargs)
10749

108-
self._auth.update(auth_kwargs)
109-
self._auth_cache.clear()
50+
self._auth_registry.authenticate(auth_kwargs)
11051

11152
def lapidary_deauthenticate(self, *sec_names: str) -> None:
11253
"""Remove reference to a given Auth instance.
11354
Calling with no parameters removes all references"""
11455

115-
if sec_names:
116-
for sec_name in sec_names:
117-
del self._auth[sec_name]
118-
else:
119-
self._auth.clear()
120-
self._auth_cache.clear()
121-
122-
def _mk_auth(self, security: Iterable[SecurityRequirements]) -> httpx.Auth:
123-
security = list(security)
124-
assert security
125-
last_error: Optional[Exception] = None
126-
for requirements in security:
127-
try:
128-
auth = _build_auth(self._auth, requirements)
129-
break
130-
except ValueError as ve:
131-
last_error = ve
132-
continue
133-
else:
134-
assert last_error
135-
# due to asserts and break above, we never enter here, unless ValueError was raised
136-
raise last_error # noqa
137-
return auth
138-
139-
140-
def _build_auth(schemes: Mapping[str, httpx.Auth], requirements: SecurityRequirements) -> httpx.Auth:
141-
auth_flows = []
142-
for scheme, scopes in requirements.items():
143-
auth_flow = schemes.get(scheme)
144-
if not auth_flow:
145-
raise ValueError('Not authenticated', scheme)
146-
auth_flows.append(auth_flow)
147-
return MultiAuth(*auth_flows)
56+
self._auth_registry.deauthenticate(sec_names)

src/lapidary/runtime/model/annotations.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -105,11 +105,6 @@ def apply_request(self, builder: 'RequestBuilder', value: typing.Any) -> None:
105105
def http_name(self) -> str:
106106
return self.alias or self._name
107107

108-
def __eq__(self, other):
109-
if not isinstance(other, type(self)):
110-
raise NotImplementedError
111-
return self.__dict__ == other.__dict__
112-
113108

114109
T = typing.TypeVar('T')
115110

src/lapidary/runtime/model/auth.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
from collections.abc import Iterable, Mapping, MutableMapping
2+
from typing import Optional
3+
4+
import httpx
5+
6+
from .._httpx import AuthType
7+
from ..types_ import MultiAuth, SecurityRequirements
8+
9+
10+
class AuthRegistry:
11+
def __init__(self, security: Optional[Iterable[SecurityRequirements]]):
12+
# Every Auth instance the user code authenticated with
13+
self._auth: MutableMapping[str, httpx.Auth] = {}
14+
15+
# (Multi)Auth instance for every operation and the client
16+
self._auth_cache: MutableMapping[str, httpx.Auth] = {}
17+
18+
# Client-wide security requirements
19+
self._security = security
20+
21+
def resolve_auth(self, name: str, security: Optional[Iterable[SecurityRequirements]]) -> AuthType:
22+
if security:
23+
sec_name = name
24+
sec_source = security
25+
elif self._security:
26+
sec_name = '*'
27+
sec_source = self._security
28+
else:
29+
sec_name = None
30+
sec_source = None
31+
32+
if sec_source:
33+
assert sec_name
34+
if sec_name not in self._auth_cache:
35+
auth = self._mk_auth(sec_source)
36+
self._auth_cache[sec_name] = auth
37+
else:
38+
auth = self._auth_cache[sec_name]
39+
return auth
40+
else:
41+
return None
42+
43+
def _mk_auth(self, security: Iterable[SecurityRequirements]) -> httpx.Auth:
44+
security = list(security)
45+
assert security
46+
last_error: Optional[Exception] = None
47+
for requirements in security:
48+
try:
49+
auth = _build_auth(self._auth, requirements)
50+
break
51+
except ValueError as ve:
52+
last_error = ve
53+
continue
54+
else:
55+
assert last_error
56+
# due to asserts and break above, we never enter here, unless ValueError was raised
57+
raise last_error # noqa
58+
return auth
59+
60+
def authenticate(self, auth_models: Mapping[str, httpx.Auth]) -> None:
61+
self._auth.update(auth_models)
62+
self._auth_cache.clear()
63+
64+
def deauthenticate(self, sec_names: Iterable[str]) -> None:
65+
if sec_names:
66+
for sec_name in sec_names:
67+
del self._auth[sec_name]
68+
else:
69+
self._auth.clear()
70+
self._auth_cache.clear()
71+
72+
73+
def _build_auth(schemes: Mapping[str, httpx.Auth], requirements: SecurityRequirements) -> httpx.Auth:
74+
auth_flows = []
75+
for scheme, scopes in requirements.items():
76+
auth_flow = schemes.get(scheme)
77+
if not auth_flow:
78+
raise ValueError('Not authenticated', scheme)
79+
auth_flows.append(auth_flow)
80+
return MultiAuth(*auth_flows)

0 commit comments

Comments
 (0)