Commit 3f7bfa1e authored by Tres Seaver's avatar Tres Seaver

Support Python 3.2 / 3.3, and test them w/ tox.

parent 4c700b17
...@@ -4,6 +4,8 @@ ...@@ -4,6 +4,8 @@
Unreleased Unreleased
---------- ----------
- Added support for Python 3.2 / 3.3.
- Added ``setup.py docs`` alias (runs ``setup.py develop`` and installs - Added ``setup.py docs`` alias (runs ``setup.py develop`` and installs
documentation dependencies). documentation dependencies).
......
...@@ -29,8 +29,12 @@ setup(name='zodburi', ...@@ -29,8 +29,12 @@ setup(name='zodburi',
classifiers=[ classifiers=[
"Intended Audience :: Developers", "Intended Audience :: Developers",
"Programming Language :: Python", "Programming Language :: Python",
"Programming Language :: Python :: 2",
"Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.6",
"Programming Language :: Python :: 2.7", "Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.2",
"Programming Language :: Python :: 3.3",
"License :: Repoze Public License", "License :: Repoze Public License",
], ],
keywords='zodb zodbconn', keywords='zodb zodbconn',
......
[tox] [tox]
envlist = envlist =
py26,py27,cover,docs py26,py27,py32,py33,cover,docs
[testenv] [testenv]
commands = commands =
python setup.py test -q python setup.py test -q
[py3]
deps =
# Py3k compatible ZODB not yet released
git+https://github.com/zopefoundation/ZODB.git@py3#egg=ZODB
git+https://github.com/zopefoundation/ZEO.git@py3#egg=ZEO
[testenv:py32]
commands =
python setup.py test -q
deps = {[py3]deps}
[testenv:py33]
commands =
python setup.py test -q
deps = {[py3]deps}
[testenv:cover] [testenv:cover]
basepython = basepython =
python2.6 python2.6
......
try:
from urllib.parse import parse_qsl
except ImportError: #pragma NO COVER
from cgi import parse_qsl
try:
from urllib.parse import quote
except ImportError: #pragma NO COVER
from urllib import quote
try:
from urllib.parse import urlsplit
except ImportError: #pragma NO COVER
from urlparse import urlsplit
import cgi from io import BytesIO
from cStringIO import StringIO
import os import os
import sys import sys
import urlparse
from ZODB.config import ZODBDatabase from ZConfig import loadConfig
from ZConfig import loadSchemaFile
from ZEO.ClientStorage import ClientStorage from ZEO.ClientStorage import ClientStorage
from ZODB.FileStorage.FileStorage import FileStorage from ZODB.blob import BlobStorage
from ZODB.config import ZODBDatabase
from ZODB.DemoStorage import DemoStorage from ZODB.DemoStorage import DemoStorage
from ZODB.FileStorage.FileStorage import FileStorage
from ZODB.MappingStorage import MappingStorage from ZODB.MappingStorage import MappingStorage
from ZODB.blob import BlobStorage
import ZConfig
from zodburi.datatypes import convert_bytesize from zodburi.datatypes import convert_bytesize
from zodburi.datatypes import convert_int from zodburi.datatypes import convert_int
from zodburi.datatypes import convert_tuple from zodburi.datatypes import convert_tuple
from zodburi._compat import parse_qsl
from zodburi._compat import urlsplit
class Resolver(object): class Resolver(object):
...@@ -55,7 +56,7 @@ class MappingStorageURIResolver(Resolver): ...@@ -55,7 +56,7 @@ class MappingStorageURIResolver(Resolver):
query = '' query = ''
else: else:
name, query = result name, query = result
kw = dict(cgi.parse_qsl(query)) kw = dict(parse_qsl(query))
kw, unused = self.interpret_kwargs(kw) kw, unused = self.interpret_kwargs(kw)
args = (name,) args = (name,)
def factory(): def factory():
...@@ -70,7 +71,7 @@ class FileStorageURIResolver(Resolver): ...@@ -70,7 +71,7 @@ class FileStorageURIResolver(Resolver):
_bytesize_args = ('quota',) _bytesize_args = ('quota',)
def __call__(self, uri): def __call__(self, uri):
# we can't use urlparse.urlsplit here due to Windows filenames # we can't use urlsplit here due to Windows filenames
prefix, rest = uri.split('file://', 1) prefix, rest = uri.split('file://', 1)
result = rest.split('?', 1) result = rest.split('?', 1)
if len(result) == 1: if len(result) == 1:
...@@ -80,7 +81,7 @@ class FileStorageURIResolver(Resolver): ...@@ -80,7 +81,7 @@ class FileStorageURIResolver(Resolver):
path, query = result path, query = result
path = os.path.normpath(path) path = os.path.normpath(path)
args = (path,) args = (path,)
kw = dict(cgi.parse_qsl(query)) kw = dict(parse_qsl(query))
kw, unused = self.interpret_kwargs(kw) kw, unused = self.interpret_kwargs(kw)
demostorage = False demostorage = False
...@@ -127,10 +128,10 @@ class ClientStorageURIResolver(Resolver): ...@@ -127,10 +128,10 @@ class ClientStorageURIResolver(Resolver):
_bytesize_args = ('cache_size', ) _bytesize_args = ('cache_size', )
def __call__(self, uri): def __call__(self, uri):
# urlparse doesnt understand zeo URLs so force to something that # urlsplit doesnt understand zeo URLs so force to something that
# doesn't break # doesn't break
uri = uri.replace('zeo://', 'http://', 1) uri = uri.replace('zeo://', 'http://', 1)
(scheme, netloc, path, query, frag) = urlparse.urlsplit(uri) (scheme, netloc, path, query, frag) = urlsplit(uri)
if netloc: if netloc:
# TCP URL # TCP URL
if ':' in netloc: if ':' in netloc:
...@@ -144,7 +145,7 @@ class ClientStorageURIResolver(Resolver): ...@@ -144,7 +145,7 @@ class ClientStorageURIResolver(Resolver):
# Unix domain socket URL # Unix domain socket URL
path = os.path.normpath(path) path = os.path.normpath(path)
args = (path,) args = (path,)
kw = dict(cgi.parse_qsl(query)) kw = dict(parse_qsl(query))
kw, unused = self.interpret_kwargs(kw) kw, unused = self.interpret_kwargs(kw)
if 'demostorage' in kw: if 'demostorage' in kw:
kw.pop('demostorage') kw.pop('demostorage')
...@@ -158,7 +159,7 @@ class ClientStorageURIResolver(Resolver): ...@@ -158,7 +159,7 @@ class ClientStorageURIResolver(Resolver):
class ZConfigURIResolver(object): class ZConfigURIResolver(object):
schema_xml_template = """ schema_xml_template = b"""
<schema> <schema>
<import package="ZODB"/> <import package="ZODB"/>
<multisection type="ZODB.storage" attribute="storages" /> <multisection type="ZODB.storage" attribute="storages" />
...@@ -167,16 +168,16 @@ class ZConfigURIResolver(object): ...@@ -167,16 +168,16 @@ class ZConfigURIResolver(object):
""" """
def __call__(self, uri): def __call__(self, uri):
(scheme, netloc, path, query, frag) = urlparse.urlsplit(uri) (scheme, netloc, path, query, frag) = urlsplit(uri)
if sys.version_info[:3] < (2, 7, 4): #pragma NO COVER if sys.version_info[:3] < (2, 7, 4): #pragma NO COVER
# urlparse used not to allow fragments in non-standard schemes, # urlsplit used not to allow fragments in non-standard schemes,
# stuffed everything into 'path' # stuffed everything into 'path'
(scheme, netloc, path, query, frag (scheme, netloc, path, query, frag
) = urlparse.urlsplit('http:' + path) ) = urlsplit('http:' + path)
path = os.path.normpath(path) path = os.path.normpath(path)
schema_xml = self.schema_xml_template schema_xml = self.schema_xml_template
schema = ZConfig.loadSchemaFile(StringIO(schema_xml)) schema = loadSchemaFile(BytesIO(schema_xml))
config, handler = ZConfig.loadConfig(schema, path) config, handler = loadConfig(schema, path)
for config_item in config.databases + config.storages: for config_item in config.databases + config.storages:
if not frag: if not frag:
# use the first defined in the file # use the first defined in the file
...@@ -198,7 +199,7 @@ class ZConfigURIResolver(object): ...@@ -198,7 +199,7 @@ class ZConfigURIResolver(object):
dbkw['database_name'] = config.database_name dbkw['database_name'] = config.database_name
else: else:
factory = config_item factory = config_item
dbkw = dict(cgi.parse_qsl(query)) dbkw = dict(parse_qsl(query))
return factory.open, dbkw return factory.open, dbkw
......
...@@ -27,8 +27,7 @@ class Base: ...@@ -27,8 +27,7 @@ class Base:
for name in names: for name in names:
kwargs[name] = '10' kwargs[name] = '10'
args = resolver.interpret_kwargs(kwargs)[0] args = resolver.interpret_kwargs(kwargs)[0]
keys = args.keys() keys = sorted(args.keys())
keys.sort()
self.assertEqual(sorted(keys), sorted(names)) self.assertEqual(sorted(keys), sorted(names))
for name, value in args.items(): for name, value in args.items():
self.assertEqual(value, 10) self.assertEqual(value, 10)
...@@ -160,8 +159,8 @@ class TestFileStorageURIResolver(Base, unittest.TestCase): ...@@ -160,8 +159,8 @@ class TestFileStorageURIResolver(Base, unittest.TestCase):
def test_invoke_factory_blobstorage(self): def test_invoke_factory_blobstorage(self):
import os import os
from urllib import quote as q
from ZODB.blob import BlobStorage from ZODB.blob import BlobStorage
from .._compat import quote as q
DB_FILE = os.path.join(self.tmpdir, 'db.db') DB_FILE = os.path.join(self.tmpdir, 'db.db')
BLOB_DIR = os.path.join(self.tmpdir, 'blob') BLOB_DIR = os.path.join(self.tmpdir, 'blob')
self.assertFalse(os.path.exists(DB_FILE)) self.assertFalse(os.path.exists(DB_FILE))
...@@ -177,8 +176,8 @@ class TestFileStorageURIResolver(Base, unittest.TestCase): ...@@ -177,8 +176,8 @@ class TestFileStorageURIResolver(Base, unittest.TestCase):
def test_invoke_factory_blobstorage_and_demostorage(self): def test_invoke_factory_blobstorage_and_demostorage(self):
import os import os
from urllib import quote as q
from ZODB.DemoStorage import DemoStorage from ZODB.DemoStorage import DemoStorage
from .._compat import quote as q
DB_FILE = os.path.join(self.tmpdir, 'db.db') DB_FILE = os.path.join(self.tmpdir, 'db.db')
BLOB_DIR = os.path.join(self.tmpdir, 'blob') BLOB_DIR = os.path.join(self.tmpdir, 'blob')
self.assertFalse(os.path.exists(DB_FILE)) self.assertFalse(os.path.exists(DB_FILE))
...@@ -290,7 +289,7 @@ class TestZConfigURIResolver(unittest.TestCase): ...@@ -290,7 +289,7 @@ class TestZConfigURIResolver(unittest.TestCase):
self.tmp.close() self.tmp.close()
def test_named_storage(self): def test_named_storage(self):
self.tmp.write(""" self.tmp.write(b"""
<demostorage foo> <demostorage foo>
</demostorage> </demostorage>
...@@ -305,7 +304,7 @@ class TestZConfigURIResolver(unittest.TestCase): ...@@ -305,7 +304,7 @@ class TestZConfigURIResolver(unittest.TestCase):
self.assertTrue(isinstance(storage, MappingStorage), storage) self.assertTrue(isinstance(storage, MappingStorage), storage)
def test_anonymous_storage(self): def test_anonymous_storage(self):
self.tmp.write(""" self.tmp.write(b"""
<mappingstorage> <mappingstorage>
</mappingstorage> </mappingstorage>
...@@ -321,7 +320,7 @@ class TestZConfigURIResolver(unittest.TestCase): ...@@ -321,7 +320,7 @@ class TestZConfigURIResolver(unittest.TestCase):
self.assertEqual(dbkw, {}) self.assertEqual(dbkw, {})
def test_query_string_args(self): def test_query_string_args(self):
self.tmp.write(""" self.tmp.write(b"""
<mappingstorage> <mappingstorage>
</mappingstorage> </mappingstorage>
...@@ -334,7 +333,7 @@ class TestZConfigURIResolver(unittest.TestCase): ...@@ -334,7 +333,7 @@ class TestZConfigURIResolver(unittest.TestCase):
self.assertEqual(dbkw, {'foo': 'bar'}) self.assertEqual(dbkw, {'foo': 'bar'})
def test_storage_not_found(self): def test_storage_not_found(self):
self.tmp.write(""" self.tmp.write(b"""
<mappingstorage x> <mappingstorage x>
</mappingstorage> </mappingstorage>
""") """)
...@@ -343,7 +342,7 @@ class TestZConfigURIResolver(unittest.TestCase): ...@@ -343,7 +342,7 @@ class TestZConfigURIResolver(unittest.TestCase):
self.assertRaises(KeyError, resolver, 'zconfig://%s#y' % self.tmp.name) self.assertRaises(KeyError, resolver, 'zconfig://%s#y' % self.tmp.name)
def test_anonymous_database(self): def test_anonymous_database(self):
self.tmp.write(""" self.tmp.write(b"""
<zodb> <zodb>
<mappingstorage> <mappingstorage>
</mappingstorage> </mappingstorage>
...@@ -360,7 +359,7 @@ class TestZConfigURIResolver(unittest.TestCase): ...@@ -360,7 +359,7 @@ class TestZConfigURIResolver(unittest.TestCase):
'connection_pool_size': 7}) 'connection_pool_size': 7})
def test_named_database(self): def test_named_database(self):
self.tmp.write(""" self.tmp.write(b"""
<zodb x> <zodb x>
<mappingstorage> <mappingstorage>
</mappingstorage> </mappingstorage>
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment