Skip to content
This repository was archived by the owner on Aug 30, 2024. It is now read-only.

Commit 233a1d3

Browse files
committed
Added exception classes for client, database, design document, document, feed, index, and replicator modules
- Added the appropriate status code to the exception with any additional values - New message module for CloudantException and ResultException messages
1 parent bdd3a69 commit 233a1d3

16 files changed

+548
-166
lines changed

src/cloudant/_messages.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
#!/usr/bin/env python
2+
# Copyright (c) 2016 IBM. All rights reserved.
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
"""
16+
Module that contains exception messages for the Cloudant Python client
17+
library.
18+
"""
19+
CLIENT = {
20+
100: 'A general Cloudant client exception was raised.',
21+
101: 'Value must be set to a Database object. Found type: {0}.',
22+
102: 'You must provide a url or an account.',
23+
404: 'Database {0} does not exist. Verify that the client is valid and try again.',
24+
409: 'Database {0} already exists.'
25+
}
26+
27+
DATABASE = {
28+
100: 'A general Cloudant database exception was raised.',
29+
101: 'Unexpected index type. Found: {0}.',
30+
400: 'Invalid database name during creation. Found: {0}',
31+
401: 'Unauthorized to create database {0}.',
32+
409: 'Document with id {0} already exists.'
33+
}
34+
35+
DESIGN_DOCUMENT = {
36+
100: 'A general Cloudant design document exception was raised.',
37+
101: 'Cannot add a MapReduce view to a design document for query indexes.',
38+
102: 'Cannot update a query index view using this method.',
39+
103: 'Cannot delete a query index view using this method.',
40+
104: 'View {0} must be of type View.',
41+
105: 'View {0} must be of type QueryIndexView.',
42+
106: 'Function for search index {0} must be of type string.',
43+
107: 'Definition for query text index {0} must be of type dict.'
44+
}
45+
46+
DOCUMENT = {
47+
100: 'A general Cloudant document exception was raised.',
48+
101: 'A document id is required to fetch document contents. '
49+
'Add an _id key and value to the document and re-try.',
50+
102: 'The field {0} is not a list.',
51+
103: 'Attempting to delete a doc with no _rev. Try running .fetch and re-try.'
52+
}
53+
54+
FEED = {
55+
100: 'A general Cloudant feed exception was raised.',
56+
101: 'Infinite _db_updates feed not supported for CouchDB.'
57+
}
58+
59+
INDEX = {
60+
100: 'A general Cloudant index exception was raised.',
61+
101: 'Creating the \"special\" index is not allowed.',
62+
102: 'Deleting the \"special\" index is not allowed.'
63+
}
64+
65+
REPLICATOR = {
66+
100: 'A general Cloudant replicator exception was raised.',
67+
101: 'You must specify either a source_db Database object or a manually composed'
68+
' \'source\' string/dict.',
69+
102: 'You must specify either a target_db Database object or a manually composed'
70+
' \'target\' string/dict.',
71+
404: 'Replication with id {0} not found.'
72+
}
73+
74+
RESULT = {
75+
100: 'A general result exception was raised.',
76+
101: 'Failed to interpret the argument {0} as a valid key value or as a valid slice.',
77+
102: 'Cannot use {0} when performing key access or key slicing. Found {1}.',
78+
103: 'Cannot use {0} for iteration. Found {1}.',
79+
104: 'Invalid page_size: {0}'
80+
}

src/cloudant/client.py

