Commit 28ae352e authored by Georges Dubus's avatar Georges Dubus

Added support for postgres:// uri.

Support for mysql and oracle should be easy to add from here.
parent 87be3603
......@@ -97,3 +97,4 @@ Contributors
------------
- Chris McDonough, 2011/08/18
- Georges Dubus 2012/05/27
......@@ -43,9 +43,10 @@ passing to the ``ZODB.DB.DB`` constructor. For example:
URI Schemes
-----------
The URI schemes currently recognized in the ``zodbconn.uri`` setting are
``file://``, ``zeo://``, ``zconfig://`` and ``memory://``. Documentation for
these URI scheme syntaxes are below.
The URI schemes currently recognized in the ``zodbconn.uri`` setting
are ``file://``, ``zeo://``, ``zconfig://``, ``memory://``
``postgres://``. Documentation for these URI scheme syntaxes are
below.
``file://`` URI scheme
~~~~~~~~~~~~~~~~~~~~~~
......@@ -286,6 +287,110 @@ An example that combines a dbname with a query string::
memory://storagename?connection_cache_size=100&database_name=fleeb
``postgres://`` URI scheme
~~~~~~~~~~~~~~~~~~~~~~~~~~
The ``postgres://`` URI scheme can be passed as ``zodbconn.uri`` to
create a RelStorage PostgresSQL database factory. The uri should
contain the user, the password, the host, the port and the db name
e.g.::
postgres://someuser:somepass@somehost:5432/somedb?connection_cache_size=20000
The URI scheme also accepts query string arguments. The query string
arguments honored by this scheme are as follows.
RelStorage-constructor related
++++++++++++++++++++++++++++++
These arguments generally inform the RelStorage constructor about
values of the same names.
poll_interval
int
cache_local_mb
int
commit_lock_timeout
int
commit_lock_id
int
read_only
boolean
shared_blob_dir
boolean
keep_history
boolean
pack_gc
boolean
pack_dry_run
boolean
strict_tpc
boolean
create
boolean
blob_cache_size
bytesize
blob_cache_size_check
bytesize
blob_cache_chunk_size
bytesize
replica_timeout
float
pack_batch_timeout
float
pack_duty_cycle
float
pack_max_delay
float
name
string
blob_dir
string
replica_conf
string
cache_module_name
string
cache_prefix
string
cache_delta_size_limit
string
cache_servers
string of the for "first,second,third"
Misc
++++
demostorage
boolean (if true, wrap RelStorage in a DemoStorage)
Connection-related
++++++++++++++++++
These arguments relate to connections created from the database.
connection_cache_size
integer (default 10000)
connection_pool_size
integer (default 7)
Database-related
++++++++++++++++
These arguments relate to the database (as opposed to storage)
settings.
database_name
string
Example
+++++++
An example that combines a path with a query string::
postgres://someuser:somepass@somehost:5432/somedb?connection_cache_size=20000
More Information
----------------
......
......@@ -12,7 +12,7 @@ except:
README = ''
CHANGES = ''
requires = ['ZODB3']
requires = ['ZODB3', 'RelStorage',]
tests_require = requires + ['mock']
setup(name='zodburi',
......@@ -40,5 +40,6 @@ setup(name='zodburi',
file = zodburi.resolvers:file_storage_resolver
zconfig = zodburi.resolvers:zconfig_resolver
memory = zodburi.resolvers:mapping_storage_resolver
postgres = zodburi.resolvers:postgresql_resolver
"""
)
......@@ -10,6 +10,9 @@ from ZODB.DemoStorage import DemoStorage
from ZODB.MappingStorage import MappingStorage
from ZODB.blob import BlobStorage
import ZConfig
from relstorage.adapters.postgresql import PostgreSQLAdapter
from relstorage.options import Options
from relstorage.storage import RelStorage
from zodburi.datatypes import convert_bytesize
from zodburi.datatypes import convert_int
......@@ -19,6 +22,8 @@ class Resolver(object):
_int_args = ()
_string_args = ()
_bytesize_args = ()
_float_args = ()
_tuple_args = ()
def interpret_kwargs(self, kw):
unused = kw.copy()
......@@ -197,7 +202,70 @@ class ZConfigURIResolver(object):
return factory.open, dbkw
# Not a real resolver, but we use interpret_kwargs
class PostgreSQLAdapterHelper(Resolver):
_int_args = ('connect_timeout', )
_string_args = ('ssl_mode', )
def __call__(self, parsed_uri, kw):
dsn_args = [
('dbname', parsed_uri.path[1:]),
('user', parsed_uri.username),
('password', parsed_uri.password),
('host', parsed_uri.hostname),
('port', parsed_uri.port)
]
kw, unused = self.interpret_kwargs(kw)
dsn_args.extend(kw.items())
dsn = ' '.join("%s='%s'"%arg for arg in dsn_args)
def factory(options):
return PostgreSQLAdapter(dsn=dsn, options=options)
return factory, unused
# The relstorage support is inspired by django-zodb.
# Oracle and mysql should be easily implementable from here
class RelStorageURIResolver(Resolver):
_int_args = ('poll_interval', 'cache_local_mb', 'commit_lock_timeout',
'commit_lock_id', 'read_only', 'shared_blob_dir',
'keep_history', 'pack_gc', 'pack_dry_run', 'strict_tpc',
'create', 'demostorage',)
_string_args = ('name', 'blob_dir', 'replica_conf', 'cache_module_name',
'cache_prefix', 'cache_delta_size_limit')
_bytesize_args = ('blob_cache_size', 'blob_cache_size_check',
'blob_cache_chunk_size')
_float_args = ('replica_timeout', 'pack_batch_timeout', 'pack_duty_cycle',
'pack_max_delay')
_tuple_args = ('cache_servers',)
def __init__(self, adapter_helper):
self.adapter_helper = adapter_helper
def __call__(self, uri):
uri = uri.replace('postgres://', 'http://', 1)
parsed_uri = urlparse.urlsplit(uri)
kw = dict(cgi.parse_qsl(parsed_uri.query))
adapter_factory, kw = self.adapter_helper(parsed_uri, kw)
kw, unused = self.interpret_kwargs(kw)
demostorage = kw.pop('demostorage', False)
options = Options(**kw)
def factory():
adapter = adapter_factory(options)
storage = RelStorage(adapter=adapter, options=options)
if demostorage:
storage = DemoStorage(base=storage)
return storage
return factory, unused
client_storage_resolver = ClientStorageURIResolver()
file_storage_resolver = FileStorageURIResolver()
zconfig_resolver = ZConfigURIResolver()
mapping_storage_resolver = MappingStorageURIResolver()
\ No newline at end of file
mapping_storage_resolver = MappingStorageURIResolver()
postgresql_resolver = RelStorageURIResolver(PostgreSQLAdapterHelper())
\ No newline at end of file
......@@ -47,6 +47,32 @@ class Base:
for name, value in args.items():
self.assertEqual(value, 'string')
def test_float_args(self):
resolver = self._makeOne()
names = list(resolver._float_args)
kwargs = {}
for name in names:
kwargs[name] = '3.14'
args = resolver.interpret_kwargs(kwargs)[0]
keys = args.keys()
keys.sort()
self.assertEqual(keys, names)
for name, value in args.items():
self.assertEqual(value, 3.14)
def test_tuple_args(self):
resolver = self._makeOne()
names = list(resolver._tuple_args)
kwargs = {}
for name in names:
kwargs[name] = 'first,second,third'
args = resolver.interpret_kwargs(kwargs)[0]
keys = args.keys()
keys.sort()
self.assertEqual(keys, names)
for name, value in args.items():
self.assertEqual(value, ('first', 'second', 'third'))
class TestFileStorageURIResolver(Base, unittest.TestCase):
def _getTargetClass(self):
......@@ -387,6 +413,88 @@ class TestMappingStorageURIResolver(Base, unittest.TestCase):
self.assertEqual(storage.__name__, 'storagename')
class TestPostgreSQLURIResolver(unittest.TestCase):
def _getTargetClass(self):
from zodburi.resolvers import RelStorageURIResolver
return RelStorageURIResolver
def _makeOne(self):
from zodburi.resolvers import PostgreSQLAdapterHelper
klass = self._getTargetClass()
return klass(PostgreSQLAdapterHelper())
def setUp(self):
# relstorage.options.Options is little more than a dict.
# We make it comparable to simplify the tests.
from relstorage.options import Options
Options.__eq__ = lambda s, o: vars(s) == vars(o)
def test_bool_args(self):
resolver = self._makeOne()
f = resolver.interpret_kwargs
kwargs = f({'read_only':'1'})
self.assertEqual(kwargs[0], {'read_only':1})
kwargs = f({'read_only':'true'})
self.assertEqual(kwargs[0], {'read_only':1})
kwargs = f({'read_only':'on'})
self.assertEqual(kwargs[0], {'read_only':1})
kwargs = f({'read_only':'off'})
self.assertEqual(kwargs[0], {'read_only':0})
kwargs = f({'read_only':'no'})
self.assertEqual(kwargs[0], {'read_only':0})
kwargs = f({'read_only':'false'})
self.assertEqual(kwargs[0], {'read_only':0})
@mock.patch('zodburi.resolvers.PostgreSQLAdapter')
@mock.patch('zodburi.resolvers.RelStorage')
def test_call(self, RelStorage, PostgreSQLAdapter):
from relstorage.options import Options
resolver = self._makeOne()
factory, dbkw = resolver('postgres://someuser:somepass@somehost:5432/somedb?read_only=1')
factory()
expected_options = Options(read_only=1)
PostgreSQLAdapter.assert_called_once_with(dsn="dbname='somedb' user='someuser' password='somepass' "
"host='somehost' port='5432'",
options=expected_options)
RelStorage.assert_called_once_with(adapter=PostgreSQLAdapter(), options=expected_options)
@mock.patch('zodburi.resolvers.PostgreSQLAdapter')
@mock.patch('zodburi.resolvers.RelStorage')
def test_call_adapter_options(self, RelStorage, PostgreSQLAdapter):
from relstorage.options import Options
resolver = self._makeOne()
factory, dbkw = resolver('postgres://someuser:somepass@somehost:5432/somedb?read_only=1'
'&connect_timeout=10')
factory()
expected_options = Options(read_only=1)
PostgreSQLAdapter.assert_called_once_with(dsn="dbname='somedb' user='someuser' password='somepass' "
"host='somehost' port='5432' connect_timeout='10'",
options=expected_options)
RelStorage.assert_called_once_with(adapter=PostgreSQLAdapter(), options=expected_options)
@mock.patch('zodburi.resolvers.PostgreSQLAdapter')
@mock.patch('zodburi.resolvers.RelStorage')
def test_invoke_factory_demostorage(self, RelStorage, PostgreSQLAdapter):
from ZODB.DemoStorage import DemoStorage
resolver = self._makeOne()
factory, dbkw = resolver('postgres://someuser:somepass@somehost:5432/somedb?read_only=1'
'&demostorage=true')
self.assertTrue(isinstance(factory(), DemoStorage))
def test_dbargs(self):
resolver = self._makeOne()
factory, dbkw = resolver('postgres://someuser:somepass@somehost:5432/somedb?read_only=1&'
'connection_pool_size=1&'
'connection_cache_size=1&'
'database_name=dbname')
self.assertEqual(dbkw, {'connection_pool_size': '1',
'connection_cache_size': '1',
'database_name': 'dbname'})
class TestEntryPoints(unittest.TestCase):
def test_it(self):
......@@ -397,6 +505,7 @@ class TestEntryPoints(unittest.TestCase):
('zeo', resolvers.ClientStorageURIResolver),
('file', resolvers.FileStorageURIResolver),
('zconfig', resolvers.ZConfigURIResolver),
('postgres', resolvers.RelStorageURIResolver),
]
for name, cls in expected:
target = load_entry_point('zodburi', 'zodburi.resolvers', name)
......
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