@@ -12,20 +12,25 @@ class UnsupportedAuthenticationError(Exception):
12
12
"""
13
13
Raised when an unsupported authentication method is used.
14
14
"""
15
+
15
16
def __init__ (self , auth_type ):
16
17
super ().__init__ (f"unsupported authentication type: { auth_type } " )
17
18
19
+
18
20
class AuthenticationError (Exception ):
19
21
"""
20
22
Raised when an unknown authentication error is encountered.
21
23
"""
24
+
22
25
def __init__ (self , user ):
23
26
super ().__init__ (f"failed to authenticate as user: { user } " )
24
27
28
+
25
29
class CatalogError (Exception ):
26
30
"""
27
31
Raised when an unknown catalog service type is requested.
28
32
"""
33
+
29
34
def __init__ (self , name ):
30
35
super ().__init__ (f"service type { name } not found in OpenStack service catalog" )
31
36
@@ -34,7 +39,10 @@ class Auth(httpx.Auth):
34
39
"""
35
40
Authenticator class for OpenStack connections.
36
41
"""
37
- def __init__ (self , auth_url , application_credential_id , application_credential_secret ):
42
+
43
+ def __init__ (
44
+ self , auth_url , application_credential_id , application_credential_secret
45
+ ):
38
46
self .url = auth_url
39
47
self ._application_credential_id = application_credential_id
40
48
self ._application_credential_secret = application_credential_secret
@@ -58,7 +66,7 @@ def _build_token_request(self):
58
66
return httpx .Request (
59
67
"POST" ,
60
68
f"{ self .url } /v3/auth/tokens" ,
61
- json = {
69
+ json = {
62
70
"auth" : {
63
71
"identity" : {
64
72
"methods" : ["application_credential" ],
@@ -68,7 +76,7 @@ def _build_token_request(self):
68
76
},
69
77
},
70
78
},
71
- }
79
+ },
72
80
)
73
81
74
82
def _handle_token_response (self , response ):
@@ -82,15 +90,16 @@ async def async_auth_flow(self, request):
82
90
response = yield self ._build_token_request ()
83
91
await response .aread ()
84
92
self ._handle_token_response (response )
85
- request .headers [' X-Auth-Token' ] = self ._token
93
+ request .headers [" X-Auth-Token" ] = self ._token
86
94
response = yield request
87
95
88
96
89
97
class Resource (rest .Resource ):
90
98
"""
91
99
Base resource for OpenStack APIs.
92
100
"""
93
- def __init__ (self , client , name , prefix = None , plural_name = None , singular_name = None ):
101
+
102
+ def __init__ (self , client , name , prefix = None , plural_name = None , singular_name = None ):
94
103
super ().__init__ (client , name , prefix )
95
104
# Some resources support a /detail endpoint
96
105
# In this case, we just want to use the name up to the slash as the plural name
@@ -114,7 +123,7 @@ def _extract_next_page(self, response):
114
123
for link in response .json ().get (f"{ self ._plural_name } _links" , [])
115
124
if link ["rel" ] == "next"
116
125
),
117
- None
126
+ None ,
118
127
)
119
128
# Sometimes, the returned URLs have http where they should have https
120
129
# To mitigate this, we split the URL and return the path and params separately
@@ -134,24 +143,27 @@ class Client(rest.AsyncClient):
134
143
"""
135
144
Client for OpenStack APIs.
136
145
"""
137
- def __init__ (self , / , base_url , prefix = None , ** kwargs ):
146
+
147
+ def __init__ (self , / , base_url , prefix = None , ** kwargs ):
138
148
# Extract the path part of the base_url
139
149
url = urllib .parse .urlsplit (base_url )
140
150
# Initialise the client with the scheme/host
141
- super ().__init__ (base_url = f"{ url .scheme } ://{ url .netloc } " , ** kwargs )
151
+ super ().__init__ (base_url = f"{ url .scheme } ://{ url .netloc } " , ** kwargs )
142
152
# If another prefix is not given, use the path from the base URL as the prefix,
143
153
# otherwise combine the prefixes and remove duplicated path sections.
144
154
# This ensures things like pagination work nicely without duplicating the prefix
145
155
if prefix :
146
- self ._prefix = "/" .join ([url .path .rstrip ("/" ), prefix .lstrip ("/" ).lstrip (url .path )])
156
+ self ._prefix = "/" .join (
157
+ [url .path .rstrip ("/" ), prefix .lstrip ("/" ).lstrip (url .path )]
158
+ )
147
159
else :
148
160
self ._prefix = url .path
149
161
150
162
def __aenter__ (self ):
151
163
# Prevent individual clients from being used in a context manager
152
164
raise RuntimeError ("clients must be used via a cloud object" )
153
165
154
- def resource (self , name , prefix = None , plural_name = None , singular_name = None ):
166
+ def resource (self , name , prefix = None , plural_name = None , singular_name = None ):
155
167
# If an additional prefix is given, combine it with the existing prefix
156
168
if prefix :
157
169
prefix = "/" .join ([self ._prefix .rstrip ("/" ), prefix .lstrip ("/" )])
@@ -164,7 +176,8 @@ class Cloud:
164
176
"""
165
177
Object for interacting with OpenStack clouds.
166
178
"""
167
- def __init__ (self , auth , transport , interface , region = None ):
179
+
180
+ def __init__ (self , auth , transport , interface , region = None ):
168
181
self ._auth = auth
169
182
self ._transport = transport
170
183
self ._interface = interface
@@ -176,7 +189,9 @@ def __init__(self, auth, transport, interface, region = None):
176
189
async def __aenter__ (self ):
177
190
await self ._transport .__aenter__ ()
178
191
# Once the transport has been initialised, we can initialise the endpoints
179
- client = Client (base_url = self ._auth .url , auth = self ._auth , transport = self ._transport )
192
+ client = Client (
193
+ base_url = self ._auth .url , auth = self ._auth , transport = self ._transport
194
+ )
180
195
try :
181
196
response = await client .get ("/v3/auth/catalog" )
182
197
except httpx .HTTPStatusError as exc :
@@ -190,8 +205,8 @@ async def __aenter__(self):
190
205
ep ["url" ]
191
206
for ep in entry ["endpoints" ]
192
207
if (
193
- ep ["interface" ] == self ._interface and
194
- (not self ._region or ep ["region" ] == self ._region )
208
+ ep ["interface" ] == self ._interface
209
+ and (not self ._region or ep ["region" ] == self ._region )
195
210
)
196
211
)
197
212
for entry in response .json ()["catalog" ]
@@ -223,16 +238,16 @@ def apis(self):
223
238
"""
224
239
return list (self ._endpoints .keys ())
225
240
226
- def api_client (self , name , prefix = None ):
241
+ def api_client (self , name , prefix = None ):
227
242
"""
228
243
Returns a client for the named API.
229
244
"""
230
245
if name not in self ._clients :
231
246
self ._clients [name ] = Client (
232
- base_url = self ._endpoints [name ],
233
- prefix = prefix ,
234
- auth = self ._auth ,
235
- transport = self ._transport
247
+ base_url = self ._endpoints [name ],
248
+ prefix = prefix ,
249
+ auth = self ._auth ,
250
+ transport = self ._transport ,
236
251
)
237
252
return self ._clients [name ]
238
253
@@ -245,13 +260,13 @@ def from_clouds(cls, clouds, cloud, cacert):
245
260
auth = Auth (
246
261
auth_url ,
247
262
config ["auth" ]["application_credential_id" ],
248
- config ["auth" ]["application_credential_secret" ]
263
+ config ["auth" ]["application_credential_secret" ],
249
264
)
250
265
region = config .get ("region_name" )
251
266
# Create a default context using the verification from the config
252
- context = httpx .create_ssl_context (verify = config .get ("verify" , True ))
267
+ context = httpx .create_ssl_context (verify = config .get ("verify" , True ))
253
268
# If a cacert was given, load it into the context
254
269
if cacert is not None :
255
- context .load_verify_locations (cadata = cacert )
256
- transport = httpx .AsyncHTTPTransport (verify = context )
270
+ context .load_verify_locations (cadata = cacert )
271
+ transport = httpx .AsyncHTTPTransport (verify = context )
257
272
return cls (auth , transport , config .get ("interface" , "public" ), region )
0 commit comments