summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/mailman/app/subscriptions.py7
-rw-r--r--src/mailman/database/base.py25
-rw-r--r--src/mailman/database/factory.py1
-rw-r--r--src/mailman/database/model.py28
-rw-r--r--src/mailman/database/sqlite.py4
-rw-r--r--src/mailman/database/types.py45
-rw-r--r--src/mailman/interfaces/database.py2
-rw-r--r--src/mailman/model/address.py9
-rw-r--r--src/mailman/model/autorespond.py19
-rw-r--r--src/mailman/model/bans.py4
-rw-r--r--src/mailman/model/bounce.py6
-rw-r--r--src/mailman/model/digests.py2
-rw-r--r--src/mailman/model/domain.py2
-rw-r--r--src/mailman/model/language.py2
-rw-r--r--src/mailman/model/mailinglist.py157
-rw-r--r--src/mailman/model/member.py6
-rw-r--r--src/mailman/model/message.py3
-rw-r--r--src/mailman/model/messagestore.py16
-rw-r--r--src/mailman/model/mime.py4
-rw-r--r--src/mailman/model/pending.py15
-rw-r--r--src/mailman/model/preferences.py6
-rw-r--r--src/mailman/model/requests.py11
-rw-r--r--src/mailman/model/roster.py4
-rw-r--r--src/mailman/model/tests/test_listmanager.py7
-rw-r--r--src/mailman/model/uid.py3
-rw-r--r--src/mailman/model/user.py27
-rw-r--r--src/mailman/model/version.py3
-rw-r--r--src/mailman/utilities/importer.py15
28 files changed, 216 insertions, 217 deletions
diff --git a/src/mailman/app/subscriptions.py b/src/mailman/app/subscriptions.py
index 303303e70..99c6ab2de 100644
--- a/src/mailman/app/subscriptions.py
+++ b/src/mailman/app/subscriptions.py
@@ -88,8 +88,7 @@ class SubscriptionService:
@dbconnection
def get_member(self, store, member_id):
"""See `ISubscriptionService`."""
- members = store.query(Member).filter(
- Member._member_id == member_id)
+ members = store.query(Member).filter(Member._member_id == member_id)
if members.count() == 0:
return None
else:
@@ -117,7 +116,7 @@ class SubscriptionService:
if address is None or user is None:
return []
query.append(or_(Member.address_id == address.id,
- Member.user_id == user.id))
+ Member.user_id == user.id))
else:
# subscriber is a user id.
user = user_manager.get_user_by_id(subscriber)
@@ -126,7 +125,7 @@ class SubscriptionService:
if len(address_ids) == 0 or user is None:
return []
query.append(or_(Member.user_id == user.id,
- Member.address_id.in_(address_ids)))
+ Member.address_id.in_(address_ids)))
# Calculate the rest of the query expression, which will get And'd
# with the Or clause above (if there is one).
if list_id is not None:
diff --git a/src/mailman/database/base.py b/src/mailman/database/base.py
index f379b3124..8c426f8cf 100644
--- a/src/mailman/database/base.py
+++ b/src/mailman/database/base.py
@@ -19,28 +19,22 @@ from __future__ import absolute_import, print_function, unicode_literals
__metaclass__ = type
__all__ = [
- 'StormBaseDatabase',
+ 'SABaseDatabase',
]
-import os
-import sys
import logging
-from lazr.config import as_boolean
-from pkg_resources import resource_listdir, resource_string
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
-from sqlalchemy.orm.session import Session
from zope.interface import implementer
from mailman.config import config
from mailman.interfaces.database import IDatabase
-from mailman.model.version import Version
from mailman.utilities.string import expand
-log = logging.getLogger('mailman.config')
+log = logging.getLogger('mailman.config')
NL = '\n'
@@ -53,17 +47,15 @@ class SABaseDatabase:
"""
# Tag used to distinguish the database being used. Override this in base
# classes.
-
TAG = ''
def __init__(self):
self.url = None
self.store = None
- self.transaction = None
def begin(self):
"""See `IDatabase`."""
- # SA does this for us.
+ # SQLAlchemy does this for us.
pass
def commit(self):
@@ -102,9 +94,13 @@ class SABaseDatabase:
"""
pass
+ # XXX Abhilash removed teh _prepare() method. Is that because SA takes
+ # care of this for us? If so, then the comment below must be updated.
+ # For reference, the SQLite bug is marked "won't fix".
+
def initialize(self, debug=None):
- """See `IDatabase`"""
- # Calculate the engine url
+ """See `IDatabase`."""
+ # Calculate the engine url.
url = expand(config.database.url, config.paths)
log.debug('Database url: %s', url)
# XXX By design of SQLite, database file creation does not honor
@@ -127,6 +123,9 @@ class SABaseDatabase:
self.store = session()
self.store.commit()
+ # XXX We should probably rename load_migrations() and perhaps get rid of
+ # load_sql(). The latter is never called any more.
+
def load_migrations(self, until=None):
"""Load schema migrations.
diff --git a/src/mailman/database/factory.py b/src/mailman/database/factory.py
index 64fcc242c..450672e5b 100644
--- a/src/mailman/database/factory.py
+++ b/src/mailman/database/factory.py
@@ -62,6 +62,7 @@ class DatabaseFactory:
def _reset(self):
"""See `IDatabase`."""
+ # Avoid a circular import at module level.
from mailman.database.model import Model
self.store.rollback()
self._pre_reset(self.store)
diff --git a/src/mailman/database/model.py b/src/mailman/database/model.py
index d86ebb80e..f8a15162c 100644
--- a/src/mailman/database/model.py
+++ b/src/mailman/database/model.py
@@ -26,23 +26,31 @@ __all__ = [
import contextlib
-from operator import attrgetter
from sqlalchemy.ext.declarative import declarative_base
from mailman.config import config
-class ModelMeta(object):
- """Do more magic on table classes."""
+class ModelMeta:
+ """The custom metaclass for all model base classes.
+
+ This is used in the test suite to quickly reset the database after each
+ test. It works by iterating over all the tables, deleting each. The test
+ suite will then recreate the tables before each test.
+ """
@staticmethod
def _reset(db):
- meta = Model.metadata
- engine = config.db.engine
- with contextlib.closing(engine.connect()) as con:
- trans = con.begin()
- for table in reversed(meta.sorted_tables):
- con.execute(table.delete())
- trans.commit()
+ with contextlib.closing(config.db.engine.connect()) as connection:
+ transaction = connection.begin()
+ try:
+ for table in reversed(Model.metadata.sorted_tables):
+ connection.execute(table.delete())
+ except:
+ transaction.abort()
+ raise
+ else:
+ transaction.commit()
+
Model = declarative_base(cls=ModelMeta)
diff --git a/src/mailman/database/sqlite.py b/src/mailman/database/sqlite.py
index 0594d9091..b70e474cc 100644
--- a/src/mailman/database/sqlite.py
+++ b/src/mailman/database/sqlite.py
@@ -56,7 +56,7 @@ class SQLiteDatabase(SABaseDatabase):
assert parts.scheme == 'sqlite', (
'Database url mismatch (expected sqlite prefix): {0}'.format(url))
path = os.path.normpath(parts.path)
- fd = os.open(path, os.O_WRONLY | os.O_NONBLOCK | os.O_CREAT, 0666)
+ fd = os.open(path, os.O_WRONLY | os.O_NONBLOCK | os.O_CREAT, 0o666)
# Ignore errors
if fd > 0:
os.close(fd)
@@ -72,7 +72,7 @@ def _cleanup(self, tempdir):
def make_temporary(database):
"""Adapts by monkey patching an existing SQLite IDatabase."""
tempdir = tempfile.mkdtemp()
- url = 'sqlite:///' + os.path.join(tempdir, 'mailman.db')
+ url = 'sqlite:///' + os.path.join(tempdir, 'mailman.db')
with configuration('database', url=url):
database.initialize()
database._cleanup = types.MethodType(
diff --git a/src/mailman/database/types.py b/src/mailman/database/types.py
index a6f0b32ca..380ce37dc 100644
--- a/src/mailman/database/types.py
+++ b/src/mailman/database/types.py
@@ -29,30 +29,28 @@ __all__ = [
import uuid
from sqlalchemy import Integer
-from sqlalchemy.types import TypeDecorator, BINARY, CHAR
from sqlalchemy.dialects import postgresql
+from sqlalchemy.types import TypeDecorator, BINARY, CHAR
class Enum(TypeDecorator):
- """
- Stores an integer-based Enum as an integer in the database, and converts it
- on-the-fly.
- """
+ """Handle Python 3.4 style enums.
+ Stores an integer-based Enum as an integer in the database, and
+ converts it on-the-fly.
+ """
impl = Integer
- def __init__(self, *args, **kw):
- self.enum = kw.pop("enum")
- TypeDecorator.__init__(self, *args, **kw)
+ def __init__(self, enum, *args, **kw):
+ self.enum = enum
+ super(Enum, self).__init__(*args, **kw)
def process_bind_param(self, value, dialect):
if value is None:
return None
-
return value.value
-
def process_result_value(self, value, dialect):
if value is None:
return None
@@ -61,29 +59,12 @@ class Enum(TypeDecorator):
class UUID(TypeDecorator):
- """
- Stores a UUID in the database natively when it can and falls back to
- a BINARY(16) or a CHAR(32) when it can't.
-
- ::
-
- from sqlalchemy_utils import UUIDType
- import uuid
-
- class User(Base):
- __tablename__ = 'user'
+ """Handle UUIds."""
- # Pass `binary=False` to fallback to CHAR instead of BINARY
- id = sa.Column(UUIDType(binary=False), primary_key=True)
- """
impl = BINARY(16)
-
python_type = uuid.UUID
def __init__(self, binary=True, native=True):
- """
- :param binary: Whether to use a BINARY(16) or CHAR(32) fallback.
- """
self.binary = binary
self.native = native
@@ -91,7 +72,6 @@ class UUID(TypeDecorator):
if dialect.name == 'postgresql' and self.native:
# Use the native UUID type.
return dialect.type_descriptor(postgresql.UUID())
-
else:
# Fallback to either a BINARY or a CHAR.
kind = self.impl if self.binary else CHAR(32)
@@ -102,29 +82,22 @@ class UUID(TypeDecorator):
if value and not isinstance(value, uuid.UUID):
try:
value = uuid.UUID(value)
-
except (TypeError, ValueError):
value = uuid.UUID(bytes=value)
-
return value
def process_bind_param(self, value, dialect):
if value is None:
return value
-
if not isinstance(value, uuid.UUID):
value = self._coerce(value)
-
if self.native and dialect.name == 'postgresql':
return str(value)
-
return value.bytes if self.binary else value.hex
def process_result_value(self, value, dialect):
if value is None:
return value
-
if self.native and dialect.name == 'postgresql':
return uuid.UUID(value)
-
return uuid.UUID(bytes=value) if self.binary else uuid.UUID(value)
diff --git a/src/mailman/interfaces/database.py b/src/mailman/interfaces/database.py
index d8fde2b93..c2997ba6b 100644
--- a/src/mailman/interfaces/database.py
+++ b/src/mailman/interfaces/database.py
@@ -61,7 +61,7 @@ class IDatabase(Interface):
"""Abort the current transaction."""
store = Attribute(
- """The underlying SQLAlchemy store on which you can do queries.""")
+ """The underlying database object on which you can do queries.""")
diff --git a/src/mailman/model/address.py b/src/mailman/model/address.py
index 7203a31a5..d078f28d5 100644
--- a/src/mailman/model/address.py
+++ b/src/mailman/model/address.py
@@ -26,8 +26,8 @@ __all__ = [
from email.utils import formataddr
-from sqlalchemy import (Column, Integer, String, Unicode,
- ForeignKey, DateTime)
+from sqlalchemy import (
+ Column, DateTime, ForeignKey, Integer, Unicode)
from sqlalchemy.orm import relationship, backref
from zope.component import getUtility
from zope.event import notify
@@ -56,10 +56,11 @@ class Address(Model):
user_id = Column(Integer, ForeignKey('user.id'))
preferences_id = Column(Integer, ForeignKey('preferences.id'))
- preferences = relationship('Preferences',
- backref=backref('Address', uselist=False))
+ preferences = relationship(
+ 'Preferences', backref=backref('Address', uselist=False))
def __init__(self, email, display_name):
+ super(Address, self).__init__()
getUtility(IEmailValidator).validate(email)
lower_case = email.lower()
self.email = lower_case
diff --git a/src/mailman/model/autorespond.py b/src/mailman/model/autorespond.py
index c3aff174a..c74434f7b 100644
--- a/src/mailman/model/autorespond.py
+++ b/src/mailman/model/autorespond.py
@@ -26,8 +26,7 @@ __all__ = [
]
-from sqlalchemy import (Column, Integer, String, Unicode,
- ForeignKey, Date)
+from sqlalchemy import Column, Date, ForeignKey, Integer
from sqlalchemy import desc
from sqlalchemy.orm import relationship
from zope.interface import implementer
@@ -55,7 +54,7 @@ class AutoResponseRecord(Model):
mailing_list_id = Column(Integer, ForeignKey('mailinglist.id'))
mailing_list = relationship('MailingList')
- response_type = Column(Enum(enum=Response))
+ response_type = Column(Enum(Response))
date_sent = Column(Date)
def __init__(self, mailing_list, address, response_type):
@@ -77,10 +76,10 @@ class AutoResponseSet:
def todays_count(self, store, address, response_type):
"""See `IAutoResponseSet`."""
return store.query(AutoResponseRecord).filter_by(
- address = address,
- mailing_list = self._mailing_list,
- response_type = response_type,
- date_sent = today()).count()
+ address=address,
+ mailing_list=self._mailing_list,
+ response_type=response_type,
+ date_sent=today()).count()
@dbconnection
def response_sent(self, store, address, response_type):
@@ -93,8 +92,8 @@ class AutoResponseSet:
def last_response(self, store, address, response_type):
"""See `IAutoResponseSet`."""
results = store.query(AutoResponseRecord).filter_by(
- address = address,
- mailing_list = self._mailing_list,
- response_type = response_type
+ address=address,
+ mailing_list=self._mailing_list,
+ response_type=response_type
).order_by(desc(AutoResponseRecord.date_sent))
return (None if results.count() == 0 else results.first())
diff --git a/src/mailman/model/bans.py b/src/mailman/model/bans.py
index fbbecaebd..8678fc1e7 100644
--- a/src/mailman/model/bans.py
+++ b/src/mailman/model/bans.py
@@ -72,8 +72,8 @@ class BanManager:
@dbconnection
def unban(self, store, email):
"""See `IBanManager`."""
- ban = store.query(Ban).filter_by(email=email,
- list_id=self._list_id).first()
+ ban = store.query(Ban).filter_by(
+ email=email, list_id=self._list_id).first()
if ban is not None:
store.delete(ban)
diff --git a/src/mailman/model/bounce.py b/src/mailman/model/bounce.py
index 1165fee96..cd658052d 100644
--- a/src/mailman/model/bounce.py
+++ b/src/mailman/model/bounce.py
@@ -27,7 +27,7 @@ __all__ = [
-from sqlalchemy import Column, Integer, Unicode, DateTime, Boolean
+from sqlalchemy import Boolean, Column, DateTime, Integer, Unicode
from zope.interface import implementer
from mailman.database.model import Model
@@ -50,7 +50,7 @@ class BounceEvent(Model):
email = Column(Unicode)
timestamp = Column(DateTime)
message_id = Column(Unicode)
- context = Column(Enum(enum=BounceContext))
+ context = Column(Enum(BounceContext))
processed = Column(Boolean)
def __init__(self, list_id, email, msg, context=None):
@@ -85,5 +85,5 @@ class BounceProcessor:
@dbconnection
def unprocessed(self, store):
"""See `IBounceProcessor`."""
- for event in store.query(BounceEvent).filter_by(processed = False):
+ for event in store.query(BounceEvent).filter_by(processed=False):
yield event
diff --git a/src/mailman/model/digests.py b/src/mailman/model/digests.py
index 1b7140824..7bfd512b6 100644
--- a/src/mailman/model/digests.py
+++ b/src/mailman/model/digests.py
@@ -50,7 +50,7 @@ class OneLastDigest(Model):
address_id = Column(Integer, ForeignKey('address.id'))
address = relationship('Address')
- delivery_mode = Column(Enum(enum=DeliveryMode))
+ delivery_mode = Column(Enum(DeliveryMode))
def __init__(self, mailing_list, address, delivery_mode):
self.mailing_list = mailing_list
diff --git a/src/mailman/model/domain.py b/src/mailman/model/domain.py
index 585eccf3d..083e1cf51 100644
--- a/src/mailman/model/domain.py
+++ b/src/mailman/model/domain.py
@@ -26,8 +26,8 @@ __all__ = [
]
+from sqlalchemy import Column, Integer, Unicode
from urlparse import urljoin, urlparse
-from sqlalchemy import Column, Unicode, Integer
from zope.event import notify
from zope.interface import implementer
diff --git a/src/mailman/model/language.py b/src/mailman/model/language.py
index 7b611b6d8..15450c936 100644
--- a/src/mailman/model/language.py
+++ b/src/mailman/model/language.py
@@ -25,8 +25,8 @@ __all__ = [
]
+from sqlalchemy import Column, Integer, Unicode
from zope.interface import implementer
-from sqlalchemy import Column, Unicode, Integer
from mailman.database import Model
from mailman.interfaces import ILanguage
diff --git a/src/mailman/model/mailinglist.py b/src/mailman/model/mailinglist.py
index 385262f28..d00cf3d31 100644
--- a/src/mailman/model/mailinglist.py
+++ b/src/mailman/model/mailinglist.py
@@ -27,10 +27,11 @@ __all__ = [
import os
-from sqlalchemy import (Column, Boolean, DateTime, Float, Integer, Unicode,
- PickleType, Interval, ForeignKey, LargeBinary)
-from sqlalchemy import event
-from sqlalchemy.orm import relationship, sessionmaker
+from sqlalchemy import (
+ Boolean, Column, DateTime, Float, ForeignKey, Integer, Interval,
+ LargeBinary, PickleType, Unicode)
+from sqlalchemy.event import listen
+from sqlalchemy.orm import relationship
from urlparse import urljoin
from zope.component import getUtility
from zope.event import notify
@@ -38,6 +39,7 @@ from zope.interface import implementer
from mailman.config import config
from mailman.database.model import Model
+from mailman.database.transaction import dbconnection
from mailman.database.types import Enum
from mailman.interfaces.action import Action, FilterAction
from mailman.interfaces.address import IAddress
@@ -68,7 +70,6 @@ from mailman.utilities.string import expand
SPACE = ' '
UNDERSCORE = '_'
-Session = sessionmaker()
@implementer(IMailingList)
@@ -100,9 +101,6 @@ class MailingList(Model):
digest_last_sent_at = Column(DateTime)
volume = Column(Integer)
last_post_at = Column(DateTime)
- # Implicit destination.
- # acceptable_aliases_id = Column(Integer, ForeignKey('acceptablealias.id'))
- # acceptable_alias = relationship('AcceptableAlias', backref='mailing_list')
# Attributes which are directly modifiable via the web u/i. The more
# complicated attributes are currently stored as pickles, though that
# will change as the schema and implementation is developed.
@@ -110,17 +108,17 @@ class MailingList(Model):
admin_immed_notify = Column(Boolean)
admin_notify_mchanges = Column(Boolean)
administrivia = Column(Boolean)
- archive_policy = Column(Enum(enum=ArchivePolicy))
+ archive_policy = Column(Enum(ArchivePolicy))
# Automatic responses.
autoresponse_grace_period = Column(Interval)
- autorespond_owner = Column(Enum(enum=ResponseAction))
+ autorespond_owner = Column(Enum(ResponseAction))
autoresponse_owner_text = Column(Unicode)
- autorespond_postings = Column(Enum(enum=ResponseAction))
+ autorespond_postings = Column(Enum(ResponseAction))
autoresponse_postings_text = Column(Unicode)
- autorespond_requests = Column(Enum(enum=ResponseAction))
+ autorespond_requests = Column(Enum(ResponseAction))
autoresponse_request_text = Column(Unicode)
# Content filters.
- filter_action = Column(Enum(enum=FilterAction))
+ filter_action = Column(Enum(FilterAction))
filter_content = Column(Boolean)
collapse_alternatives = Column(Boolean)
convert_html_to_plaintext = Column(Boolean)
@@ -132,18 +130,19 @@ class MailingList(Model):
bounce_score_threshold = Column(Integer) # XXX
bounce_you_are_disabled_warnings = Column(Integer) # XXX
bounce_you_are_disabled_warnings_interval = Column(Interval) # XXX
- forward_unrecognized_bounces_to = Column(Enum(enum=UnrecognizedBounceDisposition))
+ forward_unrecognized_bounces_to = Column(
+ Enum(UnrecognizedBounceDisposition))
process_bounces = Column(Boolean)
# Miscellaneous
- default_member_action = Column(Enum(enum=Action))
- default_nonmember_action = Column(Enum(enum=Action))
+ default_member_action = Column(Enum(Action))
+ default_nonmember_action = Column(Enum(Action))
description = Column(Unicode)
digest_footer_uri = Column(Unicode)
digest_header_uri = Column(Unicode)
digest_is_default = Column(Boolean)
digest_send_periodic = Column(Boolean)
digest_size_threshold = Column(Float)
- digest_volume_frequency = Column(Enum(enum=DigestFrequency))
+ digest_volume_frequency = Column(Enum(DigestFrequency))
digestable = Column(Boolean)
discard_these_nonmembers = Column(PickleType)
emergency = Column(Boolean)
@@ -166,21 +165,21 @@ class MailingList(Model):
mime_is_default_digest = Column(Boolean)
# FIXME: There should be no moderator_password
moderator_password = Column(LargeBinary) # TODO : was RawStr()
- newsgroup_moderation = Column(Enum(enum=NewsgroupModeration))
+ newsgroup_moderation = Column(Enum(NewsgroupModeration))
nntp_prefix_subject_too = Column(Boolean)
nondigestable = Column(Boolean)
nonmember_rejection_notice = Column(Unicode)
obscure_addresses = Column(Boolean)
owner_chain = Column(Unicode)
owner_pipeline = Column(Unicode)
- personalize = Column(Enum(enum=Personalization))
+ personalize = Column(Enum(Personalization))
post_id = Column(Integer)
posting_chain = Column(Unicode)
posting_pipeline = Column(Unicode)
_preferred_language = Column('preferred_language', Unicode)
display_name = Column(Unicode)
reject_these_nonmembers = Column(PickleType)
- reply_goes_to_list = Column(Enum(enum=ReplyToMunging))
+ reply_goes_to_list = Column(Enum(ReplyToMunging))
reply_to_address = Column(Unicode)
require_explicit_destination = Column(Boolean)
respond_to_post_requests = Column(Boolean)
@@ -194,6 +193,7 @@ class MailingList(Model):
welcome_message_uri = Column(Unicode)
def __init__(self, fqdn_listname):
+ super(MailingList, self).__init__()
listname, at, hostname = fqdn_listname.partition('@')
assert hostname, 'Bad list name: {0}'.format(fqdn_listname)
self.list_name = listname
@@ -201,15 +201,15 @@ class MailingList(Model):
self._list_id = '{0}.{1}'.format(listname, hostname)
# For the pending database
self.next_request_id = 1
- # We need to set up the rosters. Normally, this method will get
- # called when the MailingList object is loaded from the database, but
- # that's not the case when the constructor is called. So, set up the
- # rosters explicitly.
+ # We need to set up the rosters. Normally, this method will get called
+ # when the MailingList object is loaded from the database, but when the
+ # constructor is called, SQLAlchemy's `load` event isn't triggered.
+ # Thus we need to set up the rosters explicitly.
self._post_load()
makedirs(self.data_path)
-
def _post_load(self, *args):
+ # This hooks up to SQLAlchemy's `load` event.
self.owners = roster.OwnerRoster(self)
self.moderators = roster.ModeratorRoster(self)
self.administrators = roster.AdministratorRoster(self)
@@ -221,7 +221,10 @@ class MailingList(Model):
@classmethod
def __declare_last__(cls):
- event.listen(cls, 'load', cls._post_load)
+ # SQLAlchemy special directive hook called after mappings are assumed
+ # to be complete. Use this to connect the roster instance creation
+ # method with the SA `load` event.
+ listen(cls, 'load', cls._post_load)
def __repr__(self):
return '<mailing list "{0}" at {1:#x}>'.format(
@@ -331,15 +334,17 @@ class MailingList(Model):
except AttributeError:
self._preferred_language = language
- def send_one_last_digest_to(self, address, delivery_mode):
+ @dbconnection
+ def send_one_last_digest_to(self, store, address, delivery_mode):
"""See `IMailingList`."""
digest = OneLastDigest(self, address, delivery_mode)
- Session.object_session(self).add(digest)
+ store.add(digest)
@property
- def last_digest_recipients(self):
+ @dbconnection
+ def last_digest_recipients(self, store):
"""See `IMailingList`."""
- results = Session.object_session(self).query(OneLastDigest).filter(
+ results = store.query(OneLastDigest).filter(
OneLastDigest.mailing_list == self)
recipients = [(digest.address, digest.delivery_mode)
for digest in results]
@@ -347,19 +352,20 @@ class MailingList(Model):
return recipients
@property
- def filter_types(self):
+ @dbconnection
+ def filter_types(self, store):
"""See `IMailingList`."""
- results = Session.object_session(self).query(ContentFilter).filter(
+ results = store.query(ContentFilter).filter(
ContentFilter.mailing_list == self,
ContentFilter.filter_type == FilterType.filter_mime)
for content_filter in results:
yield content_filter.filter_pattern
@filter_types.setter
- def filter_types(self, sequence):
+ @dbconnection
+ def filter_types(self, store, sequence):
"""See `IMailingList`."""
# First, delete all existing MIME type filter patterns.
- store = Session.object_session(self)
results = store.query(ContentFilter).filter(
ContentFilter.mailing_list == self,
ContentFilter.filter_type == FilterType.filter_mime)
@@ -371,19 +377,20 @@ class MailingList(Model):
store.add(content_filter)
@property
- def pass_types(self):
+ @dbconnection
+ def pass_types(self, store):
"""See `IMailingList`."""
- results = Session.object_session(self).query(ContentFilter).filter(
+ results = store.query(ContentFilter).filter(
ContentFilter.mailing_list == self,
ContentFilter.filter_type == FilterType.pass_mime)
for content_filter in results:
yield content_filter.filter_pattern
@pass_types.setter
- def pass_types(self, sequence):
+ @dbconnection
+ def pass_types(self, store, sequence):
"""See `IMailingList`."""
# First, delete all existing MIME type pass patterns.
- store = Session.object_session(self)
results = store.query(ContentFilter).filter(
ContentFilter.mailing_list == self,
ContentFilter.filter_type == FilterType.pass_mime)
@@ -395,19 +402,20 @@ class MailingList(Model):
store.add(content_filter)
@property
- def filter_extensions(self):
+ @dbconnection
+ def filter_extensions(self, store):
"""See `IMailingList`."""
- results = Session.object_session(self).query(ContentFilter).filter(
+ results = store.query(ContentFilter).filter(
ContentFilter.mailing_list == self,
ContentFilter.filter_type == FilterType.filter_extension)
for content_filter in results:
yield content_filter.filter_pattern
@filter_extensions.setter
- def filter_extensions(self, sequence):
+ @dbconnection
+ def filter_extensions(self, store, sequence):
"""See `IMailingList`."""
# First, delete all existing file extensions filter patterns.
- store = Session.object_session(self)
results = store.query(ContentFilter).filter(
ContentFilter.mailing_list == self,
ContentFilter.filter_type == FilterType.filter_extension)
@@ -419,19 +427,20 @@ class MailingList(Model):
store.add(content_filter)
@property
- def pass_extensions(self):
+ @dbconnection
+ def pass_extensions(self, store):
"""See `IMailingList`."""
- results = Session.object_session(self).query(ContentFilter).filter(
+ results = store.query(ContentFilter).filter(
ContentFilter.mailing_list == self,
ContentFilter.filter_type == FilterType.pass_extension)
for content_filter in results:
yield content_filter.pass_pattern
@pass_extensions.setter
- def pass_extensions(self, sequence):
+ @dbconnection
+ def pass_extensions(self, store, sequence):
"""See `IMailingList`."""
# First, delete all existing file extensions pass patterns.
- store = Session.object_session(self)
results = store.query(ContentFilter).filter(
ContentFilter.mailing_list == self,
ContentFilter.filter_type == FilterType.pass_extension)
@@ -454,9 +463,9 @@ class MailingList(Model):
raise TypeError(
'Undefined MemberRole: {0}'.format(role))
- def subscribe(self, subscriber, role=MemberRole.member):
+ @dbconnection
+ def subscribe(self, store, subscriber, role=MemberRole.member):
"""See `IMailingList`."""
- store = Session.object_session(self)
if IAddress.providedBy(subscriber):
member = store.query(Member).filter(
Member.role == role,
@@ -512,29 +521,30 @@ class AcceptableAliasSet:
def __init__(self, mailing_list):
self._mailing_list = mailing_list
- def clear(self):
+ @dbconnection
+ def clear(self, store):
"""See `IAcceptableAliasSet`."""
- Session.object_session(self._mailing_list).query(
- AcceptableAlias).filter(
- AcceptableAlias.mailing_list == self._mailing_list).delete()
+ store.query(AcceptableAlias).filter(
+ AcceptableAlias.mailing_list == self._mailing_list).delete()
- def add(self, alias):
+ @dbconnection
+ def add(self, store, alias):
if not (alias.startswith('^') or '@' in alias):
raise ValueError(alias)
alias = AcceptableAlias(self._mailing_list, alias.lower())
- Session.object_session(self._mailing_list).add(alias)
+ store.add(alias)
- def remove(self, alias):
- Session.object_session(self._mailing_list).query(
- AcceptableAlias).filter(
- AcceptableAlias.mailing_list == self._mailing_list,
- AcceptableAlias.alias == alias.lower()).delete()
+ @dbconnection
+ def remove(self, store, alias):
+ store.query(AcceptableAlias).filter(
+ AcceptableAlias.mailing_list == self._mailing_list,
+ AcceptableAlias.alias == alias.lower()).delete()
@property
- def aliases(self):
- aliases = Session.object_session(self._mailing_list).query(
- AcceptableAlias).filter(
- AcceptableAlias.mailing_list_id == self._mailing_list.id)
+ @dbconnection
+ def aliases(self, store):
+ aliases = store.query(AcceptableAlias).filter(
+ AcceptableAlias.mailing_list_id == self._mailing_list.id)
for alias in aliases:
yield alias.alias
@@ -576,14 +586,14 @@ class ListArchiver(Model):
@implementer(IListArchiverSet)
class ListArchiverSet:
- def __init__(self, mailing_list):
+ @dbconnection
+ def __init__(self, store, mailing_list):
self._mailing_list = mailing_list
system_archivers = {}
for archiver in config.archivers:
system_archivers[archiver.name] = archiver
# Add any system enabled archivers which aren't already associated
# with the mailing list.
- store = Session.object_session(self._mailing_list)
for archiver_name in system_archivers:
exists = store.query(ListArchiver).filter(
ListArchiver.mailing_list == mailing_list,
@@ -593,14 +603,15 @@ class ListArchiverSet:
system_archivers[archiver_name]))
@property
- def archivers(self):
- entries = Session.object_session(self._mailing_list).query(
- ListArchiver).filter(ListArchiver.mailing_list == self._mailing_list)
+ @dbconnection
+ def archivers(self, store):
+ entries = store.query(ListArchiver).filter(
+ ListArchiver.mailing_list == self._mailing_list)
for entry in entries:
yield entry
- def get(self, archiver_name):
- return Session.object_session(self._mailing_list).query(
- ListArchiver).filter(
- ListArchiver.mailing_list == self._mailing_list,
- ListArchiver.name == archiver_name).first()
+ @dbconnection
+ def get(self, store, archiver_name):
+ return store.query(ListArchiver).filter(
+ ListArchiver.mailing_list == self._mailing_list,
+ ListArchiver.name == archiver_name).first()
diff --git a/src/mailman/model/member.py b/src/mailman/model/member.py
index f1007c311..9da9d5d0d 100644
--- a/src/mailman/model/member.py
+++ b/src/mailman/model/member.py
@@ -24,7 +24,7 @@ __all__ = [
'Member',
]
-from sqlalchemy import Integer, Unicode, ForeignKey, Column
+from sqlalchemy import Column, ForeignKey, Integer, Unicode
from sqlalchemy.orm import relationship
from zope.component import getUtility
from zope.event import notify
@@ -56,9 +56,9 @@ class Member(Model):
id = Column(Integer, primary_key=True)
_member_id = Column(UUID)
- role = Column(Enum(enum=MemberRole))
+ role = Column(Enum(MemberRole))
list_id = Column(Unicode)
- moderation_action = Column(Enum(enum=Action))
+ moderation_action = Column(Enum(Action))
address_id = Column(Integer, ForeignKey('address.id'))
_address = relationship('Address')
diff --git a/src/mailman/model/message.py b/src/mailman/model/message.py
index 39f33aa89..74a76ac30 100644
--- a/src/mailman/model/message.py
+++ b/src/mailman/model/message.py
@@ -24,7 +24,7 @@ __all__ = [
'Message',
]
-from sqlalchemy import Column, Integer, Unicode, LargeBinary
+from sqlalchemy import Column, Integer, LargeBinary, Unicode
from zope.interface import implementer
from mailman.database.model import Model
@@ -47,6 +47,7 @@ class Message(Model):
@dbconnection
def __init__(self, store, message_id, message_id_hash, path):
+ super(Message, self).__init__()
self.message_id = message_id
self.message_id_hash = message_id_hash
self.path = path
diff --git a/src/mailman/model/messagestore.py b/src/mailman/model/messagestore.py
index f9f224dd6..0b8a0ac78 100644
--- a/src/mailman/model/messagestore.py
+++ b/src/mailman/model/messagestore.py
@@ -54,12 +54,13 @@ class MessageStore:
def add(self, store, message):
# Ensure that the message has the requisite headers.
message_ids = message.get_all('message-id', [])
- if len(message_ids) <> 1:
+ if len(message_ids) != 1:
raise ValueError('Exactly one Message-ID header required')
# Calculate and insert the X-Message-ID-Hash.
message_id = message_ids[0]
# Complain if the Message-ID already exists in the storage.
- existing = store.query(Message).filter(Message.message_id == message_id).first()
+ existing = store.query(Message).filter(
+ Message.message_id == message_id).first()
if existing is not None:
raise ValueError(
'Message ID already exists in message store: {0}'.format(
@@ -80,9 +81,9 @@ class MessageStore:
# providing a unique serial number, but to get this information, we
# have to use a straight insert instead of relying on Elixir to create
# the object.
- row = Message(message_id=message_id,
- message_id_hash=hash32,
- path=relpath)
+ Message(message_id=message_id,
+ message_id_hash=hash32,
+ path=relpath)
# Now calculate the full file system path.
path = os.path.join(config.MESSAGES_DIR, relpath)
# Write the file to the path, but catch the appropriate exception in
@@ -95,7 +96,7 @@ class MessageStore:
pickle.dump(message, fp, -1)
break
except IOError as error:
- if error.errno <> errno.ENOENT:
+ if error.errno != errno.ENOENT:
raise
makedirs(os.path.dirname(path))
return hash32
@@ -120,7 +121,8 @@ class MessageStore:
# US-ASCII.
if isinstance(message_id_hash, unicode):
message_id_hash = message_id_hash.encode('ascii')
- row = store.query(Message).filter_by(message_id_hash=message_id_hash).first()
+ row = store.query(Message).filter_by(
+ message_id_hash=message_id_hash).first()
if row is None:
return None
return self._get_message(row)
diff --git a/src/mailman/model/mime.py b/src/mailman/model/mime.py
index 3eac4f07b..906af91ea 100644
--- a/src/mailman/model/mime.py
+++ b/src/mailman/model/mime.py
@@ -25,7 +25,7 @@ __all__ = [
]
-from sqlalchemy import Column, Integer, Unicode, ForeignKey
+from sqlalchemy import Column, ForeignKey, Integer, Unicode
from sqlalchemy.orm import relationship
from zope.interface import implementer
@@ -46,7 +46,7 @@ class ContentFilter(Model):
mailing_list_id = Column(Integer, ForeignKey('mailinglist.id'))
mailing_list = relationship('MailingList')
- filter_type = Column(Enum(enum=FilterType))
+ filter_type = Column(Enum(FilterType))
filter_pattern = Column(Unicode)
def __init__(self, mailing_list, filter_pattern, filter_type):
diff --git a/src/mailman/model/pending.py b/src/mailman/model/pending.py
index 97d394721..a06a660b2 100644
--- a/src/mailman/model/pending.py
+++ b/src/mailman/model/pending.py
@@ -32,7 +32,7 @@ import hashlib
from lazr.config import as_timedelta
from sqlalchemy import (
- Column, Integer, Unicode, ForeignKey, DateTime, LargeBinary)
+ Column, DateTime, ForeignKey, Integer, LargeBinary, Unicode)
from sqlalchemy.orm import relationship
from zope.interface import implementer
from zope.interface.verify import verifyObject
@@ -71,6 +71,7 @@ class Pended(Model):
__tablename__ = 'pended'
def __init__(self, token, expiration_date):
+ super(Pended, self).__init__()
self.token = token
self.expiration_date = expiration_date
@@ -79,6 +80,7 @@ class Pended(Model):
expiration_date = Column(DateTime)
key_values = relationship('PendedKeyValue')
+
@implementer(IPendable)
class UnpendedPendable(dict):
@@ -119,9 +121,9 @@ class Pendings:
expiration_date=now() + lifetime)
for key, value in pendable.items():
if isinstance(key, str):
- key = unicode(key, 'utf-8')
+ key = key.encode('utf-8')
if isinstance(value, str):
- value = unicode(value, 'utf-8')
+ value = value.encode('utf-8')
elif type(value) is int:
value = '__builtin__.int\1%s' % value
elif type(value) is float:
@@ -150,8 +152,9 @@ class Pendings:
pendable = UnpendedPendable()
# Find all PendedKeyValue entries that are associated with the pending
# object's ID. Watch out for type conversions.
- for keyvalue in store.query(PendedKeyValue).filter(
- PendedKeyValue.pended_id == pending.id):
+ entries = store.query(PendedKeyValue).filter(
+ PendedKeyValue.pended_id == pending.id)
+ for keyvalue in entries:
if keyvalue.value is not None and '\1' in keyvalue.value:
type_name, value = keyvalue.value.split('\1', 1)
pendable[keyvalue.key] = call_name(type_name, value)
@@ -171,7 +174,7 @@ class Pendings:
# Find all PendedKeyValue entries that are associated with the
# pending object's ID.
q = store.query(PendedKeyValue).filter(
- PendedKeyValue.pended_id == pending.id)
+ PendedKeyValue.pended_id == pending.id)
for keyvalue in q:
store.delete(keyvalue)
store.delete(pending)
diff --git a/src/mailman/model/preferences.py b/src/mailman/model/preferences.py
index d74b17e30..1278f80b7 100644
--- a/src/mailman/model/preferences.py
+++ b/src/mailman/model/preferences.py
@@ -25,7 +25,7 @@ __all__ = [
]
-from sqlalchemy import Column, Integer, Unicode, Boolean
+from sqlalchemy import Boolean, Column, Integer, Unicode
from zope.component import getUtility
from zope.interface import implementer
@@ -49,8 +49,8 @@ class Preferences(Model):
_preferred_language = Column('preferred_language', Unicode)
receive_list_copy = Column(Boolean)
receive_own_postings = Column(Boolean)
- delivery_mode = Column(Enum(enum=DeliveryMode))
- delivery_status = Column(Enum(enum=DeliveryStatus))
+ delivery_mode = Column(Enum(DeliveryMode))
+ delivery_status = Column(Enum(DeliveryStatus))
def __repr__(self):
return '<Preferences object at {0:#x}>'.format(id(self))
diff --git a/src/mailman/model/requests.py b/src/mailman/model/requests.py
index 3d15ddea9..335e1e002 100644
--- a/src/mailman/model/requests.py
+++ b/src/mailman/model/requests.py
@@ -26,7 +26,7 @@ __all__ = [
from cPickle import dumps, loads
from datetime import timedelta
-from sqlalchemy import Column, Unicode, Integer, ForeignKey, LargeBinary
+from sqlalchemy import Column, ForeignKey, Integer, LargeBinary, Unicode
from sqlalchemy.orm import relationship
from zope.component import getUtility
from zope.interface import implementer
@@ -69,7 +69,8 @@ class ListRequests:
@property
@dbconnection
def count(self, store):
- return store.query(_Request).filter_by(mailing_list=self.mailing_list).count()
+ return store.query(_Request).filter_by(
+ mailing_list=self.mailing_list).count()
@dbconnection
def count_of(self, store, request_type):
@@ -79,7 +80,8 @@ class ListRequests:
@property
@dbconnection
def held_requests(self, store):
- results = store.query(_Request).filter_by(mailing_list=self.mailing_list)
+ results = store.query(_Request).filter_by(
+ mailing_list=self.mailing_list)
for request in results:
yield request
@@ -148,13 +150,14 @@ class _Request(Model):
id = Column(Integer, primary_key=True)# TODO: ???, default=AutoReload)
key = Column(Unicode)
- request_type = Column(Enum(enum=RequestType))
+ request_type = Column(Enum(RequestType))
data_hash = Column(LargeBinary)
mailing_list_id = Column(Integer, ForeignKey('mailinglist.id'))
mailing_list = relationship('MailingList')
def __init__(self, key, request_type, mailing_list, data_hash):
+ super(_Request, self).__init__()
self.key = key
self.request_type = request_type
self.mailing_list = mailing_list
diff --git a/src/mailman/model/roster.py b/src/mailman/model/roster.py
index a9a396523..a6cbeb104 100644
--- a/src/mailman/model/roster.py
+++ b/src/mailman/model/roster.py
@@ -161,7 +161,7 @@ class AdministratorRoster(AbstractRoster):
return store.query(Member).filter(
Member.list_id == self._mlist.list_id,
or_(Member.role == MemberRole.owner,
- Member.role == MemberRole.moderator))
+ Member.role == MemberRole.moderator))
@dbconnection
def get_member(self, store, address):
@@ -169,7 +169,7 @@ class AdministratorRoster(AbstractRoster):
results = store.query(Member).filter(
Member.list_id == self._mlist.list_id,
or_(Member.role == MemberRole.moderator,
- Member.role == MemberRole.owner),
+ Member.role == MemberRole.owner),
Address.email == address,
Member.address_id == Address.id)
if results.count() == 0:
diff --git a/src/mailman/model/tests/test_listmanager.py b/src/mailman/model/tests/test_listmanager.py
index 3951e8250..b290138f3 100644
--- a/src/mailman/model/tests/test_listmanager.py
+++ b/src/mailman/model/tests/test_listmanager.py
@@ -29,11 +29,11 @@ __all__ = [
import unittest
-from sqlalchemy.orm import sessionmaker
from zope.component import getUtility
from mailman.app.lifecycle import create_list
from mailman.app.moderator import hold_message
+from mailman.config import config
from mailman.interfaces.listmanager import (
IListManager, ListCreatedEvent, ListCreatingEvent, ListDeletedEvent,
ListDeletingEvent)
@@ -148,9 +148,8 @@ Message-ID: <argon>
for name in filter_names:
setattr(self._ant, name, ['test-filter-1', 'test-filter-2'])
getUtility(IListManager).delete(self._ant)
- Session = sessionmaker()
- store = Session.object_session(self._ant)
- filters = store.query(ContentFilter).filter_by(mailing_list = self._ant)
+ filters = config.db.store.query(ContentFilter).filter_by(
+ mailing_list = self._ant)
self.assertEqual(filters.count(), 0)
diff --git a/src/mailman/model/uid.py b/src/mailman/model/uid.py
index 77f1b59bb..29d8e7021 100644
--- a/src/mailman/model/uid.py
+++ b/src/mailman/model/uid.py
@@ -29,8 +29,8 @@ __all__ = [
from sqlalchemy import Column, Integer
from mailman.database.model import Model
-from mailman.database.types import UUID
from mailman.database.transaction import dbconnection
+from mailman.database.types import UUID
@@ -54,6 +54,7 @@ class UID(Model):
@dbconnection
def __init__(self, store, uid):
+ super(UID, self).__init__()
self.uid = uid
store.add(self)
diff --git a/src/mailman/model/user.py b/src/mailman/model/user.py
index cd47a5dac..576015dbe 100644
--- a/src/mailman/model/user.py
+++ b/src/mailman/model/user.py
@@ -25,7 +25,7 @@ __all__ = [
]
from sqlalchemy import (
- Column, Unicode, Integer, DateTime, ForeignKey, LargeBinary)
+ Column, DateTime, ForeignKey, Integer, LargeBinary, Unicode)
from sqlalchemy.orm import relationship, backref
from zope.event import notify
from zope.interface import implementer
@@ -60,25 +60,24 @@ class User(Model):
_user_id = Column(UUID)
_created_on = Column(DateTime)
- addresses = relationship('Address',
- backref='user',
- primaryjoin=
- id==Address.user_id)
+ addresses = relationship(
+ 'Address', backref='user',
+ primaryjoin=(id==Address.user_id))
- _preferred_address_id = Column(Integer, ForeignKey('address.id',
- use_alter=True,
- name='_preferred_address'))
- _preferred_address = relationship('Address',
- primaryjoin=
- _preferred_address_id==Address.id,
- post_update=True)
+ _preferred_address_id = Column(
+ Integer,
+ ForeignKey('address.id', use_alter=True, name='_preferred_address'))
+ _preferred_address = relationship(
+ 'Address', primaryjoin=(_preferred_address_id==Address.id),
+ post_update=True)
preferences_id = Column(Integer, ForeignKey('preferences.id'))
- preferences = relationship('Preferences',
- backref=backref('user', uselist=False))
+ preferences = relationship(
+ 'Preferences', backref=backref('user', uselist=False))
@dbconnection
def __init__(self, store, display_name=None, preferences=None):
+ super(User, self).__init__()
self._created_on = date_factory.now()
user_id = uid_factory.new_uid()
assert store.query(User).filter_by(_user_id=user_id).count() == 0, (
diff --git a/src/mailman/model/version.py b/src/mailman/model/version.py
index 95cf03dac..ecef6ed8a 100644
--- a/src/mailman/model/version.py
+++ b/src/mailman/model/version.py
@@ -24,9 +24,9 @@ __all__ = [
'Version',
]
-from sqlalchemy import Column, Unicode, Integer
from mailman.database.model import Model
+from sqlalchemy import Column, Integer, Unicode
@@ -43,5 +43,6 @@ class Version(Model):
PRESERVE = True
def __init__(self, component, version):
+ super(Version, self).__init__()
self.component = component
self.version = version
diff --git a/src/mailman/utilities/importer.py b/src/mailman/utilities/importer.py
index 9856a8223..26b7261e3 100644
--- a/src/mailman/utilities/importer.py
+++ b/src/mailman/utilities/importer.py
@@ -186,7 +186,6 @@ NAME_MAPPINGS = dict(
filter_mime_types='filter_types',
generic_nonmember_action='default_nonmember_action',
include_list_post_header='allow_list_posts',
- last_post_time='last_post_at',
member_moderation_action='default_member_action',
mod_password='moderator_password',
news_moderation='newsgroup_moderation',
@@ -198,13 +197,13 @@ NAME_MAPPINGS = dict(
send_welcome_msg='send_welcome_message',
)
-# Datetime Fields that need a type conversion to python datetime
-# object for SQLite database.
-DATETIME_OBJECTS = [
+# These DateTime fields of the mailinglist table need a type conversion to
+# Python datetime object for SQLite databases.
+DATETIME_COLUMNS = [
'created_at',
'digest_last_sent_at',
'last_post_time',
-]
+ ]
EXCLUDES = set((
'digest_members',
@@ -225,8 +224,8 @@ def import_config_pck(mlist, config_dict):
# Some attributes must not be directly imported.
if key in EXCLUDES:
continue
- # Created at must not be set, it needs a type conversion
- if key in DATETIME_OBJECTS:
+ # These objects need explicit type conversions.
+ if key in DATETIME_COLUMNS:
continue
# Some attributes from Mailman 2 were renamed in Mailman 3.
key = NAME_MAPPINGS.get(key, key)
@@ -249,7 +248,7 @@ def import_config_pck(mlist, config_dict):
except (TypeError, KeyError):
print('Type conversion error for key "{}": {}'.format(
key, value), file=sys.stderr)
- for key in DATETIME_OBJECTS:
+ for key in DATETIME_COLUMNS:
try:
value = datetime.datetime.utcfromtimestamp(config_dict[key])
except KeyError: