Create global instance of command-line options

This eases testing of option values inside the code.  This instance
is implemented as the read-only copy of the obtained 'options' object,
so callers won't be able to modify its contents.

Signed-off-by: Eygene Ryabinkin <rea@codelabs.ru>
This commit is contained in:
Eygene Ryabinkin 2013-02-05 07:49:56 +04:00
parent 5c842c01bd
commit f4140cbbed
7 changed files with 136 additions and 5 deletions

View File

@ -60,3 +60,30 @@ An :class:`accounts.Account` connects two email repositories that are to be sync
This execption inherits directly from :exc:`Exception` and is raised
on errors during the offlineimap execution. It has an attribute
`severity` that denotes the severity level of the error.
:mod:`offlineimap.globals` -- module with global variables
==========================================================
.. module:: offlineimap.globals
Module :mod:`offlineimap.globals` provides the read-only storage
for the global variables.
All exported module attributes can be set manually, but this practice
is highly discouraged and shouldn't be used.
However, attributes of all stored variables can only be read, write
access to them is denied.
Currently, we have only :attr:`options` attribute that holds
command-line options as returned by OptionParser.
The value of :attr:`options` must be set by :func:`set_options`
prior to its first use.
.. automodule:: offlineimap.globals
:members:
.. data:: options
You can access the values of stored options using the usual
syntax, offlineimap.globals.options.<option-name>

View File

@ -15,6 +15,7 @@
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
from offlineimap import mbnames, CustomConfig, OfflineImapError
from offlineimap import globals
from offlineimap.repository import Repository
from offlineimap.ui import getglobalui
from offlineimap.threadutil import InstanceLimitedThread
@ -321,7 +322,7 @@ class SyncableAccount(Account):
self.ui.debug('', "Not syncing filtered folder '%s'"
"[%s]" % (localfolder, localfolder.repository))
continue # Ignore filtered folder
if self.config.get('general', 'single-thread') == 'False':
if not globals.options.singlethreading:
thread = InstanceLimitedThread(\
instancename = 'FOLDER_' + self.remoterepos.getname(),
target = syncfolder,

View File

@ -16,6 +16,7 @@
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
from offlineimap import threadutil
from offlineimap import globals
from offlineimap.ui import getglobalui
from offlineimap.error import OfflineImapError
import offlineimap.accounts
@ -403,7 +404,7 @@ class BaseFolder(object):
break
self.ui.copyingmessage(uid, num+1, num_to_copy, self, dstfolder)
# exceptions are caught in copymessageto()
if self.suggeststhreads() and self.config.get('general', 'single-thread') == 'False':
if self.suggeststhreads() and not globals.options.singlethreading:
self.waitforthread()
thread = threadutil.InstanceLimitedThread(\
self.getcopyinstancelimit(),

12
offlineimap/globals.py Normal file
View File

@ -0,0 +1,12 @@
# Copyright 2013 Eygene A. Ryabinkin.
#
# Module that holds various global objects.
from offlineimap.utils import const
# Holds command-line options for OfflineIMAP.
options = const.ConstProxy()
def set_options (source):
""" Sets the source for options variable """
options.set_source (source)

View File

@ -25,6 +25,7 @@ import logging
from optparse import OptionParser
import offlineimap
from offlineimap import accounts, threadutil, syncmaster
from offlineimap import globals
from offlineimap.error import OfflineImapError
from offlineimap.ui import UI_LIST, setglobalui, getglobalui
from offlineimap.CustomConfig import CustomConfigParser
@ -161,6 +162,7 @@ class OfflineImap:
", ".join(UI_LIST.keys()))
(options, args) = parser.parse_args()
globals.set_options (options)
#read in configuration file
configfilename = os.path.expanduser(options.configfile)
@ -251,9 +253,6 @@ class OfflineImap:
if type.lower() == 'imap':
imaplib.Debug = 5
# XXX: can we avoid introducing fake configuration item?
config.set_if_not_exists('general', 'single-thread', 'True' if options.singlethreading else 'False')
if options.runonce:
# FIXME: maybe need a better
for section in accounts.getaccountlist(config):

View File

@ -0,0 +1,40 @@
# Copyright 2013 Eygene A. Ryabinkin.
#
# Collection of classes that implement const-like behaviour
# for various objects.
import copy
class ConstProxy (object):
"""
Implements read-only access to a given object
that can be attached to each instance only once.
"""
def __init__ (self):
self.__dict__['__source'] = None
def __getattr__ (self, name):
src = self.__dict__['__source']
if src == None:
raise ValueError ("using non-initialized ConstProxy() object")
return copy.deepcopy (getattr (src, name))
def __setattr__ (self, name, value):
raise AttributeError ("tried to set '%s' to '%s' for constant object" % \
(name, value))
def __delattr__ (self, name):
raise RuntimeError ("tried to delete field '%s' from constant object" % \
(name))
def set_source (self, source):
""" Sets source object for this instance. """
if (self.__dict__['__source'] != None):
raise ValueError ("source object is already set")
self.__dict__['__source'] = source

51
test/tests/test_00_globals.py Executable file
View File

@ -0,0 +1,51 @@
#!/usr/bin/env python
# Copyright 2013 Eygene A. Ryabinkin
from offlineimap import globals
import unittest
class Opt:
def __init__(self):
self.one = "baz"
self.two = 42
self.three = True
class TestOfflineimapGlobals(unittest.TestCase):
@classmethod
def setUpClass(klass):
klass.o = Opt()
globals.set_options (klass.o)
def test_initial_state(self):
for k in self.o.__dict__.keys():
self.assertTrue(getattr(self.o, k) ==
getattr(globals.options, k))
def test_object_changes(self):
self.o.one = "one"
self.o.two = 119
self.o.three = False
return self.test_initial_state()
def test_modification(self):
with self.assertRaises(AttributeError):
globals.options.two = True
def test_deletion(self):
with self.assertRaises(RuntimeError):
del globals.options.three
def test_nonexistent_key(self):
with self.assertRaises(AttributeError):
a = globals.options.nosuchoption
def test_double_init(self):
with self.assertRaises(ValueError):
globals.set_options (True)
if __name__ == "__main__":
suite = unittest.TestLoader().loadTestsFromTestCase(TestOfflineimapGlobals)
unittest.TextTestRunner(verbosity=2).run(suite)