Skip to content

Commit

Permalink
Create global instance of command-line options
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
konvpalto committed Feb 11, 2013
1 parent 5c842c0 commit f4140cb
Show file tree
Hide file tree
Showing 7 changed files with 136 additions and 5 deletions.
27 changes: 27 additions & 0 deletions docs/doc-src/API.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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>
3 changes: 2 additions & 1 deletion offlineimap/accounts.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -321,7 +322,7 @@ def sync(self):
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,
Expand Down
3 changes: 2 additions & 1 deletion offlineimap/folder/Base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -403,7 +404,7 @@ def syncmessagesto_copy(self, dstfolder, statusfolder):
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(),
Expand Down
12 changes: 12 additions & 0 deletions offlineimap/globals.py
Original file line number Diff line number Diff line change
@@ -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)
5 changes: 2 additions & 3 deletions offlineimap/init.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
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
Expand Down Expand Up @@ -161,6 +162,7 @@ def parse_cmd_options(self):
", ".join(UI_LIST.keys()))

(options, args) = parser.parse_args()
globals.set_options (options)

#read in configuration file
configfilename = os.path.expanduser(options.configfile)
Expand Down Expand Up @@ -251,9 +253,6 @@ def parse_cmd_options(self):
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):
Expand Down
40 changes: 40 additions & 0 deletions offlineimap/utils/const.py
Original file line number Diff line number Diff line change
@@ -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 changes: 51 additions & 0 deletions test/tests/test_00_globals.py
Original file line number Diff line number Diff line change
@@ -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)

0 comments on commit f4140cb

Please sign in to comment.