esd-services-api-client 0.9.0__py3-none-any.whl → 0.9.2__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.
@@ -1 +1 @@
1
- __version__ = '0.9.0'
1
+ __version__ = '0.9.2'
@@ -68,3 +68,24 @@ from esd_services_api_client.boxer import BoxerConnector
68
68
  conn = BoxerConnector(base_url="https://boxer.test.sneaksanddata.com")
69
69
  pub_key = conn.get_consumer_public_key("app_consumer")
70
70
  ```
71
+
72
+ ### Using as an authentication provider for other connectors
73
+ ```python
74
+ from esd_services_api_client.boxer import BoxerConnector, RefreshableExternalTokenAuth, BoxerTokenAuth
75
+ from esd_services_api_client.crystal import CrystalConnector
76
+
77
+ auth_method = "example"
78
+
79
+ def get_external_token() -> str:
80
+ return "example_token"
81
+
82
+ # Configure authentication with boxer
83
+ external_auth = RefreshableExternalTokenAuth(lambda: get_external_token(), auth_method)
84
+ boxer_connector = BoxerConnector(base_url="https://example.com", auth=external_auth)
85
+
86
+ # Inject boxer auth to Crystal connector
87
+ connector = CrystalConnector(base_url="https://example.com", auth=BoxerTokenAuth(boxer_connector))
88
+
89
+ # Use Crystal connector with boxer auth
90
+ connector.await_runs("algorithm", ["id"])
91
+ ```
@@ -18,6 +18,7 @@
18
18
  #
19
19
 
20
20
  import base64
21
+ from abc import abstractmethod
21
22
  from functools import partial
22
23
  from typing import Callable, Any
23
24
 
@@ -75,12 +76,33 @@ class BoxerAuth(AuthBase):
75
76
  return request
76
77
 
77
78
 
78
- class ExternalTokenAuth(AuthBase):
79
- """Create authentication for external token e.g. for azuread or kubernetes auth policies"""
79
+ class ExternalAuthBase(AuthBase):
80
+ """Base class for external authentication methods"""
81
+
82
+ def __init__(self, authentication_provider):
83
+ self._authentication_provider = authentication_provider
84
+
85
+ @abstractmethod
86
+ def __call__(self, r: PreparedRequest) -> PreparedRequest:
87
+ pass
88
+
89
+ @property
90
+ def authentication_provider(self) -> str:
91
+ """
92
+ :return authentication provider name
93
+ """
94
+ return self._authentication_provider
95
+
96
+
97
+ class ExternalTokenAuth(ExternalAuthBase):
98
+ """
99
+ Create authentication for external token e.g. for azuread or kubernetes auth policies
100
+ NOTE: this class is deprecated, use RefreshableExternalTokenAuth instead
101
+ """
80
102
 
81
103
  def __init__(self, token: str, authentication_provider: str):
104
+ super().__init__(authentication_provider)
82
105
  self._token = token
83
- self._authentication_provider = authentication_provider
84
106
 
85
107
  def __call__(self, r: PreparedRequest) -> PreparedRequest:
86
108
  """
@@ -92,12 +114,55 @@ class ExternalTokenAuth(AuthBase):
92
114
  r.headers["Authorization"] = f"Bearer {self._token}"
93
115
  return r
94
116
 
95
- @property
96
- def authentication_provider(self) -> str:
117
+
118
+ class RefreshableExternalTokenAuth(ExternalAuthBase):
119
+ """
120
+ Create authentication for external token e.g. for azuread or kubernetes auth policies
121
+ If the external token is expired, this auth method will try to get new external token and retry the request once
122
+ """
123
+
124
+ def __init__(self, get_token: Callable[[], str], authentication_provider: str):
125
+ super().__init__(authentication_provider)
126
+ self._get_token = get_token
127
+ self._retrying = False
128
+
129
+ def __call__(self, r: PreparedRequest) -> PreparedRequest:
97
130
  """
98
- :return authentication provider name
131
+ Auth entrypoint
132
+
133
+ :param r: Request to authorize
134
+ :return: Request with Auth header set
99
135
  """
100
- return self._authentication_provider
136
+ r.headers["Authorization"] = f"Bearer {self._get_token()}"
137
+ return r
138
+
139
+ def refresh_token(self, response: Response, session: Session, *_, **__):
140
+ """
141
+ Refresh token hook if request fails with unauthorized or forbidden status code and retries the request.
142
+ :param response: Response received from API server
143
+ :param session: Session used for original API interaction
144
+ :param _: Positional arguments
145
+ :param __: Keyword arguments
146
+ :return:
147
+ """
148
+ if self._retrying:
149
+ return response
150
+ if response.status_code == requests.codes["unauthorized"]:
151
+ self._retrying = True
152
+ response = session.send(self(response.request))
153
+ self._retrying = False
154
+ return response
155
+ return response
156
+
157
+ def get_refresh_hook(
158
+ self, session: Session
159
+ ) -> Callable[[Response, Unpack[Any]], Response]:
160
+ """
161
+ Generate request hook
162
+ :param session: Session used for original API interaction
163
+ :returns
164
+ """
165
+ return partial(self.refresh_token, session=session)
101
166
 
102
167
 
103
168
  class BoxerTokenAuth(AuthBase):
@@ -29,6 +29,8 @@ from esd_services_api_client.boxer._auth import (
29
29
  BoxerAuth,
30
30
  ExternalTokenAuth,
31
31
  BoxerTokenAuth,
32
+ ExternalAuthBase,
33
+ RefreshableExternalTokenAuth,
32
34
  )
33
35
  from esd_services_api_client.boxer._helpers import (
34
36
  _iterate_user_claims_response,
@@ -46,7 +48,7 @@ class BoxerConnector(BoxerTokenProvider):
46
48
  self,
47
49
  *,
48
50
  base_url,
49
- auth: ExternalTokenAuth,
51
+ auth: ExternalAuthBase,
50
52
  retry_attempts=10,
51
53
  session: Optional[Session] = None,
52
54
  ):
@@ -59,6 +61,8 @@ class BoxerConnector(BoxerTokenProvider):
59
61
  self.http.auth = auth or self._create_boxer_auth()
60
62
  if auth:
61
63
  self.authentication_provider = auth.authentication_provider
64
+ if isinstance(auth, RefreshableExternalTokenAuth):
65
+ self.http.hooks["response"].append(auth.get_refresh_hook(self.http))
62
66
  self.retry_attempts = retry_attempts
63
67
 
64
68
  def push_user_claim(self, claim: BoxerClaim, user_id: str):
@@ -185,8 +189,8 @@ def select_authentication(auth_provider: str, env: str) -> Optional[BoxerTokenAu
185
189
  """
186
190
  if auth_provider == "azuread":
187
191
  proteus_client = AzureClient(subscription_id="")
188
- external_auth = ExternalTokenAuth(
189
- proteus_client.get_access_token(), auth_provider
192
+ external_auth = RefreshableExternalTokenAuth(
193
+ proteus_client.get_access_token, auth_provider
190
194
  )
191
195
  boxer_connector = BoxerConnector(
192
196
  base_url=f"https://boxer.{env}.sneaksanddata.com", auth=external_auth
@@ -97,6 +97,8 @@ class CrystalConnector:
97
97
  retry_count=default_retry_count,
98
98
  request_timeout=default_timeout.total_seconds(),
99
99
  )
100
+ if auth and isinstance(auth, BoxerTokenAuth):
101
+ self.http.hooks["response"].append(auth.get_refresh_hook(self.http))
100
102
  self._api_version = api_version
101
103
  self._logger = logger
102
104
  if isinstance(auth, BoxerTokenAuth):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: esd-services-api-client
3
- Version: 0.9.0
3
+ Version: 0.9.2
4
4
  Summary: Python clients for ESD services
5
5
  Home-page: https://github.com/SneaksAndData/esd-services-api-client
6
6
  License: Apache 2.0
@@ -1,22 +1,22 @@
1
1
  esd_services_api_client/__init__.py,sha256=rP0njtEgVSMm-sOVayVfcRUrrubl4lme7HI2zS678Lo,598
2
- esd_services_api_client/_version.py,sha256=_DlwPNmTZTKflLiAF8YIqSr2ao66CTjj4bQtA6hz0jM,22
2
+ esd_services_api_client/_version.py,sha256=HSMl6bLm3r1Ias1jHIPpJeQ0IYNuuSdlT38MTb79ewM,22
3
3
  esd_services_api_client/beast/__init__.py,sha256=XU4thkgkY6ZvT4yyDpaqeiIg3yotfcyJEewpo8iica4,743
4
4
  esd_services_api_client/beast/_auth.py,sha256=V9m75EaFP8U_Ykf4DE5SWSSUrH8_UP_ugrmayJbwdmw,2666
5
5
  esd_services_api_client/beast/_connector.py,sha256=_OGMr1qB1SyfMw0JLrCc2g7Ph4LrZ7JWa2etPbn2JTA,10505
6
6
  esd_services_api_client/beast/_models.py,sha256=XOTZqr83mX44SpXG4KBDEHJozVuDQvQcaP_ctmS98hk,9438
7
- esd_services_api_client/boxer/README.md,sha256=RGwJhADPzPH05qjZm0BaCI-9GOfhUgTnm5Is7zldnR8,2221
7
+ esd_services_api_client/boxer/README.md,sha256=U8kXXtJFi1w0woQ4F_UOdDZiQl6-K7zFmQEUV4EktfM,2983
8
8
  esd_services_api_client/boxer/__init__.py,sha256=OYsWvdnLan0kmjUcH4I2-m1rbPeARKp5iqhp8uyudPk,780
9
- esd_services_api_client/boxer/_auth.py,sha256=EQkNod3RtIwARr5Mn2MbNCbzJI0BPrPCgap98CI2Yh4,5214
9
+ esd_services_api_client/boxer/_auth.py,sha256=vA7T9y0oZV2f17UWQ2or9CK8vAsNnHB10G5HNQe1l1I,7440
10
10
  esd_services_api_client/boxer/_base.py,sha256=PSU08QzTKFkXfzx7fF5FIHBOZXshxNEd1J_qKGo0Rd0,976
11
- esd_services_api_client/boxer/_connector.py,sha256=b4_3Pg-wjHoapTUX4jmdFif7pu5CAq_FN0bICPcxfAQ,8272
11
+ esd_services_api_client/boxer/_connector.py,sha256=OBNQHlXHM3V6LWTlLFBpDBgS-m9Q3n-sLlqSGPAz4rQ,8476
12
12
  esd_services_api_client/boxer/_helpers.py,sha256=hjf96CWYEH2iThYDcBB1iEGzalUWn-S9PZJzZJ6bdG0,1736
13
13
  esd_services_api_client/boxer/_models.py,sha256=2tdVNRGQ0roR3THfzTq7KF2eQ5mS30PbxpqYhPm1y6A,3105
14
14
  esd_services_api_client/common/__init__.py,sha256=rP0njtEgVSMm-sOVayVfcRUrrubl4lme7HI2zS678Lo,598
15
15
  esd_services_api_client/crystal/__init__.py,sha256=afSGQRkDic0ECsJfgu3b291kX8CyU57sjreqxj5cx-s,787
16
16
  esd_services_api_client/crystal/_api_versions.py,sha256=2BMiQRS0D8IEpWCCys3dge5alVBRCZrOuCR1QAn8UIM,832
17
- esd_services_api_client/crystal/_connector.py,sha256=3oDR-egxj6zCvlLEWmPYJvKomE-4AhzFYud8S2WRkEE,12015
17
+ esd_services_api_client/crystal/_connector.py,sha256=lWWKhVKvaFtLTsl6abtydFH4rsMoCATD_IMpVOARyl0,12150
18
18
  esd_services_api_client/crystal/_models.py,sha256=eRhGAl8LjglCyIFwf1bcFBhjbpSuRYucuF2LO388L2E,4025
19
- esd_services_api_client-0.9.0.dist-info/LICENSE,sha256=0gS6zXsPp8qZhzi1xaGCIYPzb_0e8on7HCeFJe8fOpw,10693
20
- esd_services_api_client-0.9.0.dist-info/METADATA,sha256=L3LqysaPF4LyK7Ktuoe_GPQtgKGWmPu9B3xBzwsAkAk,1075
21
- esd_services_api_client-0.9.0.dist-info/WHEEL,sha256=d2fvjOD7sXsVzChCqf0Ty0JbHKBaLYwDbGQDwQTnJ50,88
22
- esd_services_api_client-0.9.0.dist-info/RECORD,,
19
+ esd_services_api_client-0.9.2.dist-info/LICENSE,sha256=0gS6zXsPp8qZhzi1xaGCIYPzb_0e8on7HCeFJe8fOpw,10693
20
+ esd_services_api_client-0.9.2.dist-info/METADATA,sha256=aMe49RZg_WaTlwlwSvtaxyWYOmJOhF6WDN7-MZpaqyc,1075
21
+ esd_services_api_client-0.9.2.dist-info/WHEEL,sha256=d2fvjOD7sXsVzChCqf0Ty0JbHKBaLYwDbGQDwQTnJ50,88
22
+ esd_services_api_client-0.9.2.dist-info/RECORD,,