Refactoring and more pylint-related changes.
authorJoel Rosdahl <joel@rosdahl.net>
Sun, 25 Sep 2005 20:47:52 +0000 (20:47 +0000)
committerJoel Rosdahl <joel@rosdahl.net>
Sun, 25 Sep 2005 20:47:52 +0000 (20:47 +0000)
check.py
misc/pylintrc
src/packages/kofoto/__init__.py
src/packages/kofoto/cachedobject.py
src/packages/kofoto/clientenvironment.py
src/packages/kofoto/common.py
src/packages/kofoto/config.py
src/packages/kofoto/dag.py
src/packages/kofoto/generate.py
src/packages/kofoto/gkofoto/main.py

index 8350d03..38ff48d 100755 (executable)
--- a/check.py
+++ b/check.py
@@ -4,4 +4,18 @@ import sys
 from pylint import lint
 
 sys.path.insert(0, "src/packages")
-lint.Run(["--rcfile", "misc/pylintrc", "kofoto.commandline"])
+if len(sys.argv) > 1:
+    modules = sys.argv[1:]
+else:
+    modules = ["kofoto", "kofoto.commandline", "kofoto.output"]
+
+tests_to_disable = [
+    "C0101", # "Too short variable name."
+    "W0142", # "Used * or ** magic."
+    "W0704", # "Except doesn't do anything."
+]
+
+lint.Run(
+    ["--rcfile", "misc/pylintrc"] +
+    ["--disable-msg=" + x for x in tests_to_disable] +
+    modules)
index e29cacb..f8f69d1 100644 (file)
@@ -169,7 +169,7 @@ max-parents=7
 max-attributes=17
 
 # Minimum number of public methods for a class (see R0903).
-min-public-methods=2
+min-public-methods=1
 
 # Maximum number of public methods for a class (see R0904).
 max-public-methods=20
index 5394fcf..bac3d5e 100644 (file)
@@ -1,15 +1,41 @@
+"""Implementation of the CachedObject class."""
+
 __all__ = ["CachedObject"]
 
 class CachedObject:
+    """A class for keeping track of a cached object.
+
+    This class keeps knowledge about how to create an object. The
+    object is created only when needed (when the get method is
+    called). The same object is returned for successive calls to get,
+    unless the invalidate has been called.
+    """
+
     def __init__(self, constructor, args=()):
-        self.constructor = constructor
-        self.args = args
+        """Constructor.
+
+        Arguments:
+
+        constructor -- Function to call to create the object.
+        args        -- Arguments to pass to the constructor function.
+        """
+        self.__constructor = constructor
+        self.__args = args
+        self.__object = None
+        self.__created = False
 
     def get(self):
-        if not hasattr(self, "object"):
-            self.object = self.constructor(*self.args)
-        return self.object
+        """Create (if needed) and get the cached object."""
+
+        if not self.__created:
+            self.__object = self.__constructor(*self.__args)
+        return self.__object
 
     def invalidate(self):
