Revision: 8115
          http://svn.sourceforge.net/mailman/?rev=8115&view=rev
Author:   bwarsaw
Date:     2006-12-07 06:13:56 -0800 (Thu, 07 Dec 2006)

Log Message:
-----------
Start to flesh out more of the SQLAlchemy mechanisms.

Added a MailList.__new__() which hooks instantiation to use a query on
dbcontext to get an existing mailing list.  A 'no-args' call means we're doing
a Create(), though eventually that will change too.

For now, disable the CheckVersion() call.  Eventually this will be folded into
schema migration.

list_exists(): Rewrite to use the dbcontext query to determine if the named
mailing list exists or not.  Requires the fqdn_listname.

Eradicate two failed member adaptors: BDBMemberAdaptor and SAMemberships.

Change the way the DBContext holds onto tables.  It now keeps a dictionary
mapping the table's name to the SA Table instance.  This makes it easier to
look up and use the individual tables.

Add 'web_page_url' as an attribute managed by SA, and remove a debugging
print.

Modified Paths:
--------------
    branches/tmp-sqlalchemy-branch/Mailman/MailList.py
    branches/tmp-sqlalchemy-branch/Mailman/Utils.py
    branches/tmp-sqlalchemy-branch/Mailman/database/__init__.py
    branches/tmp-sqlalchemy-branch/Mailman/database/listdata.py

Removed Paths:
-------------
    branches/tmp-sqlalchemy-branch/Mailman/BDBMemberAdaptor.py
    branches/tmp-sqlalchemy-branch/Mailman/SAMemberships.py

