[prev in list] [next in list] [prev in thread] [next in thread]
List: mailman-cvs
Subject: [Mailman-checkins] SF.net SVN: mailman: [8192] trunk/mailman/Mailman
From: bwarsaw () users ! sourceforge ! net
Date: 2007-04-17 21:37:50
Message-ID: E1HdvN0-0002Zg-B9 () sc8-pr-svn1 ! sourceforge ! net
[Download RAW message or body]
Revision: 8192
http://svn.sourceforge.net/mailman/?rev=8192&view=rev
Author: bwarsaw
Date: 2007-04-17 14:37:49 -0700 (Tue, 17 Apr 2007)
Log Message:
-----------
Another round of merges of my Pycon branch...
bin/testall.py
- Improvements to setting up the tests with temporary files for the db and
configuration. Don't accept -C/--config because that will just confuse
things.
- Copy our new testing.cfg.in template file to a temp file, populate it
with a few additional run-time calculate values, and use that in both
the parent (bin/testall script) and children (bin/mailmanctl and
friends).
Mailman/initialize.py
- Split up initialize() into two functions as required by the test suite
reorg above. Almost everything else in Mailman will continue to use the
initialize() function though.
Mailman/configuration.py
- Store the filename that was used to load the .cfg file from on the
configuration object.
Modified Paths:
--------------
trunk/mailman/Mailman/bin/testall.py
trunk/mailman/Mailman/configuration.py
trunk/mailman/Mailman/initialize.py
trunk/mailman/Mailman/testing/Makefile.in
trunk/mailman/Mailman/testing/base.py
trunk/mailman/Mailman/testing/emailbase.py
trunk/mailman/Mailman/testing/test_enum.py
Added Paths:
-----------
trunk/mailman/Mailman/testing/inmemory.py
trunk/mailman/Mailman/testing/testing.cfg.in
Modified: trunk/mailman/Mailman/bin/testall.py
===================================================================
--- trunk/mailman/Mailman/bin/testall.py 2007-04-12 18:39:57 UTC (rev 8191)
+++ trunk/mailman/Mailman/bin/testall.py 2007-04-17 21:37:49 UTC (rev 8192)
@@ -17,15 +17,26 @@
"""Mailman unit test driver."""
+from __future__ import with_statement
+
import os
import re
+import grp
+import pwd
import sys
+import shutil
import optparse
+import tempfile
import unittest
+import Mailman
+import Mailman.testing
+
from Mailman import Version
+from Mailman.configuration import config
+from Mailman.database.dbcontext import dbcontext
from Mailman.i18n import _
-from Mailman.initialize import initialize
+from Mailman.initialize import initialize_1, initialize_2
__i18n_templates__ = True
@@ -63,8 +74,6 @@
parser.add_option('-e', '--stderr',
default=False, action='store_true',
help=_('Propagate log errors to stderr.'))
- parser.add_option('-C', '--config',
- help=_('Alternative configuration file to use'))
opts, args = parser.parse_args()
return parser, opts, args
@@ -137,14 +146,48 @@
global basedir
parser, opts, args = parseargs()
- initialize(opts.config, propagate_logs=opts.stderr)
if not args:
args = ['.']
- import Mailman
- basedir = os.path.dirname(Mailman.__file__)
- runner = unittest.TextTestRunner(verbosity=opts.verbosity)
- results = runner.run(suite(args))
+ # Set up the testing configuration file both for this process, and for all
+ # sub-processes testing will spawn (e.g. the qrunners).
+ #
+ # Calculate various temporary files needed by the test suite, but only for
+ # those files which must also go into shared configuration file.
+ cfg_in = os.path.join(os.path.dirname(Mailman.testing.__file__),
+ 'testing.cfg.in')
+ fd, cfg_out = tempfile.mkstemp(suffix='.cfg')
+ os.close(fd)
+ shutil.copyfile(cfg_in, cfg_out)
+
+ initialize_1(cfg_out, propagate_logs=opts.stderr)
+ mailman_uid = pwd.getpwnam(config.MAILMAN_USER).pw_uid
+ mailman_gid = grp.getgrnam(config.MAILMAN_GROUP).gr_gid
+ os.chmod(cfg_out, 0660)
+ os.chown(cfg_out, mailman_uid, mailman_gid)
+
+ fd, config.dbfile = tempfile.mkstemp(dir=config.DATA_DIR, suffix='.db')
+ os.close(fd)
+ os.chmod(config.dbfile, 0660)
+ os.chown(config.dbfile, mailman_uid, mailman_gid)
+
+ # Patch ups
+ test_engine_url = 'sqlite:///' + config.dbfile
+ config.SQLALCHEMY_ENGINE_URL = test_engine_url
+
+ with open(cfg_out, 'a') as fp:
+ print >> fp, 'SQLALCHEMY_ENGINE_URL = "%s"' % test_engine_url
+
+ initialize_2()
+
+ try:
+ basedir = os.path.dirname(Mailman.__file__)
+ runner = unittest.TextTestRunner(verbosity=opts.verbosity)
+ results = runner.run(suite(args))
+ finally:
+ os.remove(cfg_out)
+ os.remove(config.dbfile)
+
sys.exit(bool(results.failures or results.errors))
Modified: trunk/mailman/Mailman/configuration.py
===================================================================
--- trunk/mailman/Mailman/configuration.py 2007-04-12 18:39:57 UTC (rev 8191)
+++ trunk/mailman/Mailman/configuration.py 2007-04-17 21:37:49 UTC (rev 8192)
@@ -72,12 +72,14 @@
path = os.path.abspath(os.path.expanduser(filename))
try:
execfile(path, ns, ns)
+ self.filename = path
except EnvironmentError, e:
if e.errno <> errno.ENOENT or original_filename:
raise
# The file didn't exist, so try mm_cfg.py
from Mailman import mm_cfg
ns.update(mm_cfg.__dict__)
+ self.filename = None
# Based on values possibly set in mailman.cfg, add additional qrunners
if ns['USE_MAILDIR']:
self.add_qrunner('Maildir')
Modified: trunk/mailman/Mailman/initialize.py
===================================================================
--- trunk/mailman/Mailman/initialize.py 2007-04-12 18:39:57 UTC (rev 8191)
+++ trunk/mailman/Mailman/initialize.py 2007-04-17 21:37:49 UTC (rev 8192)
@@ -32,7 +32,12 @@
-def initialize(config=None, propagate_logs=False):
+# These initialization calls are separated for the testing framework, which
+# needs to do some internal calculations after config file loading and log
+# initialization, but before database initialization. Generally all other
+# code will just call initialize().
+
+def initialize_1(config, propagate_logs):
# By default, set the umask so that only owner and group can read and
# write our files. Specifically we must have g+rw and we probably want
# o-rwx although I think in most cases it doesn't hurt if other can read
@@ -42,4 +47,12 @@
os.umask(007)
Mailman.configuration.config.load(config)
Mailman.loginit.initialize(propagate_logs)
+
+
+def initialize_2():
Mailman.database.initialize()
+
+
+def initialize(config=None, propagate_logs=False):
+ initialize_1(config, propagate_logs)
+ initialize_2()
Modified: trunk/mailman/Mailman/testing/Makefile.in
===================================================================
--- trunk/mailman/Mailman/testing/Makefile.in 2007-04-12 18:39:57 UTC (rev 8191)
+++ trunk/mailman/Mailman/testing/Makefile.in 2007-04-17 21:37:49 UTC (rev 8192)
@@ -42,6 +42,7 @@
SHELL= /bin/sh
MODULES= *.py
+OTHERFILES= testing.cfg.in
# Modes for directories and executables created by the install
# process. Default to group-writable directories but
@@ -59,7 +60,7 @@
all:
install:
- for f in $(MODULES); \
+ for f in $(MODULES) $(OTHERFILES); \
do \
$(INSTALL) -m $(FILEMODE) $(srcdir)/$$f $(DESTDIR)$(PACKAGEDIR); \
done
Modified: trunk/mailman/Mailman/testing/base.py
===================================================================
--- trunk/mailman/Mailman/testing/base.py 2007-04-12 18:39:57 UTC (rev 8191)
+++ trunk/mailman/Mailman/testing/base.py 2007-04-17 21:37:49 UTC (rev 8192)
@@ -41,26 +41,7 @@
-def dummy_mta_function(*args, **kws):
- pass
-
-
-
class TestBase(unittest.TestCase):
- def _configure(self, fp):
- # Make sure that we don't pollute the real database with our test
- # mailing list.
- test_engine_url = 'sqlite:///' + self._dbfile
- print >> fp, 'SQLALCHEMY_ENGINE_URL = "%s"' % test_engine_url
- config.SQLALCHEMY_ENGINE_URL = test_engine_url
- # Use the Mailman.MTA.stub module
- print >> fp, 'MTA = "stub"'
- config.MTA = 'stub'
- print >> fp, 'add_domain("example.com", "www.example.com")'
- # Only add this domain once to the current process
- if 'example.com' not in config.domains:
- config.add_domain('example.com', 'www.example.com')
-
def ndiffAssertEqual(self, first, second):
"""Like failUnlessEqual except use ndiff for readable output."""
if first <> second:
@@ -72,30 +53,6 @@
raise self.failureException(fp.getvalue())
def setUp(self):
- mailman_uid = pwd.getpwnam(config.MAILMAN_USER).pw_uid
- mailman_gid = grp.getgrnam(config.MAILMAN_GROUP).gr_gid
- # Write a temporary configuration file, but allow for subclasses to
- # add additional data. Make sure the config and db files, which
- # mkstemp creates, has the proper ownership and permissions.
- fd, self._config = tempfile.mkstemp(dir=config.DATA_DIR, suffix='.cfg')
- os.close(fd)
- os.chmod(self._config, 0440)
- os.chown(self._config, mailman_uid, mailman_gid)
- fd, self._dbfile = tempfile.mkstemp(dir=config.DATA_DIR, suffix='.db')
- os.close(fd)
- os.chmod(self._dbfile, 0660)
- os.chown(self._dbfile, mailman_uid, mailman_gid)
- fp = open(self._config, 'w')
- try:
- self._configure(fp)
- finally:
- fp.close()
- # Create a fake new Mailman.MTA module which stubs out the create()
- # and remove() functions.
- stubmta_module = new.module('Mailman.MTA.stub')
- sys.modules['Mailman.MTA.stub'] = stubmta_module
- stubmta_module.create = dummy_mta_function
- stubmta_module.remove = dummy_mta_function
# Be sure to close the connection to the current database, and then
# reconnect to the new temporary SQLite database. Otherwise we end up
# with turds in the main database and our qrunner subprocesses won't
@@ -116,8 +73,6 @@
self._mlist.Unlock()
rmlist.delete_list(self._mlist.fqdn_listname, self._mlist,
archives=True, quiet=True)
- os.unlink(self._config)
- os.unlink(self._dbfile)
# Clear out any site locks, which can be left over if tests fail.
for filename in os.listdir(config.LOCK_DIR):
if filename.startswith('<site>'):
Modified: trunk/mailman/Mailman/testing/emailbase.py
===================================================================
--- trunk/mailman/Mailman/testing/emailbase.py 2007-04-12 18:39:57 UTC (rev 8191)
+++ trunk/mailman/Mailman/testing/emailbase.py 2007-04-17 21:37:49 UTC (rev 8192)
@@ -52,15 +52,6 @@
class EmailBase(TestBase):
- def _configure(self, fp):
- TestBase._configure(self, fp)
- print >> fp, 'SMTPPORT =', TESTPORT
- config.SMTPPORT = TESTPORT
- # Don't go nuts on mailmanctl restarts. If a qrunner fails once, it
- # will keep failing.
- print >> fp, 'MAX_RESTARTS = 1'
- config.MAX_RESTARTS = 1
-
def setUp(self):
TestBase.setUp(self)
try:
@@ -70,7 +61,7 @@
TestBase.tearDown(self)
raise
try:
- os.system('bin/mailmanctl -C %s -q start' % self._config)
+ os.system('bin/mailmanctl -C %s -q start' % config.filename)
# If any errors occur in the above, be sure to manually call
# tearDown(). unittest doesn't call tearDown() for errors in
# setUp().
@@ -79,7 +70,7 @@
raise
def tearDown(self):
- os.system('bin/mailmanctl -C %s -q stop' % self._config)
+ os.system('bin/mailmanctl -C %s -q stop' % config.filename)
self._server.close()
# Wait a while until the server actually goes away
while True:
Added: trunk/mailman/Mailman/testing/inmemory.py
===================================================================
--- trunk/mailman/Mailman/testing/inmemory.py (rev 0)
+++ trunk/mailman/Mailman/testing/inmemory.py 2007-04-17 21:37:49 UTC (rev 8192)
@@ -0,0 +1,488 @@
+# Copyright (C) 2007 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.
+
+"""In-memory implementations of Mailman interfaces, for testing purposes."""
+
+import datetime
+import urlparse
+
+from Mailman import Utils
+from Mailman import passwords
+from Mailman.interfaces import *
+
+from zope.interface import implements
+
+
+
+class UserManager(object):
+ implements(IUserManager)
+
+ def __init__(self):
+ self._users = set()
+ self._next_id = 1
+
+ @property
+ def users(self):
+ for user in self._users:
+ yield user
+
+ def create_user(self):
+ user = User(self._next_id, self)
+ self._next_id += 1
+ self._users.add(user)
+ return user
+
+ def remove(self, user):
+ self._users.discard(user)
+
+ def get(self, address):
+ # Yes, this is slow and icky, but it's only for testing purposes
+ for user in self._users:
+ if user.controls(address):
+ return user
+ return None
+
+
+
+class User(object):
+ implements(IUser)
+
+ def __init__(self, user_id, user_mgr):
+ self._user_id = user_id
+ self._user_mgr = user_mgr
+ self._addresses = set()
+ self.real_name = u''
+ self.password = passwords.NoPasswordScheme.make_secret('ignore')
+ self.default_profile = None
+
+ def __eq__(self, other):
+ return (IUser.implementedBy(other) and
+ self.user_id == other.user_id and
+ self.user_manager is other.user_manager)
+
+ def __ne__(self, other):
+ return not self.__eq__(other)
+
+ @property
+ def user_id(self):
+ return self._user_id
+
+ @property
+ def user_manager(self):
+ return self._user_mgr
+
+ @property
+ def addresses(self):
+ for address in self._addresses:
+ yield address
+
+ def add_address(self, address):
+ if self.controls(address):
+ return
+ user_address = Address(address, self)
+ self._addresses.add(user_address)
+
+ def remove_address(self, address):
+ if not self.controls(address):
+ return
+ user_address = Address(address, self)
+ self._addresses.discard(user_address)
+
+ def controls(self, address):
+ for user_address in self.addresses:
+ if user_address == address:
+ return True
+ return False
+
+
+
+class Address(object):
+ implements(IAddress)
+
+ def __init__(self, email_address, user, profile=None):
+ self._address = email_address
+ self._user = user
+ self.profile = profile or Profile()
+ self.validated_on = None
+
+ def __eq__(self, other):
+ return (IAddress.implementedBy(other) and
+ self.address == other.address and
+ self.user == other.user)
+
+ @property
+ def address(self):
+ return self._address
+
+ @property
+ def user(self):
+ return self._user
+
+
+
+class RegularDelivery(object):
+ implements(IRegularDelivery)
+
+
+class PlainTextDigestDelivery(object):
+ implements(IPlainTextDigestDelivery)
+
+
+class MIMEDigestDelivery(object):
+ implements(IMIMEDigestDeliver)
+
+
+
+class DeliveryEnabled(object):
+ implements(IDeliveryStatus)
+
+ @property
+ def enabled(self):
+ return True
+
+
+class DeliveryDisabled(object):
+ implements(IDeliveryStatus)
+
+ @property
+ def enabled(self):
+ return False
+
+
+class DeliveryDisabledByUser(DeliveryDisabled):
+ implements(IDeliveryDisabledByUser)
+
+
+class DeliveryDisabledbyAdministrator(DeliveryDisabled):
+ implements(IDeliveryDisabledByAdministrator)
+
+ reason = u'Unknown'
+
+
+class DeliveryDisabledByBounces(DeliveryDisabled):
+ implements(IDeliveryDisabledByBounces)
+
+ bounce_info = 'XXX'
+
+
+class DeliveryTemporarilySuspended(object):
+ implements(IDeliveryTemporarilySuspended)
+
+ def __init__(self, start_date, end_date):
+ self.start_date = start_date
+ self.end_date = end_date
+
+ @property
+ def enabled(self):
+ now = datetime.datetime.now()
+ return not (self.start_date <= now < self.end_date)
+
+
+
+class OkayToPost(object):
+ implements(IPostingPermission)
+
+ # XXX
+ okay_to_post = True
+
+
+
+class Profile(object):
+ implements(IProfile)
+
+ # System defaults
+ acknowledge = False
+ hide = True
+ language = 'en'
+ list_copy = True
+ own_postings = True
+ delivery_mode = RegularDelivery()
+ delivery_status = DeliveryEnabled()
+ posting_permission = OkayToPost()
+
+
+
+class Roster(object):
+ implements(IRoster)
+
+ def __init__(self, name):
+ self._name = name
+ self._members = set()
+
+ def __eq__(self, other):
+ return (IRoster.implementedBy(other) and
+ self.name == other.name)
+
+ def __ne__(self, other):
+ return not self.__eq__(other)
+
+ @property
+ def name(self):
+ return self._name
+
+ def add(self, member):
+ self._members.add(member)
+
+ def remove(self, member):
+ self._members.remove(member)
+
+ @property
+ def members(self):
+ for member in self._members:
+ yield member
+
+
+
+class Member(object):
+ implements(IMember)
+
+ def __init__(self, address, roster, profile=None):
+ self._address = address
+ self._roster = roster
+ self.profile = profile or Profile()
+
+ @property
+ def address(self):
+ return self._address
+
+ @property
+ def roster(self):
+ return self._roster
+
+
+
+class ListManager(object):
+ implements(IListManager)
+
+ def __init__(self):
+ self._mlists = {}
+
+ def add(self, mlist):
+ self._mlists[mlist.fqdn_listname] = mlist
+
+ def remove(self, mlist):
+ del self._mlists[mlist.fqdn_listname]
+
+ @property
+ def mailing_lists(self):
+ return self._mlists.itervalues()
+
+ @property
+ def names(self):
+ return self._mlists.iterkeys()
+
+ def get(self, fqdn_listname):
+ return self._mlists.get(fqdn_listname)
+
+
+
+class MailingList(object):
+ implements(IMailingListIdentity,
+ IMailingListAddresses,
+ IMailingListURLs,
+ IMailingListRosters,
+ IMailingListStatistics,
+ )
+
+ def __init__(self, list_name, host_name, web_host):
+ self._listname = list_name
+ self._hostname = hostname
+ self._webhost = web_host
+ self._fqdn_listname = Utils.fqdn_listname(list_name, host_name)
+ # Rosters
+ self._owners = set(Roster(self.owner_address))
+ self._moderators = set(Roster(self._listname + '-moderators@' +
+ self._hostname))
+ self._members = set(Roster(self.posting_address))
+ # Statistics
+ self._created_on = datetime.datetime.now()
+ self._last_posting = None
+ self._post_number = 0
+ self._last_digest = None
+
+ # IMailingListIdentity
+
+ @property
+ def list_name(self):
+ return self._listname
+
+ @property
+ def host_name(self):
+ return self._hostname
+
+ @property
+ def fqdn_listname(self):
+ return self._fqdn_listname
+
+ # IMailingListAddresses
+
+ @property
+ def posting_address(self):
+ return self._fqdn_listname
+
+ @property
+ def noreply_address(self):
+ return self._listname + '-noreply@' + self._hostname
+
+ @property
+ def owner_address(self):
+ return self._listname + '-owner@' + self._hostname
+
+ @property
+ def request_address(self):
+ return self._listname + '-request@' + self._hostname
+
+ @property
+ def bounces_address(self):
+ return self._listname + '-bounces@' + self._hostname
+
+ @property
+ def confirm_address(self):
+ return self._listname + '-confirm@' + self._hostname
+
+ @property
+ def join_address(self):
+ return self._listname + '-join@' + self._hostname
+
+ @property
+ def leave_address(self):
+ return self._listname + '-leave@' + self._hostname
+
+ @property
+ def subscribe_address(self):
+ return self._listname + '-subscribe@' + self._hostname
+
+ @property
+ def unsubscribe_address(self):
+ return self._listname + '-unsubscribe@' + self._hostname
+
+ # IMailingListURLs
+
+ protocol = 'http'
+
+ @property
+ def web_host(self):
+ return self._webhost
+
+ def script_url(self, target, context=None):
+ if context is None:
+ return urlparse.urlunsplit((self.protocol, self.web_host, target,
+ # no extra query or fragment
+ '', ''))
+ return urlparse.urljoin(context.location, target)
+
+ # IMailingListRosters
+
+ @property
+ def owner_rosters(self):
+ return iter(self._owners)
+
+ @property
+ def moderator_rosters(self):
+ return iter(self._moderators)
+
+ @property
+ def member_rosters(self):
+ return iter(self._members)
+
+ def add_owner_roster(self, roster):
+ self._owners.add(roster)
+
+ def add_moderator_roster(self, roster):
+ self._moderators.add(roster)
+
+ def add_member_roster(self, roster):
+ self._members.add(roster)
+
+ def remove_owner_roster(self, roster):
+ self._owners.discard(roster)
+
+ def remove_moderator_roster(self, roster):
+ self._moderators.discard(roster)
+
+ def remove_member_roster(self, roster):
+ self._members.discard(roster)
+
+ @property
+ def owners(self):
+ for roster in self._owners:
+ for member in roster.members:
+ yield member
+
+ @property
+ def moderators(self):
+ for roster in self._moderators:
+ for member in roster.members:
+ yield member
+
+ @property
+ def administrators(self):
+ for member in self.owners:
+ yield member
+ for member in self.moderators:
+ yield member
+
+ @property
+ def members(self):
+ for roster in self._members:
+ for member in roster.members:
+ yield member
+
+ @property
+ def regular_members(self):
+ for member in self.members:
+ if IRegularDelivery.implementedBy(member.profile.delivery_mode):
+ yield member
+
+ @property
+ def digest_member(self):
+ for member in self.members:
+ if IDigestDelivery.implementedBy(member.profile.delivery_mode):
+ yield member
+
+ # Statistic
+
+ @property
+ def creation_date(self):
+ return self._created_on
+
+ @property
+ def last_post_date(self):
+ return self._last_posting
+
+ @property
+ def post_number(self):
+ return self._post_number
+
+ @property
+ def last_digest_date(self):
+ return self._last_digest
+
+
+
+class MailingListRequest(object):
+ implements(IMailingListRequest)
+
+ location = ''
+
+
+
+def initialize():
+ from Mailman.configuration import config
+ config.user_manager = UserManager()
+ config.list_manager = ListManager()
+ config.message_manager = None
Modified: trunk/mailman/Mailman/testing/test_enum.py
===================================================================
--- trunk/mailman/Mailman/testing/test_enum.py 2007-04-12 18:39:57 UTC (rev 8191)
+++ trunk/mailman/Mailman/testing/test_enum.py 2007-04-17 21:37:49 UTC (rev 8192)
@@ -93,9 +93,10 @@
eq(int(Colors.blue), 3)
eq(int(MoreColors.red), 1)
eq(int(OtherColors.blue), 2)
-
+
def test_enum_duplicates(self):
try:
+ # This is bad because kyle and kenny have the same integer value.
class Bad(Enum):
cartman = 1
stan = 2
Added: trunk/mailman/Mailman/testing/testing.cfg.in
===================================================================
--- trunk/mailman/Mailman/testing/testing.cfg.in (rev 0)
+++ trunk/mailman/Mailman/testing/testing.cfg.in 2007-04-17 21:37:49 UTC (rev 8192)
@@ -0,0 +1,14 @@
+# -*- python -*-
+
+# Configuration file template for the unit test suite. We need this because
+# both the process running the tests and all sub-processes (e.g. qrunners)
+# must share the same configuration file.
+
+MANAGERS_INIT_FUNCTION = 'Mailman.testing.inmemory.initialize'
+SMTPPORT = 10825
+MAX_RESTARTS = 1
+MTA = None
+
+add_domain('example.com', 'www.example.com')
+
+# bin/testall will add a SQLALCHEMY_ENGINE_URL below
This was sent by the SourceForge.net collaborative development platform, the world's \
largest Open Source development site. _______________________________________________
Mailman-checkins mailing list
Mailman-checkins@python.org
Unsubscribe: http://mail.python.org/mailman/options/mailman-checkins/mailman-cvs%40progressive-comp.com
[prev in list] [next in list] [prev in thread] [next in thread]
Configure |
About |
News |
Add a list |
Sponsored by KoreLogic