-        if hasattr(self, "object"):
-            del self.object
+        """Invalidate the cached object.
+
+        The object will be recreated in the next call to get.
+        """
+
+        if self.__created:
+            self.__object = None
index 088c1b2..fa3553e 100644 (file)
@@ -12,7 +12,12 @@ __all__ = [
 import locale
 import os
 import sys
-from kofoto.config import *
+from kofoto.config import \
+    BadConfigurationValueError, \
+    Config, \
+    MissingConfigurationKeyError, \
+    MissingSectionHeaderError, \
+    createConfigTemplate
 from kofoto.shelf import Shelf, FailedWritingError
 from kofoto.imagecache import ImageCache
 from kofoto.version import version as kofotoVersion
@@ -21,33 +26,71 @@ from kofoto.version import version as kofotoVersion
 # Public classes.
 
 class ClientEnvironmentError(Exception):
+    """Base class for exceptions in the module."""
     pass
 
 class ConfigFileError(ClientEnvironmentError):
+    """Base class for configuration file exceptions in the module."""
     pass
 
 class MissingConfigFileError(ConfigFileError):
+    """Missing configuration file.
+
+    Exception parameter: configuration file location.
+    """
     pass
 
 class BadConfigFileError(ConfigFileError):
+    """Bad configuration file.
+
+    Exception parameter: configuration file location.
+    """
     pass
 
 class ShelfError(ClientEnvironmentError):
+    """Base class for shelf-related exceptions in the module."""
     pass
 
 class MissingShelfError(ShelfError):
+    """Missing shelf.
+
+    Exception parameter: shelf location.
+    """
     pass
 
 class BadShelfError(ShelfError):
+    """Bad shelf.
+
+    Exception parameter: shelf location.
+    """
     pass
 
 class ClientEnvironment(object):
+    """Environment useful for a Kofoto client.
+
+    A properly initialized ClientEnvironment instance has the
+    following available attributes:
+
+    codeset            -- The codeset to use for encoding to and decoding from
+                          the current locale.
+    config             -- A kofoto.config.Config instance.
+    configFileLocation -- Location of the configuration file.
+    shelf              -- A kofoto.shelf.Shelf instance.
+    shelfLocation      -- Location of the shelf.
+    imageCache         -- A kofoto.imagecache.ImageCache instance.
+    version            -- Kofoto version (a string).
+    """
+
     def __init__(self, localCodeset=None):
         """Initialize the client environment instance.
 
         If localCodeset is None, the preferred character set encoding
         is read from the environment and used as the local codeset.
-        Otherwise, localCodeset is used."""
+        Otherwise, localCodeset is used.
+
+        Note that the setup method must be called to further
+        initialize the instance.
+        """
 
         if localCodeset == None:
             locale.setlocale(locale.LC_ALL, "")
@@ -55,6 +98,13 @@ class ClientEnvironment(object):
         else:
             self.__codeset = localCodeset
 
+        # These are initiazlied in the setup method.
+        self.__config = None
+        self.__configFileLocation = None
+        self.__imageCache = None
+        self.__shelf = None
+        self.__shelfLocation = None
+
     def setup(self, configFileLocation=None, shelfLocation=None,
               createMissingConfigFile=True, createMissingShelf=True):
         """Set up the environment.
@@ -94,12 +144,12 @@ class ClientEnvironment(object):
                     ("Missing configuration file: \"%s\"\n" %
                          self.configFileLocation,
                      self.configFileLocation)
-        self.__config = Config(self.configFileLocation, self.codeset)
+        self.__config = Config(self.codeset)
 
         try:
-            self.config.read()
+            self.config.read(self.configFileLocation)
             self.config.verify()
-        except MissingSectionHeaderError, x:
+        except MissingSectionHeaderError:
             raise BadConfigFileError, \
                   ("Bad configuration (missing section headers).\n",
                    self.configFileLocation)
@@ -133,10 +183,12 @@ class ClientEnvironment(object):
                         ("Could not create metadata database \"%s\".\n" % (
                              self.shelfLocation),
                          self.shelfLocation)
-                self._writeInfo("Created metadata database \"%s\".\n" % self.shelfLocation)
+                self._writeInfo(
+                    "Created metadata database \"%s\".\n" % self.shelfLocation)
             else:
                 raise MissingShelfError, \
-                    ("Could not open metadata database \"%s\"" % self.shelfLocation,
+                    ("Could not open metadata database \"%s\"" % (
+                        self.shelfLocation),
                      self.shelfLocation)
 
         self.__imageCache = ImageCache(
@@ -146,37 +198,47 @@ class ClientEnvironment(object):
             self.config.getboolean("image cache", "use_orientation_attribute"))
 
     def getCodeset(self):
+        """Get codeset."""
         return self.__codeset
     codeset = property(getCodeset)
 
     def getConfig(self):
+        """Get the Config instance."""
         return self.__config
     config = property(getConfig)
 
     def getConfigFileLocation(self):
+        """Get the configuration file location."""
         return self.__configFileLocation
     configFileLocation = property(getConfigFileLocation)
 
     def getShelf(self):
+        """Get the Shelf instance."""
         return self.__shelf
     shelf = property(getShelf)
 
     def getShelfLocation(self):
+        """Get the shelf location."""
         return self.__shelfLocation
     shelfLocation = property(getShelfLocation)
 
     def getImageCache(self):
+        """Get the ImageCache instance."""
         return self.__imageCache
     imageCache = property(getImageCache)
 
     def getVersion(self):
+        """Get the Kofoto version (a string)."""
         return kofotoVersion
     version = property(getVersion)
 
     def unicodeToLocalizedString(self, unicodeString):
-        """If unicodeString is a Unicode string, convert it to a
+        """Convert a Unicode string to a localized string.
+
+        If unicodeString is a Unicode string, convert it to a
         localized string. Otherwise, unicodeString is returned without
-        any conversion."""
+        any conversion.
+        """
         if isinstance(unicodeString, unicode):
             return unicodeString.encode(self.codeset)
         else:
@@ -187,6 +249,11 @@ class ClientEnvironment(object):
         return localizedString.decode(self.codeset)
 
     def _writeInfo(self, infoString):
-        """Default implementation: Write to standard output."""
+        """Write an informational string to a suitable place.
+
+        This is the default implementation: write to standard output. 
+        Subclasses should override this method if they want to handle
+        the output themselves.
+        """
         sys.stdout.write(self.unicodeToLocalizedString(infoString))
         sys.stdout.flush()
index ad5361a..de34f8f 100644 (file)
@@ -25,6 +25,8 @@ class KofotoError(Exception):
 ### Functions.
 
 def calculateDownscaledDimensions(width, height, widthlimit, heightlimit):
+    """Scale down width and height to fit within given limits."""
+
     w = width
     h = height
     if w > widthlimit:
@@ -36,6 +38,8 @@ def calculateDownscaledDimensions(width, height, widthlimit, heightlimit):
     return w, h
 
 def symlinkOrCopyFile(source, destination):
+    """Create a symbolic link, or copy if support links are not supported."""
+
     try:
         os.unlink(destination)
     except OSError:
@@ -43,6 +47,8 @@ def symlinkOrCopyFile(source, destination):
     try:
         os.symlink(source, destination)
     except AttributeError:
+        # The platform doesn't support symlinks.
+
         import shutil
         if not os.path.dirname(source):
             # Handle the case of "ln -s foo dir/bar".
index ebafaa0..6663c7d 100644 (file)
@@ -47,14 +47,13 @@ class BadConfigurationValueError(KofotoError):
     pass
 
 class Config(ConfigParser):
-    def __init__(self, filename, encoding):
+    def __init__(self, encoding):
         ConfigParser.__init__(self)
-        self.filename = filename
         self.encoding = encoding
 
-    def read(self):
+    def read(self, filenames):
         try:
-            ConfigParser.read(self, self.filename)
+            ConfigParser.read(self, filenames)
         except ConfigParserMissingSectionHeaderError:
             raise MissingSectionHeaderError
 
@@ -79,11 +78,6 @@ class Config(ConfigParser):
             ret.append((x, y))
         return ret
 
-    def getImageSizeList(self):
-        """Returns image size limits as a sorted list of unique tuples
-        of width and height."""
-        return imgsizes
-
     def verify(self):
         def checkConfigurationItem(section, key, function):
             if not self.has_option(section, key):
index 794cb7d..d5fe9e3 100644 (file)
@@ -1,4 +1,4 @@
-"""A directed, acyclic graph."""
+"""Implementation of the DAG class."""
 
 __all__ = ["DAG", "LoopError"]
 
@@ -6,20 +6,26 @@ from sets import Set
 from kofoto.common import KofotoError
 
 class LoopError(KofotoError):
+    """A loop would have been created."""
     pass
 
 class DAG:
+    """A directed, acyclic graph."""
     def __init__(self, initlist=None):
+        """Constructor.
+
+        Arguments:
+
+        initlist -- A list of elements to add to the DAG. The element
+                    will be unconnected.
+        """
         self.roots = Set()
         self.elements = Set()
         self.parents = {}
         self.children = {}
         if initlist:
             for element in initlist:
-                self.roots.add(element)
-                self.elements.add(element)
-                self.parents[element] = Set()
-                self.children[element] = Set()
+                self.add(element)
 
     def __contains__(self, element):
         return element in self.elements
@@ -28,12 +34,14 @@ class DAG:
         return self.elements.__iter__()
 
     def add(self, element):
+        """Add an element to the DAG."""
         self.roots.add(element)
         self.elements.add(element)
         self.parents[element] = Set()
         self.children[element] = Set()
 
     def connect(self, parent, child):
+        """Add an element to another element."""
         if self.reachable(child, parent):
             raise LoopError, (parent, child)
         self.parents[child].add(parent)
@@ -41,15 +49,24 @@ class DAG:
         self.roots.discard(child)
 
     def connected(self, parent, child):
+        """Check whether an element is connected to another element."""
         return child in self.children[parent]
 
     def disconnect(self, parent, child):
+        """Disconnect a parent element from a child element.
+
+        If the elements are not connected, nothing will happen.
+        """
         self.parents[child].discard(parent)
         self.children[parent].discard(child)
         if len(self.children[parent]) == 0:
             self.roots.add(parent)
 
     def getAncestors(self, element):
+        """Get the ancestors of an element.
+
+        An iterable is returned.
+        """
         visited = Set()
         stack = [element]
         while stack:
@@ -61,9 +78,17 @@ class DAG:
             stack.extend(self.parents[el])
 
     def getChildren(self, element):
+        """Get the immediate children of an element.
+
+        An iterable is returned.
+        """
         return self.children[element]
 
     def getDescendants(self, element):
+        """Get the descendants of an element.
+
+        An iterable is returned.
+        """
         visited = Set()
         stack = [element]
         while stack:
@@ -75,15 +100,25 @@ class DAG:
             stack.extend(self.children[el])
 
     def getParents(self, element):
+        """Get the immediate parents of an element.
+
+        An iterable is returned.
+        """
         return self.parents[element]
 
     def getRoots(self):
+        """Get the roots of the DAG.
+
+        An iterable is returned.
+        """
         return self.roots
 
     def reachable(self, parent, child):
+        """Check whether an element is reachable from another element."""
         return child in self.getDescendants(parent)
 
     def remove(self, element):
+        """Remove an element from the DAG."""
         self.roots.discard(element)
         self.elements.remove(element)
         for parent in self.parents[element]:
index cd783f6..9dae774 100644 (file)
@@ -1,3 +1,5 @@
+"""Implementation of the Generator class."""
+
 __all__ = ["Generator", "OutputTypeError"]
 
 from kofoto.common import KofotoError
@@ -8,7 +10,17 @@ class OutputTypeError(KofotoError):
 
 
 class Generator:
+    """HTML output generator."""
+
     def __init__(self, outputtype, env):
+        """Constructor.
+
+        Arguments:
+
+        outputtype -- Output module name.
+        env        -- Client environment instance.
+        """
+
         self.env = env
         try:
             outputmodule = getattr(
@@ -21,5 +33,16 @@ class Generator:
 
 
     def generate(self, root, subalbums, dest, character_encoding):
+        """Generate HTML.
+
+        Arguments:
+
+        root      -- Album to consider the root album.
+        subalbums -- A list of subalbums to generate. If empty, all
+                     subalbums are generated.
+        dest      -- Directory in which the generated HTML files should be
+                     put.
+        character_encoding -- Codeset to use in HTML files.
+        """
         og = self.ogclass(self.env, character_encoding)
         og.generate(root, subalbums, dest)
index 1cd39f8..6cfeb52 100644 (file)
@@ -1,6 +1,6 @@
 import os
 import sys
-from kofoto.clientenvironment import DEFAULT_CONFIGFILE_LOCATION
+from kofoto.config import DEFAULT_CONFIGFILE_LOCATION
 from kofoto.gkofoto.environment import env
 from kofoto.gkofoto.controller import Controller
 from optparse import OptionParser