Deleted: branches/tmp-sqlalchemy-branch/Mailman/BDBMemberAdaptor.py
===================================================================
--- branches/tmp-sqlalchemy-branch/Mailman/BDBMemberAdaptor.py  2006-12-06 
05:16:54 UTC (rev 8114)
+++ branches/tmp-sqlalchemy-branch/Mailman/BDBMemberAdaptor.py  2006-12-07 
14:13:56 UTC (rev 8115)
@@ -1,636 +0,0 @@
-# Copyright (C) 2003-2006 by the Free Software Foundation, Inc.
-#
-# 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 2
-# 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, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 
USA.
-
-"""A MemberAdaptor based on the Berkeley database wrapper for Python.
-
-Requires Python 2.2.2 or newer, and PyBSDDB3 4.1.3 or newer.
-"""
-
-# To use, put the following in a file called extend.py in the mailing list's
-# directory:
-#
-# from Mailman.BDBMemberAdaptor import extend
-#
-# that's it!
-
-import os
-import new
-import time
-import errno
-import struct
-import cPickle as pickle
-
-try:
-    # Python 2.3
-    from bsddb import db
-except ImportError:
-    # earlier Pythons
-    from bsddb3 import db
-
-from Mailman import mm_cfg
-from Mailman import Utils
-from Mailman import Errors
-from Mailman import MemberAdaptor
-from Mailman.MailList import MailList
-
-STORAGE_VERSION = 'BA01'
-FMT = '>BHB'
-FMTSIZE = struct.calcsize(FMT)
-
-REGDELIV = 1
-DIGDELIV = 2
-REGFLAG = struct.pack('>B', REGDELIV)
-DIGFLAG = struct.pack('>B', DIGDELIV)
-
-# Positional arguments for _unpack()
-CPADDR = 0
-PASSWD = 1
-LANG = 2
-NAME = 3
-DIGEST = 4
-OPTIONS = 5
-STATUS = 6
-
-
-
-class BDBMemberAdaptor(MemberAdaptor.MemberAdaptor):
-    def __init__(self, mlist):
-        self._mlist = mlist
-        # metainfo -- {key -> value}
-        #     This table contains storage metadata information.  The keys and
-        #     values are simple strings of variable length.   Here are the
-        #     valid keys:
-        #
-        #         version - the version of the database
-        #
-        #  members -- {address | rec}
-        #     For all regular delivery members, this maps from the member's
-        #     key to their data record, which is a string concatenated of the
-        #     following:
-        #
-        #     -- fixed data (as a packed struct)
-        #        + 1-byte digest or regular delivery flag
-        #        + 2-byte option flags
-        #        + 1-byte delivery status
-        #     -- variable data (as a pickle of a tuple)
-        #        + their case preserved address or ''
-        #        + their plaintext password
-        #        + their chosen language
-        #        + their realname or ''
-        #
-        # status -- {address | status+time}
-        #     Maps the member's key to their delivery status and change time.
-        #     These are passed as a tuple and are pickled for storage.
-        #
-        # topics -- {address | topicstrings}
-        #     Maps the member's key to their topic strings, concatenated and
-        #     separated by SEP
-        #
-        # bounceinfo -- {address | bounceinfo}
-        #     Maps the member's key to their bounceinfo, as a pickle
-        #
-        # Make sure the database directory exists
-        path = os.path.join(mlist.fullpath(), 'member.db')
-        exists = False
-        try:
-            os.mkdir(path, 02775)
-        except OSError, e:
-            if e.errno <> errno.EEXIST: raise
-            exists = True
-        # Create the environment
-        self._env = env = db.DBEnv()
-        if exists:
-            # We must join an existing environment, otherwise we'll get
-            # DB_RUNRECOVERY errors when the second process to open the
-            # environment begins a transaction.  I don't get it.
-            env.open(path, db.DB_JOINENV)
-        else:
-            env.open(path,
-                     db.DB_CREATE |
-                     db.DB_RECOVER |
-                     db.DB_INIT_MPOOL |
-                     db.DB_INIT_TXN
-                     )
-        self._txn = None
-        self._tables = []
-        self._metainfo = self._setupDB('metainfo')
-        self._members = self._setupDB('members')
-        self._status = self._setupDB('status')
-        self._topics = self._setupDB('topics')
-        self._bounceinfo = self._setupDB('bounceinfo')
-        # Check the database version number
-        version = self._metainfo.get('version')
-        if version is None:
-            # Initialize
-            try:
-                self.txn_begin()
-                self._metainfo.put('version', STORAGE_VERSION, txn=self._txn)
-            except:
-                self.txn_abort()
-                raise
-            else:
-                self.txn_commit()
-        else:
-            # Currently there's nothing to upgrade
-            assert version == STORAGE_VERSION
-
-    def _setupDB(self, name):
-        d = db.DB(self._env)
-        openflags = db.DB_CREATE
-        # db 4.1 requires that databases be opened in a transaction.  We'll
-        # use auto commit, but only if that flag exists (i.e. we're using at
-        # least db 4.1).
-        try:
-            openflags |= db.DB_AUTO_COMMIT
-        except AttributeError:
-            pass
-        d.open(name, db.DB_BTREE, openflags)
-        self._tables.append(d)
-        return d
-
-    def _close(self):
-        self.txn_abort()
-        for d in self._tables:
-            d.close()
-        # Checkpoint the database twice, as recommended by Sleepycat
-        self._checkpoint()
-        self._checkpoint()
-        self._env.close()
-
-    def _checkpoint(self):
-        self._env.txn_checkpoint(0, 0, db.DB_FORCE)
-
-    def txn_begin(self):
-        assert self._txn is None
-        self._txn = self._env.txn_begin()
-
-    def txn_commit(self):
-        assert self._txn is not None
-        self._txn.commit()
-        self._checkpoint()
-        self._txn = None
-
-    def txn_abort(self):
-        if self._txn is not None:
-            self._txn.abort()
-            self._checkpoint()
-        self._txn = None
-
-    def _unpack(self, member):
-        # Assume member is a LCE (i.e. lowercase key)
-        rec = self._members.get(member.lower())
-        assert rec is not None
-        fixed = struct.unpack(FMT, rec[:FMTSIZE])
-        vari = pickle.loads(rec[FMTSIZE:])
-        return vari + fixed
-
-    def _pack(self, member, cpaddr, passwd, lang, name, digest, flags, status):
-        # Assume member is a LCE (i.e. lowercase key)
-        fixed = struct.pack(FMT, digest, flags, status)
-        vari = pickle.dumps((cpaddr, passwd, lang, name))
-        self._members.put(member.lower(), fixed+vari, txn=self._txn)
-
-    # MemberAdaptor writeable interface
-
-    def addNewMember(self, member, **kws):
-        assert self._mlist.Locked()
-        # Make sure this address isn't already a member
-        if self.isMember(member):
-            raise Errors.MMAlreadyAMember, member
-        # Parse the keywords
-        digest = False
-        password = Utils.MakeRandomPassword()
-        language = self._mlist.preferred_language
-        realname = None
-        if kws.has_key('digest'):
-            digest = kws['digest']
-            del kws['digest']
-        if kws.has_key('password'):
-            password = kws['password']
-            del kws['password']
-        if kws.has_key('language'):
-            language = kws['language']
-            del kws['language']
-        if kws.has_key('realname'):
-            realname = kws['realname']
-            del kws['realname']
-        # Assert that no other keywords are present
-        if kws:
-            raise ValueError, kws.keys()
-        # Should we store the case-preserved address?
-        if Utils.LCDomain(member) == member.lower():
-            cpaddress = ''
-        else:
-            cpaddress = member
-        # Calculate the realname
-        if realname is None:
-            realname = ''
-        # Calculate the digest flag
-        if digest:
-            digest = DIGDELIV
-        else:
-            digest = REGDELIV
-        self._pack(member.lower(),
-                   cpaddress, password, language, realname,
-                   digest, self._mlist.new_member_options,
-                   MemberAdaptor.ENABLED)
-
-    def removeMember(self, member):
-        txn = self._txn
-        assert txn is not None
-        assert self._mlist.Locked()
-        self.__assertIsMember(member)
-        key = member.lower()
-        # Remove the table entries
-        self._members.delete(key, txn=txn)
-        if self._status.has_key(key):
-            self._status.delete(key, txn=txn)
-        if self._topics.has_key(key):
-            self._topics.delete(key, txn=txn)
-        if self._bounceinfo.has_key(key):
-            self._bounceinfo.delete(key, txn=txn)
-
-    def changeMemberAddress(self, member, newaddress, nodelete=0):
-        assert self._mlist.Locked()
-        self.__assertIsMember(member)
-        okey = member.lower()
-        nkey = newaddress.lower()
-        txn = self._txn
-        assert txn is not None
-        # First, store a new member record, changing the case preserved addr.
-        # Then delete the old record.
-        cpaddr, passwd, lang, name, digest, flags, sts = self._unpack(okey)
-        self._pack(nkey, newaddress, passwd, lang, name, digest, flags, sts)
-        if not nodelete:
-            self._members.delete(okey, txn)
-        # Copy over the status times, topics, and bounce info, if present
-        timestr = self._status.get(okey)
-        if timestr is not None:
-            self._status.put(nkey, timestr, txn=txn)
-            if not nodelete:
-                self._status.delete(okey, txn)
-        topics = self._topics.get(okey)
-        if topics is not None:
-            self._topics.put(nkey, topics, txn=txn)
-            if not nodelete:
-                self._topics.delete(okey, txn)
-        binfo = self._bounceinfo.get(nkey)
-        if binfo is not None:
-            self._binfo.put(nkey, binfo, txn=txn)
-            if not nodelete:
-                self._binfo.delete(okey, txn)
-
-    def setMemberPassword(self, member, password):
-        assert self._mlist.Locked()
-        self.__assertIsMember(member)
-        member = member.lower()
-        cpaddr, oldpw, lang, name, digest, flags, status = self._unpack(member)
-        self._pack(member, cpaddr, password, lang, name, digest, flags, status)
-
-    def setMemberLanguage(self, member, language):
-        assert self._mlist.Locked()
-        self.__assertIsMember(member)
-        member = member.lower()
-        cpaddr, passwd, olang, name, digest, flags, sts = self._unpack(member)
-        self._pack(member, cpaddr, passwd, language, name, digest, flags, sts)
-
-    def setMemberOption(self, member, flag, value):
-        assert self._mlist.Locked()
-        self.__assertIsMember(member)
-        member = member.lower()
-        cpaddr, passwd, lang, name, digest, options, sts = self._unpack(member)
-        # Sanity check for the digest flag
-        if flag == mm_cfg.Digests:
-            if value:
-                # Be sure the list supports digest delivery
-                if not self._mlist.digestable:
-                    raise Errors.CantDigestError
-                digest = DIGDELIV
-            else:
-                # Be sure the list supports regular delivery
-                if not self._mlist.nondigestable:
-                    raise Errors.MustDigestError
-                # When toggling off digest delivery, we want to be sure to set
-                # things up so that the user receives one last digest,
-                # otherwise they may lose some email
-                self._mlist.one_last_digest[member] = cpaddr
-                digest = REGDELIV
-        else:
-            if value:
-                options |= flag
-            else:
-                options &= ~flag
-        self._pack(member, cpaddr, passwd, lang, name, digest, options, sts)
-
-    def setMemberName(self, member, realname):
-        assert self._mlist.Locked()
-        self.__assertIsMember(member)
-        member = member.lower()
-        cpaddr, passwd, lang, oldname, digest, flags, sts = self._unpack(
-            member)
-        self._pack(member, cpaddr, passwd, lang, realname, digest, flags, sts)
-
-    def setMemberTopics(self, member, topics):
-        assert self._mlist.Locked()
-        self.__assertIsMember(member)
-        member = member.lower()
-        if topics:
-            self._topics.put(member, SEP.join(topics), txn=self._txn)
-        elif self._topics.has_key(member):
-            # No record is the same as no topics
-            self._topics.delete(member, self._txn)
-
-    def setDeliveryStatus(self, member, status):
-        assert status in (MemberAdaptor.ENABLED,  MemberAdaptor.UNKNOWN,
-                          MemberAdaptor.BYUSER,   MemberAdaptor.BYADMIN,
-                          MemberAdaptor.BYBOUNCE)
-        assert self._mlist.Locked()
-        self.__assertIsMember(member)
-        if status == MemberAdaptor.ENABLED:
-            # Enable by resetting their bounce info
-            self.setBounceInfo(member, None)
-        else:
-            # Pickle up the status an the current time and store that in the
-            # database.  Use binary mode.
-            data = pickle.dumps((status, time.time()), 1)
-            self._status.put(member.lower(), data, txn=self._txn)
-
-    def setBounceInfo(self, member, info):
-        assert self._mlist.Locked()
-        self.__assertIsMember(member)
-        member = member.lower()
-        if info is None:
-            # This means to reset the bounce and delivery status information
-            if self._bounceinfo.has_key(member):
-                self._bounceinfo.delete(member, self._txn)
-            if self._status.has_key(member):
-                self._status.delete(member, self._txn)
-        else:
-            # Use binary mode
-            data = pickle.dumps(info, 1)
-            self._status.put(member, data, txn=self._txn)
-
-    # The readable interface
-
-    # BAW: It would be more efficient to simply return the iterator, but
-    # modules like admin.py can't handle that yet.  They requires lists.
-    def getMembers(self):
-        return list(_AllMembersIterator(self._members))
-
-    def getRegularMemberKeys(self):
-        return list(_DeliveryMemberIterator(self._members, REGFLAG))
-
-    def getDigestMemberKeys(self):
-        return list(_DeliveryMemberIterator(self._members, DIGFLAG))
-
-    def __assertIsMember(self, member):
-        if not self.isMember(member):
-            raise Errors.NotAMemberError, member
-
-    def isMember(self, member):
-        return self._members.has_key(member.lower())
-
-    def getMemberKey(self, member):
-        self.__assertIsMember(member)
-        return member.lower()
-
-    def getMemberCPAddress(self, member):
-        self.__assertIsMember(member)
-        cpaddr = self._unpack(member)[CPADDR]
-        if cpaddr:
-            return cpaddr
-        return member
-
-    def getMemberCPAddresses(self, members):
-        rtn = []
-        for member in members:
-            member = member.lower()
-            if self._members.has_key(member):
-                rtn.append(self._unpack(member)[CPADDR])
-            else:
-                rtn.append(None)
-        return rtn
-
-    def authenticateMember(self, member, response):
-        self.__assertIsMember(member)
-        passwd = self._unpack(member)[PASSWD]
-        if passwd == response:
-            return passwd
-        return False
-
-    def getMemberPassword(self, member):
-        self.__assertIsMember(member)
-        return self._unpack(member)[PASSWD]
-
-    def getMemberLanguage(self, member):
-        if not self.isMember(member):
-            return self._mlist.preferred_language
-        lang = self._unpack(member)[LANG]
-        if lang in self._mlist.GetAvailableLanguages():
-            return lang
-        return self._mlist.preferred_language
-
-    def getMemberOption(self, member, flag):
-        self.__assertIsMember(member)
-        if flag == mm_cfg.Digests:
-            return self._unpack(member)[DIGEST] == DIGDELIV
-        options = self._unpack(member)[OPTIONS]
-        return bool(options & flag)
-
-    def getMemberName(self, member):
-        self.__assertIsMember(member)
-        name = self._unpack(member)[NAME]
-        return name or None
-
-    def getMemberTopics(self, member):
-        self.__assertIsMember(member)
-        topics = self._topics.get(member.lower(), '')
-        if not topics:
-            return []
-        return topics.split(SEP)
-
-    def getDeliveryStatus(self, member):
-        self.__assertIsMember(member)
-        data = self._status.get(member.lower())
-        if data is None:
-            return MemberAdaptor.ENABLED
-        status, when = pickle.loads(data)
-        return status
-
-    def getDeliveryStatusChangeTime(self, member):
-        self.__assertIsMember(member)
-        data = self._status.get(member.lower())
-        if data is None:
-            return 0
-        status, when = pickle.loads(data)
-        return when
-
-    # BAW: see above, re iterators
-    def getDeliveryStatusMembers(self, status=(MemberAdaptor.UNKNOWN,
-                                               MemberAdaptor.BYUSER,
-                                               MemberAdaptor.BYADMIN,
-                                               MemberAdaptor.BYBOUNCE)):
-        return list(_StatusMemberIterator(self._members, self._status, status))
-
-    def getBouncingMembers(self):
-        return list(_BouncingMembersIterator(self._bounceinfo))
-
-    def getBounceInfo(self, member):
-        self.__assertIsMember(member)
-        return self._bounceinfo.get(member.lower())
-
-
-
-class _MemberIterator:
-    def __init__(self, table):
-        self._table = table
-        self._c = table.cursor()
-
-    def __iter__(self):
-        raise NotImplementedError
-
-    def next(self):
-        raise NotImplementedError
-
-    def close(self):
-        if self._c:
-            self._c.close()
-            self._c = None
-
-    def __del__(self):
-        self.close()
-
-
-class _AllMembersIterator(_MemberIterator):
-    def __iter__(self):
-        return _AllMembersIterator(self._table)
-
-    def next(self):
-        rec = self._c.next()
-        if rec:
-            return rec[0]
-        self.close()
-        raise StopIteration
-
-
-class _DeliveryMemberIterator(_MemberIterator):
-    def __init__(self, table, flag):
-        _MemberIterator.__init__(self, table)
-        self._flag = flag
-
-    def __iter__(self):
-        return _DeliveryMemberIterator(self._table, self._flag)
-
-    def next(self):
-        rec = self._c.next()
-        while rec:
-            addr, data = rec
-            if data[0] == self._flag:
-                return addr
-            rec = self._c.next()
-        self.close()
-        raise StopIteration
-
-
-class _StatusMemberIterator(_MemberIterator):
-    def __init__(self, table, statustab, status):
-        _MemberIterator.__init__(self, table)
-        self._statustab = statustab
-        self._status = status
-
-    def __iter__(self):
-        return _StatusMemberIterator(self._table,
-                                     self._statustab,
-                                     self._status)
-
-    def next(self):
-        rec = self._c.next()
-        while rec:
-            addr = rec[0]
-            data = self._statustab.get(addr)
-            if data is None:
-                status = MemberAdaptor.ENABLED
-            else:
-                status, when = pickle.loads(data)
-            if status in self._status:
-                return addr
-            rec = self._c.next()
-        self.close()
-        raise StopIteration
-
-
-class _BouncingMembersIterator(_MemberIterator):
-    def __iter__(self):
-        return _BouncingMembersIterator(self._table)
-
-    def next(self):
-        rec = self._c.next()
-        if rec:
-            return rec[0]
-        self.close()
-        raise StopIteration
-
-
-
-# For extend.py
-def fixlock(mlist):
-    def Lock(self, timeout=0):
-        MailList.Lock(self, timeout)
-        try:
-            self._memberadaptor.txn_begin()
-        except:
-            MailList.Unlock(self)
-            raise
-    mlist.Lock = new.instancemethod(Lock, mlist, MailList)
-
-
-def fixsave(mlist):
-    def Save(self):
-        self._memberadaptor.txn_commit()
-        MailList.Save(self)
-    mlist.Save = new.instancemethod(Save, mlist, MailList)
-
-
-def fixunlock(mlist):
-    def Unlock(self):
-        # It's fine to abort the transaction even if there isn't one in
-        # process, say because the Save() already committed it
-        self._memberadaptor.txn_abort()
-        MailList.Unlock(self)
-    mlist.Unlock = new.instancemethod(Unlock, mlist, MailList)
-
-
-def extend(mlist):
-    mlist._memberadaptor = BDBMemberAdaptor(mlist)
-    fixlock(mlist)
-    fixsave(mlist)
-    fixunlock(mlist)
-    # To make sure we got everything, let's actually delete the
-    # OldStyleMemberships dictionaries.  Assume if it has one, it has all
-    # attributes.
-    try:
-        del mlist.members
-        del mlist.digest_members
-        del mlist.passwords
-        del mlist.language
-        del mlist.user_options
-        del mlist.usernames
-        del mlist.topics_userinterest
-        del mlist.delivery_status
-        del mlist.bounce_info
-    except AttributeError:
-        pass
-    # BAW: How can we ensure that the BDBMemberAdaptor is closed?

