3
3
import asyncio
4
4
import logging
5
5
import uuid
6
+ from abc import ABC
6
7
from typing import Awaitable , Optional
7
8
8
9
from aiohttp import BasicAuth , ClientSession , ClientTimeout
9
10
from more_itertools import chunked
10
11
from yarl import URL
11
12
12
- from bssclient .client .config import BssConfig
13
+ from bssclient .client .config import BasicAuthBssConfig , BssConfig , OAuthBssConfig
14
+ from bssclient .client .oauth import _OAuthHttpClient
13
15
from bssclient .models .aufgabe import AufgabeStats
14
16
from bssclient .models .ermittlungsauftrag import Ermittlungsauftrag , _ListOfErmittlungsauftraege
15
17
16
18
_logger = logging .getLogger (__name__ )
17
19
18
20
19
- class BssClient :
21
+ class BssClient ( ABC ) :
20
22
"""
21
23
an async wrapper around the BSS API
22
24
"""
23
25
24
26
def __init__ (self , config : BssConfig ):
25
27
self ._config = config
26
- self ._auth = BasicAuth (login = self ._config .usr , password = self ._config .pwd )
27
28
self ._session_lock = asyncio .Lock ()
28
29
self ._session : Optional [ClientSession ] = None
29
30
_logger .info ("Instantiated BssClient with server_url %s" , str (self ._config .server_url ))
30
31
32
+ async def _get_session (self ):
33
+ raise NotImplementedError ("The inheriting class has to implement this with its respective authentication" )
34
+
31
35
def get_top_level_domain (self ) -> URL | None :
32
36
"""
33
37
Returns the top level domain of the server_url; this is useful to differentiate prod from test systems.
@@ -47,24 +51,6 @@ def get_top_level_domain(self) -> URL | None:
47
51
tld = "." .join (domain_parts [- 2 :])
48
52
return URL (self ._config .server_url .scheme + "://" + tld )
49
53
50
- async def _get_session (self ) -> ClientSession :
51
- """
52
- returns a client session (that may be reused or newly created)
53
- re-using the same (threadsafe) session will be faster than re-creating a new session for every request.
54
- see https://docs.aiohttp.org/en/stable/http_request_lifecycle.html#how-to-use-the-clientsession
55
- """
56
- async with self ._session_lock :
57
- if self ._session is None or self ._session .closed :
58
- _logger .info ("creating new session" )
59
- self ._session = ClientSession (
60
- auth = self ._auth ,
61
- timeout = ClientTimeout (60 ),
62
- raise_for_status = True ,
63
- )
64
- else :
65
- _logger .log (5 , "reusing aiohttp session" ) # log level 5 is half as "loud" logging.DEBUG
66
- return self ._session
67
-
68
54
async def close_session (self ):
69
55
"""
70
56
closes the client session
@@ -167,3 +153,72 @@ async def get_all_ermittlungsauftraege(self, package_size: int = 100) -> list[Er
167
153
result .extend ([item for sublist in list_of_lists_of_io_from_chunk for item in sublist ])
168
154
_logger .info ("Downloaded %i Ermittlungsautraege" , len (result ))
169
155
return result
156
+
157
+
158
+ class BasicAuthBssClient (BssClient ):
159
+ """BSS client with basic auth"""
160
+
161
+ def __init__ (self , config : BasicAuthBssConfig ):
162
+ """instantiate by providing a valid config"""
163
+ if not isinstance (config , BasicAuthBssConfig ):
164
+ raise ValueError ("You must provide a valid config" )
165
+ super ().__init__ (config )
166
+ self ._auth = BasicAuth (login = config .usr , password = config .pwd )
167
+
168
+ async def _get_session (self ) -> ClientSession :
169
+ """
170
+ returns a client session (that may be reused or newly created)
171
+ re-using the same (threadsafe) session will be faster than re-creating a new session for every request.
172
+ see https://docs.aiohttp.org/en/stable/http_request_lifecycle.html#how-to-use-the-clientsession
173
+ """
174
+ async with self ._session_lock :
175
+ if self ._session is None or self ._session .closed :
176
+ _logger .info ("creating new session" )
177
+ self ._session = ClientSession (
178
+ auth = self ._auth ,
179
+ timeout = ClientTimeout (60 ),
180
+ raise_for_status = True ,
181
+ )
182
+ else :
183
+ _logger .log (5 , "reusing aiohttp session" ) # log level 5 is half as "loud" logging.DEBUG
184
+ return self ._session
185
+
186
+
187
+ class OAuthBssClient (BssClient , _OAuthHttpClient ):
188
+ """BSS client with OAuth"""
189
+
190
+ def __init__ (self , config : OAuthBssConfig ):
191
+ if not isinstance (config , OAuthBssConfig ):
192
+ raise ValueError ("You must provide a valid config" )
193
+ super ().__init__ (config )
194
+ _OAuthHttpClient .__init__ (
195
+ self ,
196
+ base_url = config .server_url ,
197
+ oauth_client_id = config .client_id ,
198
+ oauth_client_secret = config .client_secret ,
199
+ oauth_token_url = str (config .token_url ),
200
+ )
201
+ self ._oauth_config = config
202
+ self ._bearer_token : str | None = None
203
+
204
+ async def _get_session (self ) -> ClientSession :
205
+ """
206
+ returns a client session (that may be reused or newly created)
207
+ re-using the same (threadsafe) session will be faster than re-creating a new session for every request.
208
+ see https://docs.aiohttp.org/en/stable/http_request_lifecycle.html#how-to-use-the-clientsession
209
+ """
210
+ async with self ._session_lock :
211
+ if self ._bearer_token is None :
212
+ self ._bearer_token = await self ._get_oauth_token ()
213
+ elif not self ._token_is_valid (self ._bearer_token ):
214
+ await self .close_session ()
215
+ if self ._session is None or self ._session .closed :
216
+ _logger .info ("creating new session" )
217
+ self ._session = ClientSession (
218
+ timeout = ClientTimeout (60 ),
219
+ raise_for_status = True ,
220
+ headers = {"Authorization" : f"Bearer { self ._bearer_token } " },
221
+ )
222
+ else :
223
+ _logger .log (5 , "reusing aiohttp session" ) # log level 5 is half as "loud" logging.DEBUG
224
+ return self ._session
0 commit comments