Lines changed: 17 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
from ._2to3 import bytes_, unicode_
2525
from .database import CloudantDatabase, CouchDatabase
2626
from .feed import Feed, InfiniteFeed
27-
from .error import CloudantException, CloudantArgumentError
27+
from .error import CloudantArgumentError, CloudantClientException
2828
from ._common_util import (
2929
USER_AGENT,
3030
append_response_error_content,
@@ -198,37 +198,34 @@ def create_database(self, dbname, **kwargs):
198198
Creates a new database on the remote server with the name provided
199199
and adds the new database object to the client's locally cached
200200
dictionary before returning it to the caller. The method will
201-
optionally throw a CloudantException if the database exists remotely.
201+
optionally throw a CloudantClientException if the database
202+
exists remotely.
202203
203204
:param str dbname: Name used to create the database.
204205
:param bool throw_on_exists: Boolean flag dictating whether or
205-
not to throw a CloudantException when attempting to create a
206-
database that already exists.
206+
not to throw a CloudantClientException when attempting to
207+
create a database that already exists.
207208
208209
:returns: The newly created database object
209210
"""
210211
new_db = self._DATABASE_CLASS(self, dbname)
211212
if new_db.exists():
212213
if kwargs.get('throw_on_exists', True):
213-
raise CloudantException(
214-
"Database {0} already exists".format(dbname)
215-
)
214+
raise CloudantClientException(409, dbname)
216215
new_db.create()
217216
super(CouchDB, self).__setitem__(dbname, new_db)
218217
return new_db
219218

220219
def delete_database(self, dbname):
221220
"""
222-
Removes the named database remotely and locally. The method will throw a
223-
CloudantException if the database does not exist.
221+
Removes the named database remotely and locally. The method will throw
222+
a CloudantClientException if the database does not exist.
224223
225224
:param str dbname: Name of the database to delete.
226225
"""
227226
db = self._DATABASE_CLASS(self, dbname)
228227
if not db.exists():
229-
raise CloudantException(
230-
"Database {0} does not exist".format(dbname)
231-
)
228+
raise CloudantClientException(404, dbname)
232229
db.delete()
233230
if dbname in list(self.keys()):
234231
super(CouchDB, self).__delitem__(dbname)
@@ -378,8 +375,7 @@ def __setitem__(self, key, value, remote=False):
378375
create the database remotely or not. Defaults to False.
379376
"""
380377
if not isinstance(value, self._DATABASE_CLASS):
381-
msg = "Value must be set to a Database object"
382-
raise CloudantException(msg)
378+
raise CloudantClientException(101, type(value).__name__)
383379
if remote and not value.exists():
384380
value.create()
385381
super(CouchDB, self).__setitem__(key, value)
@@ -426,7 +422,7 @@ def __init__(self, cloudant_user, auth_token, **kwargs):
426422
self._client_user_header['X-Cloudant-User'] = x_cloudant_user
427423

428424
if self.server_url is None:
429-
raise CloudantException('You must provide a url or an account.')
425+
raise CloudantClientException(102)
430426

431427
if kwargs.get('connect', False):
432428
self.connect()
@@ -537,7 +533,7 @@ def _usage_endpoint(self, endpoint, year=None, month=None):
537533
between 1 and 12. Optional parameter. Defaults to None.
538534
If used, it must be accompanied by ``year``.
539535
"""
540-
err = None
536+
err = False
541537
if year is None and month is None:
542538
resp = self.r_session.get(endpoint)
543539
else:
@@ -548,14 +544,14 @@ def _usage_endpoint(self, endpoint, year=None, month=None):
548544
endpoint, str(int(year)), str(int(month)))
549545
)
550546
else:
551-
err = ('Invalid year and/or month supplied. '
552-
'Found: year - {0}, month - {1}').format(year, month)
547+
err = True
553548
except (ValueError, TypeError):
554-
err = ('Invalid year and/or month supplied. '
555-
'Found: year - {0}, month - {1}').format(year, month)
549+
err = True
556550

557551
if err:
558-
raise CloudantArgumentError(err)
552+
msg = ('Invalid year and/or month supplied. '
553+
'Found: year - {0}, month - {1}').format(year, month)
554+
raise CloudantArgumentError(msg)
559555
else:
560556
resp.raise_for_status()
561557
return resp.json()

src/cloudant/database.py

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
from .view import View
3535
from .index import Index, TextIndex, SpecialIndex
3636
from .query import Query
37-
from .error import CloudantException, CloudantArgumentError
37+
from .error import CloudantArgumentError, CloudantDatabaseException
3838
from .result import Result, QueryResult
3939
from .feed import Feed, InfiniteFeed
4040

@@ -158,9 +158,7 @@ def create_document(self, data, throw_on_exists=False):
158158
else:
159159
doc = Document(self, docid)
160160
if throw_on_exists and doc.exists():
161-
raise CloudantException(
162-
'Error - Document with id {0} already exists.'.format(docid)
163-
)
161+
raise CloudantDatabaseException(409, docid)
164162
doc.update(data)
165163
doc.create()
166164
super(CouchDatabase, self).__setitem__(doc['_id'], doc)
@@ -346,11 +344,8 @@ def create(self):
346344
if resp.status_code == 201 or resp.status_code == 202:
347345
return self
348346

349-
raise CloudantException(
350-
"Unable to create database {0}: Reason: {1}".format(
351-
self.database_url, resp.text
352-
),
353-
code=resp.status_code
347+
raise CloudantDatabaseException(
348+
resp.status_code, self.database_url, resp.text
354349
)
355350

356351
def delete(self):
@@ -747,10 +742,7 @@ def get_revision_limit(self):
747742
try:
748743
ret = int(resp.text)
749744
except ValueError:
750-
resp.status_code = 400
751-
raise CloudantException(
752-
'Error - Invalid Response Value: {}'.format(resp.json())
753-
)
745+
raise CloudantDatabaseException(400, resp.json())
754746

755747
return ret
756748

@@ -1090,7 +1082,7 @@ def get_query_indexes(self, raw_result=False):
10901082
**data.get('def', {})
10911083
))
10921084
else:
1093-
raise CloudantException('Unexpected index content: {0} found.')
1085+
raise CloudantDatabaseException(101, data.get('type'))
10941086
return indexes
10951087

10961088
def create_query_index(

src/cloudant/design_document.py

Lines changed: 8 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
from ._common_util import QUERY_LANGUAGE, codify
2020
from .document import Document
2121
from .view import View, QueryIndexView
22-
from .error import CloudantArgumentError, CloudantException
22+
from .error import CloudantArgumentError, CloudantDesignDocumentException
2323

2424
class DesignDocument(Document):
2525
"""
@@ -285,9 +285,7 @@ def add_view(self, view_name, map_func, reduce_func=None, **kwargs):
285285
msg = "View {0} already exists in this design doc".format(view_name)
286286
raise CloudantArgumentError(msg)
287287
if self.get('language', None) == QUERY_LANGUAGE:
288-
msg = ('Cannot add a MapReduce view to a '
289-
'design document for query indexes.')
290-
raise CloudantException(msg)
288+
raise CloudantDesignDocumentException(101)
291289

292290
view = View(self, view_name, map_func, reduce_func, **kwargs)
293291
self.views.__setitem__(view_name, view)
@@ -361,8 +359,7 @@ def update_view(self, view_name, map_func, reduce_func=None, **kwargs):
361359
msg = "View {0} does not exist in this design doc".format(view_name)
362360
raise CloudantArgumentError(msg)
363361
if isinstance(view, QueryIndexView):
364-
msg = 'Cannot update a query index view using this method.'
365-
raise CloudantException(msg)
362+
raise CloudantDesignDocumentException(102)
366363

367364
view = View(self, view_name, map_func, reduce_func, **kwargs)
368365
self.views.__setitem__(view_name, view)
@@ -432,8 +429,7 @@ def delete_view(self, view_name):
432429
if view is None:
433430
return
434431
if isinstance(view, QueryIndexView):
435-
msg = 'Cannot delete a query index view using this method.'
436-
raise CloudantException(msg)
432+
raise CloudantDesignDocumentException(103)
437433

438434
self.views.__delitem__(view_name)
439435

@@ -517,34 +513,22 @@ def save(self):
517513
if self.get('language', None) != QUERY_LANGUAGE:
518514
for view_name, view in self.iterviews():
519515
if isinstance(view, QueryIndexView):
520-
msg = 'View {0} must be of type View.'.format(view_name)
521-
raise CloudantException(msg)
516+
raise CloudantDesignDocumentException(104, view_name)
522517
else:
523518
for view_name, view in self.iterviews():
524519
if not isinstance(view, QueryIndexView):
525-
msg = (
526-
'View {0} must be of type QueryIndexView.'
527-
).format(view_name)
528-
raise CloudantException(msg)
520+
raise CloudantDesignDocumentException(105, view_name)
529521

530522
if self.indexes:
531523
if self.get('language', None) != QUERY_LANGUAGE:
532524
for index_name, search in self.iterindexes():
533525
# Check the instance of the javascript search function
534526
if not isinstance(search['index'], STRTYPE):
535-
msg = (
536-
'Function for search index {0} must '
537-
'be of type string.'
538-
).format(index_name)
539-
raise CloudantException(msg)
527+
raise CloudantDesignDocumentException(106, index_name)
540528
else:
541529
for index_name, index in self.iterindexes():
542530
if not isinstance(index['index'], dict):
543-
msg = (
544-
'Definition for query text index {0} must '
545-
'be of type dict.'
546-
).format(index_name)
547-
raise CloudantException(msg)
531+
raise CloudantDesignDocumentException(107, index_name)
548532

549533
for prop in self._nested_object_names:
550534
if not getattr(self, prop):

src/cloudant/document.py

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@
2121
from requests.exceptions import HTTPError
2222

2323
from ._2to3 import url_quote, url_quote_plus
24-
from .error import CloudantException
24+
from .error import CloudantDocumentException
25+
2526

2627
class Document(dict):
2728
"""
@@ -161,10 +162,7 @@ def fetch(self):
161162
the locally cached Document object.
162163
"""
163164
if self.document_url is None:
164-
raise CloudantException(
165-
'A document id is required to fetch document contents. '
166-
'Add an _id key and value to the document and re-try.'
167-
)
165+
raise CloudantDocumentException(101)
168166
resp = self.r_session.get(self.document_url)
169167
resp.raise_for_status()
170168
self.clear()
@@ -210,9 +208,7 @@ def list_field_append(doc, field, value):
210208
if doc.get(field) is None:
211209
doc[field] = []
212210
if not isinstance(doc[field], list):
213-
raise CloudantException(
214-
'The field {0} is not a list.'.format(field)
215-
)
211+
raise CloudantDocumentException(102, field)
216212
if value is not None:
217213
doc[field].append(value)
218214

@@ -227,9 +223,7 @@ def list_field_remove(doc, field, value):
227223
:param value: Value to remove from the field list.
228224
"""
229225
if not isinstance(doc[field], list):
230-
raise CloudantException(
231-
'The field {0} is not a list.'.format(field)
232-
)
226+
raise CloudantDocumentException(102, field)
233227
doc[field].remove(value)
234228

235229
@staticmethod
@@ -314,10 +308,7 @@ def delete(self):
314308
object.
315309
"""
316310
if not self.get("_rev"):
317-
raise CloudantException(
318-
"Attempting to delete a doc with no _rev. Try running "
319-
".fetch first!"
320-
)
311+
raise CloudantDocumentException(103)
321312

322313
del_resp = self.r_session.delete(
323314
self.document_url,

0 commit comments

Comments
 (0)