Modified: branches/tmp-sqlalchemy-branch/Mailman/MailList.py
===================================================================
--- branches/tmp-sqlalchemy-branch/Mailman/MailList.py  2006-12-06 05:16:54 UTC 
(rev 8114)
+++ branches/tmp-sqlalchemy-branch/Mailman/MailList.py  2006-12-07 14:13:56 UTC 
(rev 8115)
@@ -88,6 +88,21 @@
 class MailList(object, HTMLFormatter, Deliverer, ListAdmin,
                Archiver, Digester, SecurityManager, Bouncer, GatewayManager,
                Autoresponder, TopicMgr, Pending.Pending):
+    def __new__(cls, *args):
+        if not args:
+            # We're probably being created from the database query, so just
+            # let the super class do its thing.
+            return super(MailList, cls).__new__(cls, *args)
+        # Some one is opening the mailing list, so do a query
+        if '@' in args[0]:
+            fqdn_listname = args[0]
+        else:
+            fqdn_listname = '[EMAIL PROTECTED]' % (args[0], 
config.DEFAULT_EMAIL_HOST)
+        mlist = dbcontext.get_list(fqdn_listname)
+        if not mlist:
+            raise Errors.MMUnknownListError(fqdn_listname)
+        return mlist
+
     #
     # A MailList object's basic Python object model support
     #
