From b9de2af55ab7fbccadf1fdb92d06858c9f29acfa Mon Sep 17 00:00:00 2001 From: Victor Shyba Date: Tue, 20 Oct 2015 16:50:17 -0300 Subject: [refactor] move couch errors and expose methods errors.py was holding a few specific CouchDB errors, now moved into couch.errors module. Also, some of CouchDatabase methods were declared as private, but external classes needs them. --- client/src/leap/soledad/client/sync.py | 2 +- common/src/leap/soledad/common/backend.py | 99 +++----------- common/src/leap/soledad/common/couch/__init__.py | 36 ++---- common/src/leap/soledad/common/couch/errors.py | 144 +++++++++++++++++++++ common/src/leap/soledad/common/errors.py | 124 ------------------ common/src/leap/soledad/common/tests/test_couch.py | 52 ++++---- 6 files changed, 206 insertions(+), 251 deletions(-) create mode 100644 common/src/leap/soledad/common/couch/errors.py diff --git a/client/src/leap/soledad/client/sync.py b/client/src/leap/soledad/client/sync.py index 2276db2a..626ad2e5 100644 --- a/client/src/leap/soledad/client/sync.py +++ b/client/src/leap/soledad/client/sync.py @@ -22,7 +22,7 @@ import logging from twisted.internet import defer from u1db import errors -from leap.soledad.common.errors import MissingDesignDocError +from leap.soledad.common.couch.errors import MissingDesignDocError from u1db.sync import Synchronizer diff --git a/common/src/leap/soledad/common/backend.py b/common/src/leap/soledad/common/backend.py index 1cbf3d57..2f0ef4f7 100644 --- a/common/src/leap/soledad/common/backend.py +++ b/common/src/leap/soledad/common/backend.py @@ -141,19 +141,7 @@ class SoledadBackend(CommonBackend): :return: The current generation. :rtype: int - :raise MissingDesignDocError: Raised when tried to access a missing - design document. - :raise MissingDesignDocListFunctionError: Raised when trying to access - a missing list function on a - design document. - :raise MissingDesignDocNamedViewError: Raised when trying to access a - missing named view on a design - document. - :raise MissingDesignDocDeletedError: Raised when trying to access a - deleted design document. - :raise MissingDesignDocUnknownError: Raised when failed to access a - design document for an yet - unknown reason. + :raise SoledadError: Raised by database on operation failure """ return self._get_generation_info()[0] @@ -164,24 +152,12 @@ class SoledadBackend(CommonBackend): :return: A tuple containing the current generation and transaction id. :rtype: (int, str) - :raise MissingDesignDocError: Raised when tried to access a missing - design document. - :raise MissingDesignDocListFunctionError: Raised when trying to access - a missing list function on a - design document. - :raise MissingDesignDocNamedViewError: Raised when trying to access a - missing named view on a design - document. - :raise MissingDesignDocDeletedError: Raised when trying to access a - deleted design document. - :raise MissingDesignDocUnknownError: Raised when failed to access a - design document for an yet - unknown reason. + :raise SoledadError: Raised by database on operation failure """ if self.replica_uid + '_gen' in self.cache: response = self.cache[self.replica_uid + '_gen'] return response - cur_gen, newest_trans_id = self._database._get_generation_info() + cur_gen, newest_trans_id = self._database.get_generation_info() self.cache[self.replica_uid + '_gen'] = (cur_gen, newest_trans_id) return (cur_gen, newest_trans_id) @@ -195,8 +171,10 @@ class SoledadBackend(CommonBackend): :return: The transaction id for C{generation}. :rtype: str + :raise InvalidGeneration: Raised when the generation does not exist. + """ - return self._database._get_trans_id_for_gen(generation) + return self._database.get_trans_id_for_gen(generation) def _get_transaction_log(self): """ @@ -206,7 +184,7 @@ class SoledadBackend(CommonBackend): :rtype: [(str, str)] """ - return self._database._get_transaction_log() + return self._database.get_transaction_log() def _get_doc(self, doc_id, check_for_conflicts=False): """ @@ -223,7 +201,7 @@ class SoledadBackend(CommonBackend): :return: The document. :rtype: ServerDocument """ - return self._database._get_doc(doc_id, check_for_conflicts) + return self._database.get_doc(doc_id, check_for_conflicts) def get_doc(self, doc_id, include_deleted=False): """ @@ -403,12 +381,10 @@ class SoledadBackend(CommonBackend): synchronized with the replica, this is (0, ''). :rtype: (int, str) """ - return self._database._get_replica_gen_and_trans_id(other_replica_uid) + return self._database.get_replica_gen_and_trans_id(other_replica_uid) def _set_replica_gen_and_trans_id(self, other_replica_uid, - other_generation, other_transaction_id, - number_of_docs=None, doc_idx=None, - sync_id=None): + other_generation, other_transaction_id): """ Set the last-known generation and transaction id for the other database replica. @@ -424,30 +400,18 @@ class SoledadBackend(CommonBackend): :param other_transaction_id: The transaction id associated with the generation. :type other_transaction_id: str - :param number_of_docs: The total amount of documents sent on this sync - session. - :type number_of_docs: int - :param doc_idx: The index of the current document being sent. - :type doc_idx: int - :param sync_id: The id of the current sync session. - :type sync_id: str """ if other_replica_uid is not None and other_generation is not None: - self._do_set_replica_gen_and_trans_id( - other_replica_uid, other_generation, other_transaction_id, - number_of_docs=number_of_docs, doc_idx=doc_idx, - sync_id=sync_id) + self.cache[other_replica_uid] = (other_generation, + other_transaction_id) + self._database.set_replica_gen_and_trans_id(other_replica_uid, + other_generation, + other_transaction_id) def _do_set_replica_gen_and_trans_id( - self, other_replica_uid, other_generation, other_transaction_id, - number_of_docs=None, doc_idx=None, sync_id=None): + self, other_replica_uid, other_generation, other_transaction_id): """ - Set the last-known generation and transaction id for the other - database replica. - - We have just performed some synchronization, and we want to track what - generation the other replica was at. See also - _get_replica_gen_and_trans_id. + _put_doc_if_newer from super class is calling it. So we declare this. :param other_replica_uid: The U1DB identifier for the other replica. :type other_replica_uid: str @@ -456,19 +420,10 @@ class SoledadBackend(CommonBackend): :param other_transaction_id: The transaction id associated with the generation. :type other_transaction_id: str - :param number_of_docs: The total amount of documents sent on this sync - session. - :type number_of_docs: int - :param doc_idx: The index of the current document being sent. - :type doc_idx: int - :param sync_id: The id of the current sync session. - :type sync_id: str """ - self.cache[other_replica_uid] = (other_generation, - other_transaction_id) - self._database._do_set_replica_gen_and_trans_id(other_replica_uid, - other_generation, - other_transaction_id) + self._set_replica_gen_and_trans_id(other_replica_uid, + other_generation, + other_transaction_id) def _force_doc_sync_conflict(self, doc): """ @@ -501,19 +456,7 @@ class SoledadBackend(CommonBackend): supersedes. :type conflicted_doc_revs: [str] - :raise MissingDesignDocError: Raised when tried to access a missing - design document. - :raise MissingDesignDocListFunctionError: Raised when trying to access - a missing list function on a - design document. - :raise MissingDesignDocNamedViewError: Raised when trying to access a - missing named view on a design - document. - :raise MissingDesignDocDeletedError: Raised when trying to access a - deleted design document. - :raise MissingDesignDocUnknownError: Raised when failed to access a - design document for an yet - unknown reason. + :raise SoledadError: Raised by database on operation failure """ cur_doc = self._get_doc(doc.doc_id, check_for_conflicts=True) new_rev = self._ensure_maximal_rev(cur_doc.rev, diff --git a/common/src/leap/soledad/common/couch/__init__.py b/common/src/leap/soledad/common/couch/__init__.py index 1780cecd..6fd4d0ce 100644 --- a/common/src/leap/soledad/common/couch/__init__.py +++ b/common/src/leap/soledad/common/couch/__init__.py @@ -51,8 +51,8 @@ from u1db.remote import http_app from leap.soledad.common import ddocs -from leap.soledad.common.errors import raise_server_error -from leap.soledad.common.errors import raise_missing_design_doc_error +from .errors import raise_server_error +from .errors import raise_missing_design_doc_error from leap.soledad.common.errors import InvalidURLError from leap.soledad.common.document import ServerDocument from leap.soledad.common.backend import SoledadBackend @@ -260,7 +260,7 @@ class CouchDatabase(object): :rtype: (int, [ServerDocument]) """ - generation, _ = self._get_generation_info() + generation, _ = self.get_generation_info() results = list(self.get_docs(self._database, include_deleted=include_deleted)) return (generation, results) @@ -294,7 +294,7 @@ class CouchDatabase(object): # This will not be needed when/if we switch from python-couchdb to # paisley. time.strptime('Mar 8 1917', '%b %d %Y') - get_one = lambda doc_id: self._get_doc(doc_id, check_for_conflicts) + get_one = lambda doc_id: self.get_doc(doc_id, check_for_conflicts) docs = [THREAD_POOL.apply_async(get_one, [doc_id]) for doc_id in doc_ids] for doc in docs: @@ -303,7 +303,7 @@ class CouchDatabase(object): continue yield doc - def _get_doc(self, doc_id, check_for_conflicts=False): + def get_doc(self, doc_id, check_for_conflicts=False): """ Extract the document from storage. @@ -376,7 +376,7 @@ class CouchDatabase(object): conflicts.append(doc) return conflicts - def _get_trans_id_for_gen(self, generation): + def get_trans_id_for_gen(self, generation): """ Get the transaction id corresponding to a particular generation. @@ -418,7 +418,7 @@ class CouchDatabase(object): except ServerError as e: raise_server_error(e, ddoc_path) - def _get_replica_gen_and_trans_id(self, other_replica_uid): + def get_replica_gen_and_trans_id(self, other_replica_uid): """ Return the last known generation and transaction id for the other db replica. @@ -470,7 +470,7 @@ class CouchDatabase(object): params['rev'] = couch_rev # restric document's couch revision else: # TODO: move into resource logic! - first_entry = self._get_doc(doc_id, check_for_conflicts=True) + first_entry = self.get_doc(doc_id, check_for_conflicts=True) conflicts.append(first_entry) resource = self._database.resource(doc_id, 'u1db_conflicts') try: @@ -480,16 +480,15 @@ class CouchDatabase(object): except ResourceNotFound: return [] - def _do_set_replica_gen_and_trans_id( - self, other_replica_uid, other_generation, other_transaction_id, - number_of_docs=None, doc_idx=None, sync_id=None): + def set_replica_gen_and_trans_id( + self, other_replica_uid, other_generation, other_transaction_id): """ Set the last-known generation and transaction id for the other database replica. We have just performed some synchronization, and we want to track what generation the other replica was at. See also - _get_replica_gen_and_trans_id. + get_replica_gen_and_trans_id. :param other_replica_uid: The U1DB identifier for the other replica. :type other_replica_uid: str @@ -498,13 +497,6 @@ class CouchDatabase(object): :param other_transaction_id: The transaction id associated with the generation. :type other_transaction_id: str - :param number_of_docs: The total amount of documents sent on this sync - session. - :type number_of_docs: int - :param doc_idx: The index of the current document being sent. - :type doc_idx: int - :param sync_id: The id of the current sync session. - :type sync_id: str """ doc_id = 'u1db_sync_%s' % other_replica_uid try: @@ -515,7 +507,7 @@ class CouchDatabase(object): doc['transaction_id'] = other_transaction_id self._database.save(doc) - def _get_transaction_log(self): + def get_transaction_log(self): """ This is only for the test suite, it is not part of the api. @@ -603,7 +595,7 @@ class CouchDatabase(object): newest_trans_id = changes[0][2] changes.reverse() else: - cur_gen, newest_trans_id = self._get_generation_info() + cur_gen, newest_trans_id = self.get_generation_info() return cur_gen, newest_trans_id, changes except ResourceNotFound as e: @@ -611,7 +603,7 @@ class CouchDatabase(object): except ServerError as e: raise_server_error(e, ddoc_path) - def _get_generation_info(self): + def get_generation_info(self): """ Return the current generation. diff --git a/common/src/leap/soledad/common/couch/errors.py b/common/src/leap/soledad/common/couch/errors.py new file mode 100644 index 00000000..e894d58f --- /dev/null +++ b/common/src/leap/soledad/common/couch/errors.py @@ -0,0 +1,144 @@ +# -*- coding: utf-8 -*- +# errors.py +# Copyright (C) 2015 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +from leap.soledad.common.errors import SoledadError +from leap.soledad.common.errors import register_exception + +""" +Specific errors that can be raised by CouchDatabase. +""" + + +@register_exception +class MissingDesignDocError(SoledadError): + + """ + Raised when trying to access a missing couch design document. + """ + + wire_description = "missing design document" + status = 500 + + +@register_exception +class MissingDesignDocNamedViewError(SoledadError): + + """ + Raised when trying to access a missing named view on a couch design + document. + """ + + wire_description = "missing design document named function" + status = 500 + + +@register_exception +class MissingDesignDocListFunctionError(SoledadError): + + """ + Raised when trying to access a missing list function on a couch design + document. + """ + + wire_description = "missing design document list function" + status = 500 + + +@register_exception +class MissingDesignDocDeletedError(SoledadError): + + """ + Raised when trying to access a deleted couch design document. + """ + + wire_description = "design document was deleted" + status = 500 + + +@register_exception +class DesignDocUnknownError(SoledadError): + + """ + Raised when trying to access a couch design document and getting an + unknown error. + """ + + wire_description = "missing design document unknown error" + status = 500 + + +def raise_missing_design_doc_error(exc, ddoc_path): + """ + Raise an appropriate exception when catching a ResourceNotFound when + accessing a design document. + + :param exc: The exception cought. + :type exc: ResourceNotFound + :param ddoc_path: A list representing the requested path. + :type ddoc_path: list + + :raise MissingDesignDocError: Raised when tried to access a missing design + document. + :raise MissingDesignDocListFunctionError: Raised when trying to access a + missing list function on a + design document. + :raise MissingDesignDocNamedViewError: Raised when trying to access a + missing named view on a design + document. + :raise MissingDesignDocDeletedError: Raised when trying to access a + deleted design document. + :raise MissingDesignDocUnknownError: Raised when failed to access a design + document for an yet unknown reason. + """ + path = "".join(ddoc_path) + if exc.message[1] == 'missing': + raise MissingDesignDocError(path) + elif exc.message[1] == 'missing function' or \ + exc.message[1].startswith('missing lists function'): + raise MissingDesignDocListFunctionError(path) + elif exc.message[1] == 'missing_named_view': + raise MissingDesignDocNamedViewError(path) + elif exc.message[1] == 'deleted': + raise MissingDesignDocDeletedError(path) + # other errors are unknown for now + raise DesignDocUnknownError("%s: %s" % (path, str(exc.message))) + + +def raise_server_error(exc, ddoc_path): + """ + Raise an appropriate exception when catching a ServerError when + accessing a design document. + + :param exc: The exception cought. + :type exc: ResourceNotFound + :param ddoc_path: A list representing the requested path. + :type ddoc_path: list + + :raise MissingDesignDocListFunctionError: Raised when trying to access a + missing list function on a + design document. + :raise MissingDesignDocUnknownError: Raised when failed to access a design + document for an yet unknown reason. + """ + path = "".join(ddoc_path) + msg = exc.message[1][0] + if msg == 'unnamed_error': + raise MissingDesignDocListFunctionError(path) + elif msg == 'TypeError': + if 'point is undefined' in exc.message[1][1]: + raise MissingDesignDocListFunctionError + # other errors are unknown for now + raise DesignDocUnknownError("%s: %s" % (path, str(exc.message))) diff --git a/common/src/leap/soledad/common/errors.py b/common/src/leap/soledad/common/errors.py index 84d8d813..2f6fd1d8 100644 --- a/common/src/leap/soledad/common/errors.py +++ b/common/src/leap/soledad/common/errors.py @@ -133,66 +133,6 @@ class CouldNotObtainLockError(SoledadError): # # SoledadBackend errors -# - -@register_exception -class MissingDesignDocError(SoledadError): - - """ - Raised when trying to access a missing couch design document. - """ - - wire_description = "missing design document" - status = 500 - - -@register_exception -class MissingDesignDocNamedViewError(SoledadError): - - """ - Raised when trying to access a missing named view on a couch design - document. - """ - - wire_description = "missing design document named function" - status = 500 - - -@register_exception -class MissingDesignDocListFunctionError(SoledadError): - - """ - Raised when trying to access a missing list function on a couch design - document. - """ - - wire_description = "missing design document list function" - status = 500 - - -@register_exception -class MissingDesignDocDeletedError(SoledadError): - - """ - Raised when trying to access a deleted couch design document. - """ - - wire_description = "design document was deleted" - status = 500 - - -@register_exception -class DesignDocUnknownError(SoledadError): - - """ - Raised when trying to access a couch design document and getting an - unknown error. - """ - - wire_description = "missing design document unknown error" - status = 500 - - # u1db error statuses also have to be updated http_errors.ERROR_STATUSES = set( http_errors.wire_description_to_status.values()) @@ -203,67 +143,3 @@ class InvalidURLError(Exception): """ Exception raised when Soledad encounters a malformed URL. """ - - -def raise_missing_design_doc_error(exc, ddoc_path): - """ - Raise an appropriate exception when catching a ResourceNotFound when - accessing a design document. - - :param exc: The exception cought. - :type exc: ResourceNotFound - :param ddoc_path: A list representing the requested path. - :type ddoc_path: list - - :raise MissingDesignDocError: Raised when tried to access a missing design - document. - :raise MissingDesignDocListFunctionError: Raised when trying to access a - missing list function on a - design document. - :raise MissingDesignDocNamedViewError: Raised when trying to access a - missing named view on a design - document. - :raise MissingDesignDocDeletedError: Raised when trying to access a - deleted design document. - :raise MissingDesignDocUnknownError: Raised when failed to access a design - document for an yet unknown reason. - """ - path = "".join(ddoc_path) - if exc.message[1] == 'missing': - raise MissingDesignDocError(path) - elif exc.message[1] == 'missing function' or \ - exc.message[1].startswith('missing lists function'): - raise MissingDesignDocListFunctionError(path) - elif exc.message[1] == 'missing_named_view': - raise MissingDesignDocNamedViewError(path) - elif exc.message[1] == 'deleted': - raise MissingDesignDocDeletedError(path) - # other errors are unknown for now - raise DesignDocUnknownError("%s: %s" % (path, str(exc.message))) - - -def raise_server_error(exc, ddoc_path): - """ - Raise an appropriate exception when catching a ServerError when - accessing a design document. - - :param exc: The exception cought. - :type exc: ResourceNotFound - :param ddoc_path: A list representing the requested path. - :type ddoc_path: list - - :raise MissingDesignDocListFunctionError: Raised when trying to access a - missing list function on a - design document. - :raise MissingDesignDocUnknownError: Raised when failed to access a design - document for an yet unknown reason. - """ - path = "".join(ddoc_path) - msg = exc.message[1][0] - if msg == 'unnamed_error': - raise MissingDesignDocListFunctionError(path) - elif msg == 'TypeError': - if 'point is undefined' in exc.message[1][1]: - raise MissingDesignDocListFunctionError - # other errors are unknown for now - raise DesignDocUnknownError("%s: %s" % (path, str(exc.message))) diff --git a/common/src/leap/soledad/common/tests/test_couch.py b/common/src/leap/soledad/common/tests/test_couch.py index 01f0587b..a311e0ef 100644 --- a/common/src/leap/soledad/common/tests/test_couch.py +++ b/common/src/leap/soledad/common/tests/test_couch.py @@ -37,7 +37,7 @@ from u1db import vectorclock from leap.soledad.common import couch from leap.soledad.common.document import ServerDocument -from leap.soledad.common import errors +from leap.soledad.common.couch import errors from leap.soledad.common.tests import u1db_tests as tests from leap.soledad.common.tests.util import CouchDBTestCase @@ -1257,18 +1257,18 @@ class SoledadBackendExceptionsTests(CouchDBTestCase): design docs are not present. """ self.create_db(ensure=False) - # _get_generation_info() + # get_generation_info() self.assertRaises( errors.MissingDesignDocError, - self.db._get_generation_info) - # _get_trans_id_for_gen() + self.db.get_generation_info) + # get_trans_id_for_gen() self.assertRaises( errors.MissingDesignDocError, - self.db._get_trans_id_for_gen, 1) - # _get_transaction_log() + self.db.get_trans_id_for_gen, 1) + # get_transaction_log() self.assertRaises( errors.MissingDesignDocError, - self.db._get_transaction_log) + self.db.get_transaction_log) # whats_changed() self.assertRaises( errors.MissingDesignDocError, @@ -1284,14 +1284,14 @@ class SoledadBackendExceptionsTests(CouchDBTestCase): transactions = self.db._database['_design/transactions'] transactions['lists'] = {} self.db._database.save(transactions) - # _get_generation_info() + # get_generation_info() self.assertRaises( errors.MissingDesignDocListFunctionError, - self.db._get_generation_info) - # _get_trans_id_for_gen() + self.db.get_generation_info) + # get_trans_id_for_gen() self.assertRaises( errors.MissingDesignDocListFunctionError, - self.db._get_trans_id_for_gen, 1) + self.db.get_trans_id_for_gen, 1) # whats_changed() self.assertRaises( errors.MissingDesignDocListFunctionError, @@ -1307,14 +1307,14 @@ class SoledadBackendExceptionsTests(CouchDBTestCase): transactions = self.db._database['_design/transactions'] del transactions['lists'] self.db._database.save(transactions) - # _get_generation_info() + # get_generation_info() self.assertRaises( errors.MissingDesignDocListFunctionError, - self.db._get_generation_info) + self.db.get_generation_info) # _get_trans_id_for_gen() self.assertRaises( errors.MissingDesignDocListFunctionError, - self.db._get_trans_id_for_gen, 1) + self.db.get_trans_id_for_gen, 1) # whats_changed() self.assertRaises( errors.MissingDesignDocListFunctionError, @@ -1338,18 +1338,18 @@ class SoledadBackendExceptionsTests(CouchDBTestCase): transactions = self.db._database['_design/transactions'] del transactions['views'] self.db._database.save(transactions) - # _get_generation_info() + # get_generation_info() self.assertRaises( errors.MissingDesignDocNamedViewError, - self.db._get_generation_info) + self.db.get_generation_info) # _get_trans_id_for_gen() self.assertRaises( errors.MissingDesignDocNamedViewError, - self.db._get_trans_id_for_gen, 1) + self.db.get_trans_id_for_gen, 1) # _get_transaction_log() self.assertRaises( errors.MissingDesignDocNamedViewError, - self.db._get_transaction_log) + self.db.get_transaction_log) # whats_changed() self.assertRaises( errors.MissingDesignDocNamedViewError, @@ -1367,18 +1367,18 @@ class SoledadBackendExceptionsTests(CouchDBTestCase): del self.db._database['_design/syncs'] # delete _design/transactions del self.db._database['_design/transactions'] - # _get_generation_info() + # get_generation_info() self.assertRaises( errors.MissingDesignDocDeletedError, - self.db._get_generation_info) - # _get_trans_id_for_gen() + self.db.get_generation_info) + # get_trans_id_for_gen() self.assertRaises( errors.MissingDesignDocDeletedError, - self.db._get_trans_id_for_gen, 1) - # _get_transaction_log() + self.db.get_trans_id_for_gen, 1) + # get_transaction_log() self.assertRaises( errors.MissingDesignDocDeletedError, - self.db._get_transaction_log) + self.db.get_transaction_log) # whats_changed() self.assertRaises( errors.MissingDesignDocDeletedError, @@ -1393,9 +1393,9 @@ class SoledadBackendExceptionsTests(CouchDBTestCase): del self.db._database['_design/transactions'] self.assertRaises( errors.MissingDesignDocDeletedError, - self.db._get_transaction_log) + self.db.get_transaction_log) self.create_db(ensure=True, dbname=self.db._dbname) - self.db._get_transaction_log() + self.db.get_transaction_log() def test_ensure_security_doc(self): """ -- cgit v1.2.3