37
37
import aiohttp
38
38
39
39
from . import database , document
40
- from .exception import NotFoundError , generator_raises , raises
40
+ from .exception import ClientResponseError , NotFoundError , generator_raises , raises
41
41
from .typing import JsonDict
42
42
43
43
@@ -160,7 +160,21 @@ async def _request(
160
160
async with self ._http_session .request (
161
161
method , url = f"{ self ._server } { path } " , ** kwargs
162
162
) as resp :
163
- resp .raise_for_status ()
163
+ if not resp .ok :
164
+ reason = None
165
+ with suppress (Exception ):
166
+ reason = (await resp .json ())["reason" ]
167
+ # Copied from aiohttp v3.9.5 raise_for_status():
168
+ assert resp .reason is not None
169
+ resp .release ()
170
+ raise ClientResponseError (
171
+ reason ,
172
+ resp .request_info ,
173
+ resp .history ,
174
+ status = resp .status ,
175
+ message = resp .reason ,
176
+ headers = resp .headers ,
177
+ )
164
178
return (
165
179
HTTPResponse (resp ),
166
180
await resp .json () if return_json else await resp .read (),
@@ -179,14 +193,29 @@ async def _streamed_request(
179
193
async with self ._http_session .request (
180
194
method , url = f"{ self ._server } { path } " , ** kwargs
181
195
) as resp :
182
- resp .raise_for_status ()
196
+ if not resp .ok :
197
+ reason = None
198
+ with suppress (Exception ):
199
+ reason = (await resp .json ())["reason" ]
200
+ # Copied from aiohttp v3.9.5 raise_for_status():
201
+ assert resp .reason is not None
202
+ resp .release ()
203
+ raise ClientResponseError (
204
+ reason ,
205
+ resp .request_info ,
206
+ resp .history ,
207
+ status = resp .status ,
208
+ message = resp .reason ,
209
+ headers = resp .headers ,
210
+ )
183
211
184
212
async for line in resp .content :
185
213
# this should only happen for empty lines
186
214
with suppress (json .JSONDecodeError ):
187
215
yield json .loads (line )
188
216
189
217
@raises (401 , "Invalid credentials" )
218
+ @raises (403 , "Access forbidden: {reason}" )
190
219
async def _all_dbs (self , ** params : Any ) -> List [str ]:
191
220
_ , json = await self ._get ("/_all_dbs" , params )
192
221
assert not isinstance (json , bytes )
@@ -203,12 +232,14 @@ async def close(self) -> None:
203
232
await asyncio .sleep (0.250 if has_ssl_conn else 0 )
204
233
205
234
@raises (401 , "Invalid credentials" )
235
+ @raises (403 , "Access forbidden: {reason}" )
206
236
async def _info (self ) -> JsonDict :
207
237
_ , json = await self ._get ("/" )
208
238
assert not isinstance (json , bytes )
209
239
return json
210
240
211
241
@raises (401 , "Authentication failed, check provided credentials." )
242
+ @raises (403 , "Access forbidden: {reason}" )
212
243
async def _check_session (self ) -> RequestResult :
213
244
return await self ._get ("/_session" )
214
245
@@ -223,19 +254,19 @@ def endpoint(self) -> str:
223
254
return f"/{ _quote_id (self .id )} "
224
255
225
256
@raises (401 , "Invalid credentials" )
226
- @raises (403 , "Read permission required " )
257
+ @raises (403 , "Access forbidden: {reason} " )
227
258
async def _exists (self ) -> bool :
228
259
try :
229
260
await self ._remote ._head (self .endpoint )
230
261
return True
231
- except aiohttp . ClientResponseError as e :
262
+ except ClientResponseError as e :
232
263
if e .status == 404 :
233
264
return False
234
265
else :
235
266
raise e
236
267
237
268
@raises (401 , "Invalid credentials" )
238
- @raises (403 , "Read permission required " )
269
+ @raises (403 , "Access forbidden: {reason} " )
239
270
@raises (404 , "Requested database not found ({id})" )
240
271
async def _get (self ) -> JsonDict :
241
272
_ , json = await self ._remote ._get (self .endpoint )
@@ -244,6 +275,7 @@ async def _get(self) -> JsonDict:
244
275
245
276
@raises (400 , "Invalid database name" )
246
277
@raises (401 , "CouchDB Server Administrator privileges required" )
278
+ @raises (403 , "Access forbidden: {reason}" )
247
279
@raises (412 , "Database already exists" )
248
280
async def _put (self , ** params : Any ) -> JsonDict :
249
281
_ , json = await self ._remote ._put (self .endpoint , params = params )
@@ -252,13 +284,14 @@ async def _put(self, **params: Any) -> JsonDict:
252
284
253
285
@raises (400 , "Invalid database name or forgotten document id by accident" )
254
286
@raises (401 , "CouchDB Server Administrator privileges required" )
287
+ @raises (403 , "Access forbidden: {reason}" )
255
288
@raises (404 , "Database doesn't exist or invalid database name ({id})" )
256
289
async def _delete (self ) -> None :
257
290
await self ._remote ._delete (self .endpoint )
258
291
259
292
@raises (400 , "The request provided invalid JSON data or invalid query parameter" )
260
293
@raises (401 , "Read permission required" )
261
- @raises (403 , "Read permission required " )
294
+ @raises (403 , "Access forbidden: {reason} " )
262
295
@raises (404 , "Invalid database name" )
263
296
@raises (415 , "Bad Content-Type value" )
264
297
async def _bulk_get (self , docs : List [str ], ** params : Any ) -> JsonDict :
@@ -270,7 +303,7 @@ async def _bulk_get(self, docs: List[str], **params: Any) -> JsonDict:
270
303
271
304
@raises (400 , "The request provided invalid JSON data" )
272
305
@raises (401 , "Invalid credentials" )
273
- @raises (403 , "Write permission required " )
306
+ @raises (403 , "Access forbidden: {reason} " )
274
307
@raises (417 , "At least one document was rejected by the validation function" )
275
308
async def _bulk_docs (self , docs : List [JsonDict ], ** data : Any ) -> JsonDict :
276
309
data ["docs" ] = docs
@@ -280,7 +313,7 @@ async def _bulk_docs(self, docs: List[JsonDict], **data: Any) -> JsonDict:
280
313
281
314
@raises (400 , "Invalid request" )
282
315
@raises (401 , "Read privilege required for document '{id}'" )
283
- @raises (403 , "Read permission required " )
316
+ @raises (403 , "Access forbidden: {reason} " )
284
317
@raises (500 , "Query execution failed" , RuntimeError )
285
318
async def _find (self , selector : Any , ** data : Any ) -> JsonDict :
286
319
data ["selector" ] = selector
@@ -290,6 +323,7 @@ async def _find(self, selector: Any, **data: Any) -> JsonDict:
290
323
291
324
@raises (400 , "Invalid request" )
292
325
@raises (401 , "Admin permission required" )
326
+ @raises (403 , "Access forbidden: {reason}" )
293
327
@raises (404 , "Database not found" )
294
328
@raises (500 , "Execution error" )
295
329
async def _index (self , index : JsonDict , ** data : Any ) -> JsonDict :
@@ -299,20 +333,21 @@ async def _index(self, index: JsonDict, **data: Any) -> JsonDict:
299
333
return json
300
334
301
335
@raises (401 , "Invalid credentials" )
302
- @raises (403 , "Permission required " )
336
+ @raises (403 , "Access forbidden: {reason} " )
303
337
async def _get_security (self ) -> JsonDict :
304
338
_ , json = await self ._remote ._get (f"{ self .endpoint } /_security" )
305
339
assert not isinstance (json , bytes )
306
340
return json
307
341
308
342
@raises (401 , "Invalid credentials" )
309
- @raises (403 , "Permission required " )
343
+ @raises (403 , "Access forbidden: {reason} " )
310
344
async def _put_security (self , doc : JsonDict ) -> JsonDict :
311
345
_ , json = await self ._remote ._put (f"{ self .endpoint } /_security" , doc )
312
346
assert not isinstance (json , bytes )
313
347
return json
314
348
315
349
@generator_raises (400 , "Invalid request" )
350
+ @generator_raises (403 , "Access forbidden: {reason}" )
316
351
async def _changes (self , ** params : Any ) -> AsyncGenerator [JsonDict , None ]:
317
352
if "feed" in params and params ["feed" ] == "continuous" :
318
353
params .setdefault ("heartbeat" , True )
@@ -329,6 +364,7 @@ async def _changes(self, **params: Any) -> AsyncGenerator[JsonDict, None]:
329
364
yield result
330
365
331
366
@raises (400 , "Invalid database or JSON payload" )
367
+ @raises (403 , "Access forbidden: {reason}" )
332
368
@raises (415 , "Bad Content-Type header value" )
333
369
@raises (500 , "Internal server error or timeout" )
334
370
async def _purge (self , docs : JsonDict , ** params : Any ) -> JsonDict :
@@ -350,13 +386,13 @@ def endpoint(self) -> str:
350
386
return f"{ self ._database .endpoint } /{ _quote_id (self .id )} "
351
387
352
388
@raises (401 , "Read privilege required for document '{id}'" )
353
- @raises (403 , "Read privilege required for document '{id}' " )
389
+ @raises (403 , "Access forbidden: {reason} " )
354
390
@raises (404 , "Document {id} was not found" )
355
391
async def _head (self ) -> None :
356
392
await self ._database ._remote ._head (self .endpoint )
357
393
358
394
@raises (401 , "Read privilege required for document '{id}'" )
359
- @raises (403 , "Read privilege required for document '{id}' " )
395
+ @raises (403 , "Access forbidden: {reason} " )
360
396
@raises (404 , "Document {id} was not found" )
361
397
async def _info (self ) -> JsonDict :
362
398
response , _ = await self ._database ._remote ._head (self .endpoint )
@@ -376,7 +412,7 @@ async def _exists(self) -> bool:
376
412
377
413
@raises (400 , "The format of the request or revision was invalid" )
378
414
@raises (401 , "Read privilege required for document '{id}'" )
379
- @raises (403 , "Read privilege required for document '{id}' " )
415
+ @raises (403 , "Access forbidden: {reason} " )
380
416
@raises (404 , "Document {id} was not found" )
381
417
async def _get (self , ** params : Any ) -> JsonDict :
382
418
_ , json = await self ._database ._remote ._get (self .endpoint , params )
@@ -385,7 +421,7 @@ async def _get(self, **params: Any) -> JsonDict:
385
421
386
422
@raises (400 , "The format of the request or revision was invalid" )
387
423
@raises (401 , "Write privilege required for document '{id}'" )
388
- @raises (403 , "Write privilege required for document '{id}' " )
424
+ @raises (403 , "Access forbidden: {reason} " )
389
425
@raises (404 , "Specified database or document ID doesn't exists ({endpoint})" )
390
426
@raises (
391
427
409 ,
@@ -401,7 +437,7 @@ async def _put(
401
437
402
438
@raises (400 , "Invalid request body or parameters" )
403
439
@raises (401 , "Write privilege required for document '{id}'" )
404
- @raises (403 , "Write privilege required for document '{id}' " )
440
+ @raises (403 , "Access forbidden: {reason} " )
405
441
@raises (404 , "Specified database or document ID doesn't exists ({endpoint})" )
406
442
@raises (
407
443
409 , "Specified revision ({rev}) is not the latest for target document '{id}'"
@@ -414,7 +450,7 @@ async def _delete(self, rev: str, **params: Any) -> Tuple[HTTPResponse, JsonDict
414
450
415
451
@raises (400 , "Invalid request body or parameters" )
416
452
@raises (401 , "Read or write privileges required" )
417
- @raises (403 , "Read or write privileges required " )
453
+ @raises (403 , "Access forbidden: {reason} " )
418
454
@raises (
419
455
404 , "Specified database, document ID or revision doesn't exists ({endpoint})"
420
456
)
@@ -444,23 +480,23 @@ def endpoint(self) -> str:
444
480
return f"{ self ._document .endpoint } /{ _quote_id (self .id )} "
445
481
446
482
@raises (401 , "Read privilege required for document '{document_id}'" )
447
- @raises (403 , "Read privilege required for document '{document_id}' " )
483
+ @raises (403 , "Access forbidden: {reason} " )
448
484
async def _exists (self ) -> bool :
449
485
try :
450
486
response , _ = await self ._document ._database ._remote ._head (
451
487
self .endpoint , return_json = False
452
488
)
453
489
self .content_type = response .headers ["Content-Type" ]
454
490
return True
455
- except aiohttp . ClientResponseError as e :
491
+ except ClientResponseError as e :
456
492
if e .status == 404 :
457
493
return False
458
494
else :
459
495
raise e
460
496
461
497
@raises (400 , "Invalid request parameters" )
462
498
@raises (401 , "Read privilege required for document '{document_id}'" )
463
- @raises (403 , "Read privilege required for document '{document_id}' " )
499
+ @raises (403 , "Access forbidden: {reason} " )
464
500
@raises (404 , "Document '{document_id}' or attachment '{id}' doesn't exists" )
465
501
async def _get (self , ** params : Any ) -> bytes :
466
502
response , data = await self ._document ._database ._remote ._get_bytes (
@@ -472,7 +508,7 @@ async def _get(self, **params: Any) -> bytes:
472
508
473
509
@raises (400 , "Invalid request body or parameters" )
474
510
@raises (401 , "Write privilege required for document '{document_id}'" )
475
- @raises (403 , "Write privilege required for document '{document_id}' " )
511
+ @raises (403 , "Access forbidden: {reason} " )
476
512
@raises (404 , "Document '{document_id}' doesn't exists" )
477
513
@raises (
478
514
409 , "Specified revision {document_rev} is not the latest for target document"
@@ -490,7 +526,7 @@ async def _put(
490
526
491
527
@raises (400 , "Invalid request body or parameters" )
492
528
@raises (401 , "Write privilege required for document '{document_id}'" )
493
- @raises (403 , "Write privilege required for document '{document_id}' " )
529
+ @raises (403 , "Access forbidden: {reason} " )
494
530
@raises (404 , "Specified database or document ID doesn't exists ({endpoint})" )
495
531
@raises (
496
532
409 , "Specified revision {document_rev} is not the latest for target document"
@@ -519,7 +555,7 @@ def endpoint(self) -> str:
519
555
520
556
@raises (400 , "Invalid request" )
521
557
@raises (401 , "Read privileges required" )
522
- @raises (403 , "Read privileges required " )
558
+ @raises (403 , "Access forbidden: {reason} " )
523
559
@raises (404 , "Specified database, design document or view is missing" )
524
560
async def _get (self , ** params : Any ) -> JsonDict :
525
561
_ , json = await self ._database ._remote ._get (self .endpoint , params )
@@ -528,7 +564,7 @@ async def _get(self, **params: Any) -> JsonDict:
528
564
529
565
@raises (400 , "Invalid request" )
530
566
@raises (401 , "Write privileges required" )
531
- @raises (403 , "Write privileges required " )
567
+ @raises (403 , "Access forbidden: {reason} " )
532
568
@raises (404 , "Specified database, design document or view is missing" )
533
569
async def _post (self , keys : List [str ], ** params : Any ) -> JsonDict :
534
570
_ , json = await self ._database ._remote ._post (
0 commit comments