@@ -567,7 +582,9 @@
             raise Errors.MMUnknownListError(fqdn_listname)
         self._memberadaptor.load()
         if check_version:
-            self.CheckVersion(dict)
+            # XXX for now disable version checks.  We'll fold this into schema
+            # updates eventually.
+            #self.CheckVersion(dict)
             self.CheckValues()
 
 

Deleted: branches/tmp-sqlalchemy-branch/Mailman/SAMemberships.py
===================================================================
--- branches/tmp-sqlalchemy-branch/Mailman/SAMemberships.py     2006-12-06 
05:16:54 UTC (rev 8114)
+++ branches/tmp-sqlalchemy-branch/Mailman/SAMemberships.py     2006-12-07 
14:13:56 UTC (rev 8115)
@@ -1,330 +0,0 @@
-# Copyright (C) 2006 by the Free Software Foundation, Inc.
-#
-# 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 2
-# 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, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
-# USA.
-
-"""An experimental SQLAlchemy-based membership adaptor."""
-
-# XXX THIS FILE DOES NOT YET WORK!
-
-import os
-import re
-import time
-
-from sqlalchemy import *
-from string import Template
-
-from Mailman import Defaults
-from Mailman import Errors
-from Mailman import MemberAdaptor
-from Mailman import Utils
-from Mailman.configuration import config
-
-NUL = '\0'
-
-
-
-# Python classes representing the data in the SQLAlchemy database.  These will
-# be associated with tables via object mappers.
-
-class Member(object):
-    def __init__(self, mlist,
-                 address, realname=None, password=None,
-                 digests_p=False, language=None):
-        self.lckey          = address.lower()
-        self.address        = address
-        self.realname       = realname
-        self.password       = password or Utils.MakeRandomPassword()
-        self.language       = language or mlist.preferred_language
-        self.digests_p      = digests_p
-        self.options        = mlist.new_member_options
-        self.topics         = ''
-        self.status         = MemberAdaptor.ENABLED
-        # XXX This should really be a datetime
-        self.disable_time   = 0
-
-
-
-_table      = None
-_metadata   = None
-_mapper     = None
-
-def create_table():
-    global _table, _metadata, _mapper
-
-    if _table:
-        return
-    _metadata = MetaData('table metadata')
-    _table = Table(
-            'members', _metadata,
-            Column('member_id', Integer, primary_key=True),
-            Column('lckey', Unicode(), index=True, nullable=False),
-            Column('address', Unicode(), index=True, nullable=False),
-            Column('realname', Unicode()),
-            Column('password', Unicode()),
-            Column('language', String(2)),
-            Column('digest', Boolean),
-            Column('options', Integer),
-            Column('status', Integer),
-            Column('disable_time', Float),
-            )
-    _mapper = mapper(Member, _table)
-
-
-
-class SAMemberships(MemberAdaptor.MemberAdaptor):
-    def __init__(self, mlist):
-        self._mlist     = mlist
-        self._metadata  = None
-        self._session   = None
-        self._txn       = None
-
-    def _connect(self):
-        create_table()
-        # We cannot connect in the __init__() because our adaptor requires the
-        # fqdn_listname to exist.  In MailList.Create() that won't be the case.
-        #
-        # Calculate the engine url, expanding placeholder variables.
-        engine_url = Template(config.SQLALCHEMY_ENGINE_URL).substitute(
-            {'listname' : self._mlist.fqdn_listname,
-             'listdir'  : os.path.join(config.LIST_DATA_DIR,
-                                       self._mlist.fqdn_listname),
-             })
-        print 'engine_url:', engine_url
-        self._engine = create_engine(engine_url)
-        self._session = create_session(bind_to=self._engine)
-        self._session.bind_table(_table, self._engine)
-        self._session.bind_mapper(_mapper, self._engine)
-        # XXX There must be a better way to figure out whether the tables need
-        # to be created or not.
-        try:
-            _table.create()
-        except exceptions.SQLError:
-            pass
-
-    #
-    # The transaction interface
-    #
-
-    def load(self):
-        if self._session is None:
-            self._connect()
-        assert self._txn is None
-        self._session.clear()
-        self._txn = self._session.create_transaction()
-
-    def lock(self):
-        pass
-
-    def save(self):
-        # When a MailList is first Create()'d, the load() callback doesn't get
-        # called, so there will be no transaction.
-        if self._txn:
-            self._txn.commit()
-            self._txn = None
-
-    def unlock(self):
-        if self._txn is not None:
-            # The MailList has not been saved, but it is being unlocked, so
-            # throw away all pending changes.
-            self._txn.rollback()
-            self._txn = None
-
-    #
-    # The readable interface
-    #
-
-    def getMembers(self):
-        return [m.lckey for m in self._session.query(Member).select_by()]
-
-    def getRegularMemberKeys(self):
-        query = self._session.query(Member)
-        return [m.lckey for m in query.select(Member.c.digests_p == False)]
-
-    def getDigestMemberKeys(self):
-        query = self._session.query(Member)
-        return [m.lckey for m in query.select(Member.c.digests_p == True)]
-
-    def _get_member(self, member):
-        members = self._session.query(Member).select_by(lckey=member.lower())
-        if not members:
-            return None
-        assert len(members) == 1
-        return members[0]
-
-    def _get_member_strict(self, member):
-        member_obj = self._get_member(member)
-        if not member_obj:
-            raise Errors.NotAMemberError(member)
-        return member_obj
-
-    def isMember(self, member):
-        return bool(self._get_member(member))
-
-    def getMemberKey(self, member):
-        self._get_member_strict(member)
-        return member.lower()
-
-    def getMemberCPAddress(self, member):
-        return self._get_member(member).address
-
-    def getMemberCPAddresses(self, members):
-        query = self._session.query(Member)
-        return [user.address for user in query.select(
-            in_(Member.c.lckey, [m.lower() for m in members]))]
-
-    def getMemberPassword(self, member):
-        return self._get_member_strict(member).password
-
-    def authenticateMember(self, member, response):
-        return self._get_member_strict(member).password == response
-
-    def getMemberLanguage(self, member):
-        member = self._get_member(member)
-        if member and member.language in self._mlist.GetAvailableLanguages():
-            return member.language
-        return self._mlist.preferred_language
-
-    def getMemberOption(self, member, flag):
-        return bool(self._get_member_strict(member).options & flag)
-
-    def getMemberName(self, member):
-        return self._get_member_strict(member).realname
-
-    def getMemberTopics(self, member):
-        topics = self._get_member_strict(member).topics
-        if not topics:
-            return []
-        return topics.split(NUL)
-
-    def getDeliveryStatus(self, member):
-        return self._get_member_strict(member).status
-
-    def getDeliveryStatusChangeTime(self, member):
-        member = self._get_member_strict(member)
-        if member.status == MemberAdaptor.ENABLED:
-            return 0
-        return member.disable_time
-
-    def getDeliveryStatusMembers(self, status=(MemberAdaptor.UNKNOWN,
-                                               MemberAdaptor.BYUSER,
-                                               MemberAdaptor.BYADMIN,
-                                               MemberAdaptor.BYBOUNCE)):
-        query = self._session.query(Member)
-        return [user.lckey for user in query.select(
-            in_(Member.c.status, status))]
-
-    def getBouncingMembers(self):
-        "XXX"
-
-    def getBounceInfo(self):
-        "XXX"
-
-    #
-    # The writable interface
-    #
-
-    def addNewMember(self, member, **kws):
-        assert self._mlist.Locked()
-        if self.isMember(member):
-            raise Errors.MMAlreadyAMember(member)
-        try:
-            new_member = Member(self._mlist, member, **kws)
-            self._session.save(new_member)
-            self._session.flush()
-        except TypeError:
-            # Transform exception to API specification
-            raise ValueError
-
-    def removeMember(self, memberkey):
-        assert self._mlist.Locked()
-        member = self._get_member_strict(memberkey)
-        self._session.delete(member)
-
-    def changeMemberAddress(self, memberkey, newaddress, nodelete=False):
-        assert self._mlist.Locked()
-        member = self._get_member_strict(memberkey)
-        # First, add the new member from the previous data
-        self.addNewMember(newaddress, member.realname, member.password,
-                          member.digests_p, member.language)
-        new_member = self._get_member(newaddress)
-        assert new_member
-        new_member.options      = member.options
-        new_member.topics       = member.topics
-        new_member.status       = MemberAdaptor.ENABLED
-        new_member.disable_time = 0
-        if not nodelete:
-            self._session.delete(member)
-
-    def setMemberPassword(self, member, password):
-        assert self._mlist.Locked()
-        self._get_member_strict(member).password = password
-
-    def setMemberLanguage(self, member, language):
-        assert self._mlist.Locked()
-        self._get_member_strict(member).language = language
-
-    def setMemberOption(self, member, flag, value):
-        assert self._mlist.Locked()
-        member = self._get_member_strict(member)
-        # XXX the OldStyleMemberships adaptor will raise CantDigestError,
-        # MustDigestError, AlreadyReceivingDigests, and
-        # AlreadyReceivingRegularDeliveries in certain cases depending on the
-        # configuration of the mailing list and the member's delivery status.
-        # These semantics are not defined in the API so to keep things simple,
-        # I am not reproducing them here.  Ideally, adaptors should not be
-        # doing semantic integrity checks, but I'm also not going to change
-        # the OldStyleMemberships adaptor.
-        #
-        # We still need to handle digests differently, because they aren't
-        # really represented as a unique flag in the options bitfield.
-        if flag == Defaults.Digests:
-            member.digests_p = bool(value)
-        else:
-            if value:
-                member.options |= flag
-            else:
-                member.options &= ~flag
-
-    def setMemberName(self, member, realname):
-        assert self._mlist.Locked()
-        self._get_member_strict(member).realname = realname
-
-    def setMemberTopics(self, member, topics):
-        assert self._mlist.Locked()
-        # For simplicity, we represent a user's topics of interest as a
-        # null-joined string, which will be split properly by the accessor.
-        if not topics:
-            topics = None
-        else:
-            topics = NUL.join(topics)
-        self._get_member_strict(member).topics = topics
-
-    def setDeliveryStatus(self, member, status):
-        assert status in (MemberAdaptor.ENABLED,  MemberAdaptor.UNKNOWN,
-                          MemberAdaptor.BYUSER,   MemberAdaptor.BYADMIN,
-                          MemberAdaptor.BYBOUNCE)
-        assert self._mlist.Locked()
-        member = self._get_member_strict(member)
-        if status == MemberAdaptor.ENABLED:
-            # XXX zap bounce info
-            disable_time = 0
-        else:
-            disable_time = time.time()
-        member.disable_time = disable_time
-        member.status = status
-
-    def setBounceInfo(self, member, info):
-        "XXX"

Modified: branches/tmp-sqlalchemy-branch/Mailman/Utils.py
===================================================================
--- branches/tmp-sqlalchemy-branch/Mailman/Utils.py     2006-12-06 05:16:54 UTC 
(rev 8114)
+++ branches/tmp-sqlalchemy-branch/Mailman/Utils.py     2006-12-07 14:13:56 UTC 
(rev 8115)
@@ -42,6 +42,7 @@
 from Mailman import Errors
 from Mailman.SafeDict import SafeDict
 from Mailman.configuration import config
+from Mailman.database import dbcontext
 
 # REMOVEME when Python 2.4 is minimum requirement
 try:
@@ -67,19 +68,9 @@
 
 
 
-def list_exists(listname):
-    """Return true iff list `listname' exists."""
-    # The existance of any of the following file proves the list exists
-    # <wink>: config.pck, config.pck.last, config.db, config.db.last
-    #
-    # The former two are for 2.1alpha3 and beyond, while the latter two are
-    # for all earlier versions.
-    basepath = os.path.join(config.LIST_DATA_DIR, listname)
-    for ext in ('.pck', '.pck.last', '.db', '.db.last'):
-        dbfile = os.path.join(basepath, 'config' + ext)
-        if os.path.exists(dbfile):
-            return True
-    return False
+def list_exists(fqdn_listname):
+    """Return true iff list `fqdn_listname' exists."""
+    return bool(dbcontext.get_list(fqdn_listname))
 
 
 def list_names():

