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