    import os
    import sys

    try:
        from cPickle import load, dump
    except ImportError:
        from pickle import load, dump

    from MiscUtils.Configurable import Configurable
    from MiscUtils import NoDefault

    class ModelError(Exception):

        def __init__(self, error, line=None):
            self._line = line
            self._error = error
            if line is not None:
                args = (line, error)
            else:
                args = (error,)
            Exception.__init__(self, *args)

        def setLine(self, line):
            self._line = line

        def printError(self, filename):
            self.args = (filename,) + self.args
            if self._line:
                print '%s:%d: %s' % (filename, self._line, self._error)
            else:
                print '%s: %s' % (filename, self._error)

    class Model(Configurable):
        """A Model defines the classes, attributes and enumerations of an application.

        It also provides access to the Python classes that implement these structures
        for use by other MiddleKit entities including code generators and object stores.

        """

        pickleProtocol = -1 # highest protocol available

        def __init__(self,
                filename=None, classesFilename=None, configFilename=None,
                customCoreClasses={}, rootModel=None, havePythonClasses=True):
            Configurable.__init__(self)
            self._havePythonClasses = havePythonClasses
            self._filename = None
            self._configFilename = configFilename or 'Settings.config'
            self._coreClasses = customCoreClasses
            self._klasses = None
            self._name = None
            self._parents = [] # e.g., parent models
            self._pyClassForName = {}

            # _allModelsByFilename is used to avoid loading the same parent model twice
            if rootModel:
                self._allModelsByFilename = rootModel._allModelsByFilename
            else:
                self._allModelsByFilename = {}
            self._rootModel = rootModel

            if filename or classesFilename:
                self.read(filename or classesFilename, classesFilename is not None)

        def name(self):
            if self._name is None:
                if self._filename:
                    self._name = os.path.splitext(os.path.basename(self._filename))[0]
                else:
                    self._name = 'unnamed-mk-model'
            return self._name

        def setName(self, name):
            self._name = name

        def filename(self):
            return self._filename

        def read(self, filename, isClassesFile=False):
            assert self._filename is None, 'Cannot read twice.'
            # Assume the .mkmodel extension if none is given
            if os.path.splitext(filename)[1] == '':
                filename += '.mkmodel'
            self._filename = os.path.abspath(filename)
            self._name = None
            if isClassesFile:
                self.dontReadParents()
            else:
                self.readParents() # the norm
            try:
                if isClassesFile:
                    self.readKlassesDirectly(filename)
                else:
                    self.readKlassesInModelDir() # the norm
                self.awakeFromRead()
            except ModelError, e:
                print
                print 'Error while reading model:'
                e.printError(filename)
                raise

        def readKlassesInModelDir(self):
            """Read the Classes.csv or Classes.pickle.cache file as appropriate."""
            path = None
            csvPath = os.path.join(self._filename, 'Classes.csv')
            if os.path.exists(csvPath):
                path = csvPath
            xlPath = os.path.join(self._filename, 'Classes.xls')
            if os.path.exists(xlPath):
                path = xlPath
            if path is None:
                open(csvPath) # to get a properly constructed IOError

            self.readKlassesDirectly(path)

        def readKlassesDirectly(self, path):
            # read the pickled version of Classes if possible
            data = None
            shouldUseCache = self.setting('UsePickledClassesCache', False)
            if shouldUseCache:
                from MiscUtils.PickleCache import readPickleCache, writePickleCache
                data = readPickleCache(path,
                    pickleProtocol=self.pickleProtocol, source='MiddleKit')

            # read the regular file if necessary
            if data is None:
                self.klasses().read(path)
                if shouldUseCache:
                    writePickleCache(self._klasses, path,
                        pickleProtocol=self.pickleProtocol, source='MiddleKit')
            else:
                self._klasses = data
                self._klasses._model = self

        def __getstate__(self):
            raise Exception('Model instances were not designed to be pickled.')

        def awakeFromRead(self):
            # create containers for all klasses, uniqued by name
            byName = {}
            inOrder = []
            for model in reversed(self._searchOrder):
                for klass in model.klasses().klassesInOrder():
                    name = klass.name()
                    if name in byName:
                        for i in range(len(inOrder)):
                            if inOrder[i].name() == name:
                                inOrder[i] = klass
                    else:
                        inOrder.append(klass)
                    byName[name] = klass
            assert len(byName) == len(inOrder)
            for name, klass in byName.items():
                assert klass is self.klass(name)
            for klass in inOrder:
                assert klass is self.klass(klass.name())
            self._allKlassesByName = byName
            self._allKlassesInOrder = inOrder
            self._klasses.awakeFromRead(self)

        def readParents(self, parentFilenames=None):
            """Read parent models.

            Reads the parent models of the current model, as specified in the
            'Inherit' setting. The attributes _parents and _searchOrder are set.

            """
            if parentFilenames is None:
                parentFilenames = self.setting('Inherit', [])
            for filename in parentFilenames:
                filename = os.path.abspath(os.path.join(
                    os.path.dirname(self._filename), filename))
                if filename in self._allModelsByFilename:
                    model = self._allModelsByFilename[filename]
                    assert model != self._rootModel
                else:
                    model = self.__class__(filename,
                        customCoreClasses=self._coreClasses,
                        rootModel=self, havePythonClasses=self._havePythonClasses)
                    self._allModelsByFilename[filename] = model
                self._parents.append(model)

            # establish the search order
            # algorithm taken from http://www.python.org/2.2/descrintro.html#mro
            searchOrder = self.allModelsDepthFirstLeftRight()

            # remove duplicates:
            searchSet = set()
            for i in reversed(range(len(searchOrder))):
                model = searchOrder[i]
                if model in searchSet:
                    del searchOrder[i]
                else:
                    searchSet.add(model)

            self._searchOrder = searchOrder

        def dontReadParents(self):
            """Set attributes _parents and _searchOrder.

            Used internally for the rare case of reading class files directly
            (instead of from a model directory).

            """
            self._parents = []
            self._searchOrder = [self]

        def allModelsDepthFirstLeftRight(self, parents=None):
            """Return ordered list of models.

            Returns a list of all models, including self, parents and
            ancestors, in a depth-first, left-to-right order. Does not
            remove duplicates (found in inheritance diamonds).

            Mostly useful for readParents() to establish the lookup
            order regarding model inheritance.

            """
            if parents is None:
                parents = []
            parents.append(self)
            for parent in self._parents:
                parent.allModelsDepthFirstLeftRight(parents)
            return parents

        def coreClass(self, className):
            """Return code class.

            For the given name, returns a class from MiddleKit.Core
            or the custom set of classes that were passed in via initialization.

            """
            pyClass = self._coreClasses.get(className)
            if pyClass is None:
                results = {}
                exec 'import MiddleKit.Core.%s as module'% className in results
                pyClass = getattr(results['module'], className)
                assert isinstance(pyClass, type)
                self._coreClasses[className] = pyClass
            return pyClass

        def coreClassNames(self):
            """Return a list of model class names found in MiddleKit.Core."""
            # a little cheesy, but it does the job:
            import MiddleKit.Core as Core
            return Core.__all__

        def klasses(self):
            """Get klasses.

            Returns an instance that inherits from Klasses, using the base
            classes passed to __init__, if any.

            See also: klass(), allKlassesInOrder(), allKlassesByName()

            """
            if self._klasses is None:
                Klasses = self.coreClass('Klasses')
                self._klasses = Klasses(self)
            return self._klasses

        def klass(self, name, default=NoDefault):
            """Get klass.

            Returns the klass with the given name, searching the parent
            models if necessary.

            """
            for model in self._searchOrder:
                klass = model.klasses().get(name)
                if klass:
                    return klass
            if default is NoDefault:
                raise KeyError(name)
            else:
                return default

        def allKlassesInOrder(self):
            """Get klasses in order.

            Returns a sequence of all the klasses in this model, unique by
            name, including klasses inherited from parent models.

            The order is the order of declaration, top-down.

            """
            return self._allKlassesInOrder

        def allKlassesByName(self):
            """Get klasses by name.

            Returns a dictionary of all the klasses in this model, unique
            by name, including klasses inherited from parent models.

            """
            return self._allKlassesByName

        def allKlassesInDependencyOrder(self):
            """Get klasses in dependency order.

            Returns a sequence of all the klasses in this model, in an
            order such that klasses follow the klasses they refer to
            (via obj ref attributes).
            The typical use for such an order is to avoid SQL errors
            about foreign keys referring to tables that do not exist.

            A ModelError is raised if there is a dependency cycle
            since there can be no definitive order when a cycle exists.
            You can break cycles by setting Ref=False for some
            attribute in the cycle.

            """
            for klass in self._allKlassesInOrder:
                klass.willBuildDependencies()
            for klass in self._allKlassesInOrder:
                klass.buildDependencies()
            allKlasses = []
            visited = set()
            for klass in self._allKlassesInOrder:
                if not klass._dependents:
                    # print '>>', klass.name()
                    klass.recordDependencyOrder(allKlasses, visited)
            # The above loop fails to capture classes that are in cycles,
            # but in that case there really is no dependency order.
            if len(allKlasses) < len(self._allKlassesInOrder):
                raise ModelError("Cannot determine a dependency order"
                    " among the classes due to a cycle. Try setting Ref=0"
                    " for one of the attributes to break the cycle.")
            assert len(allKlasses) == len(self._allKlassesInOrder), \
                '%r, %r, %r' % (len(allKlasses), len(self._allKlassesInOrder), allKlasses)
            # print '>> allKlassesInDependencyOrder() =', ' '.join([k.name() for k in allKlasses])
            return allKlasses

        def pyClassForName(self, name):
            """Get Python class for name.

            Returns the Python class for the given name, which must be present
            in the object model. Accounts for self.setting('Package').

            If you already have a reference to the model klass, then you can
            just ask it for klass.pyClass().

            """
            pyClass = self._pyClassForName.get(name)
            if pyClass is None:
                results = {}
                pkg = self.setting('Package', '')
                if pkg:
                    pkg += '.'
                try:
                    exec 'import %s%s as module' % (pkg, name) in results
                except ImportError, exc:
                    raise ModelError("Could not import module for class '%s' due to %r."
                        " If you've added this class recently,"
                        " you need to re-generate your model." % (name, exc.args[0]))
                pyClass = getattr(results['module'], 'pyClass', None)
                if pyClass is None:
                    pyClass = getattr(results['module'], name)
                # Note: The 'pyClass' variable name that is first looked for is a hook for
                # those modules that have replaced the class variable by something else,
                # like a function. I did this in a project with a class called UniqueString()
                # in order to guarantee uniqueness per string.
                self._pyClassForName[name] = pyClass
            return pyClass

        ## Being configurable ##

        def configFilename(self):
            if self._filename is None:
                return None
            else:
                return os.path.join(self._filename, self._configFilename)

        def defaultConfig(self):
            return dict(
                Threaded = True,
                ObjRefSuffixes = ('ClassId', 'ObjId'),
                UseBigIntObjRefColumns = False,
                # SQLLog = {'File': 'stdout'},
                PreSQL = '',
                PostSQL = '',
                DropStatements = 'database', # database, tables
                SQLSerialColumnName = 'serialNum', # can use [cC]lassName, _ClassName
                AccessorStyle = 'methods', # can be 'methods' or 'properties'
                ExternalEnumsSQLNames = dict(
                    Enable = False,
                    TableName = '%(ClassName)s%(AttrName)sEnum',
                    ValueColName = 'value',
                    NameColName = 'name',
                ),
                # can use: [cC]lassName, _ClassName, [aA]ttrName, _AttrName.
                # "_" prefix means "as is", the others control the case of the first character.
            )

        def usesExternalSQLEnums(self):
            flag = getattr(self, '_usesExternalSQLEnums', None)
            if flag is None:
                flag = self.setting('ExternalEnumsSQLNames')['Enable']
                self._usesExternalSQLEnums = flag
            return flag

        ## Warnings ##

        def printWarnings(self, out=None):
            if out is None:
                out = sys.stdout
            if len(self._klasses.klassesInOrder()) < 1:
                out.write("warning: Model '%s' doesn't contain any class definitions.\n"
                    % self.name())
            for klass in self.klasses().klassesInOrder():
                klass.printWarnings(out)