Modified: branches/tmp-sqlalchemy-branch/Mailman/database/__init__.py
===================================================================
--- branches/tmp-sqlalchemy-branch/Mailman/database/__init__.py 2006-12-06 
05:16:54 UTC (rev 8114)
+++ branches/tmp-sqlalchemy-branch/Mailman/database/__init__.py 2006-12-07 
14:13:56 UTC (rev 8115)
@@ -28,7 +28,7 @@
 
 class DBContext(object):
     def __init__(self):
-        self.tables = []
+        self.tables = {}
         self.metadata = None
         self.session = None
         self._txn = None
@@ -44,7 +44,7 @@
         version_table = None
         for module in (address, listdata, version):
             table = module.make_table(self.metadata)
-            self.tables.append(table)
+            self.tables[table.name] = table
             if module is version:
                 version_table = table
         self.metadata.create_all()
@@ -76,6 +76,16 @@
             self._txn.rollback()
             self._txn = None
 
+    # Interface methods
+    def get_list(self, fqdn_listname):
+        from Mailman.MailList import MailList
+        list_name, host_name = fqdn_listname.split('@', 1)
+        q = self.session.query(MailList)
+        mlists = q.select_by(list_name=list_name, host_name=host_name)
+        if mlists:
+            return mlists[0]
+        return None
 
+
 
 dbcontext = DBContext()

Modified: branches/tmp-sqlalchemy-branch/Mailman/database/listdata.py
===================================================================
--- branches/tmp-sqlalchemy-branch/Mailman/database/listdata.py 2006-12-06 
05:16:54 UTC (rev 8114)
+++ branches/tmp-sqlalchemy-branch/Mailman/database/listdata.py 2006-12-07 
14:13:56 UTC (rev 8115)
@@ -27,6 +27,7 @@
         # Attributes not directly modifiable via the web u/i
         Column('list_id',           Integer, primary_key=True),
         Column('list_name',         Unicode),
+        Column('web_page_url',      Unicode),
         # OldStyleMemberships attributes, temporarily stored as pickles.
         Column('bounce_info',           PickleType),
         Column('delivery_status',       PickleType),
@@ -158,7 +159,6 @@
             list_name = row['listdata_list_name']
             host_name = row['listdata_host_name']
             fqdn_name = '[EMAIL PROTECTED]' % (list_name, host_name)
-            print 'fqdn_name:', fqdn_name
             mlist.InitTempVars(fqdn_name)
         # In all cases, let SA proceed as normal
         return EXT_PASS


This was sent by the SourceForge.net collaborative development platform, the 
world's largest Open Source development site.
_______________________________________________
Mailman-checkins mailing list
[email protected]
Unsubscribe: 
http://mail.python.org/mailman/options/mailman-checkins/archive%40jab.org

Reply via email to