diff --git a/ming/config.py b/ming/config.py index 6cad426..370d21c 100644 --- a/ming/config.py +++ b/ming/config.py @@ -1,46 +1,58 @@ import six -from formencode import schema, validators -from formencode.variabledecode import variable_decode - +from ming.exc import MingConfigError from ming.session import Session from ming.datastore import create_datastore -class AuthenticateSchema(schema.Schema): - name=validators.UnicodeString(not_empty=True) - password=validators.UnicodeString(not_empty=True) -class DatastoreSchema(schema.Schema): - allow_extra_fields=True +def variable_decode(**kwargs): + try: + from formencode.variabledecode import variable_decode as fe_variable_decode + except ImportError: + raise MingConfigError("Need to install FormEncode to use ``ming.configure``") + return fe_variable_decode(kwargs) - uri=validators.UnicodeString(if_missing=None, if_empty=None) - database=validators.UnicodeString(if_missing=None, if_empty=None) - authenticate=AuthenticateSchema(if_missing=None, if_empty=None) - connect_retry=validators.Number(if_missing=3, if_empty=0) - auto_ensure_indexes = validators.StringBool(if_missing=True) - # pymongo - tz_aware=validators.Bool(if_missing=False) def configure(**kwargs): """ Given a (flat) dictionary of config values, creates DataStores and saves them by name """ - config = variable_decode(kwargs) - configure_from_nested_dict(config['ming']) + config = variable_decode(**kwargs) + configure_from_nested_dict(config["ming"]) + def configure_from_nested_dict(config): + try: + from formencode import schema, validators + except ImportError: + raise MingConfigError("Need to install FormEncode to use ``ming.configure``") + + class AuthenticateSchema(schema.Schema): + name = validators.UnicodeString(not_empty=True) + password = validators.UnicodeString(not_empty=True) + + class DatastoreSchema(schema.Schema): + allow_extra_fields = True + + uri = validators.UnicodeString(if_missing=None, if_empty=None) + database = validators.UnicodeString(if_missing=None, if_empty=None) + authenticate = AuthenticateSchema(if_missing=None, if_empty=None) + connect_retry = validators.Number(if_missing=3, if_empty=0) + auto_ensure_indexes = validators.StringBool(if_missing=True) + # pymongo + tz_aware = validators.Bool(if_missing=False) + datastores = {} for name, datastore in six.iteritems(config): args = DatastoreSchema.to_python(datastore, None) - database = args.pop('database', None) + database = args.pop("database", None) if database is None: datastores[name] = create_datastore(**args) else: - datastores[name] = create_datastore(database, **args) + datastores[name] = create_datastore(database=database, **args) Session._datastores = datastores # bind any existing sessions for name, session in six.iteritems(Session._registry): session.bind = datastores.get(name, None) session._name = name - diff --git a/ming/datastore.py b/ming/datastore.py index 26d5682..b34d7e8 100644 --- a/ming/datastore.py +++ b/ming/datastore.py @@ -44,7 +44,6 @@ def create_datastore(uri, **kwargs): Optional Keyword args: - bind - a :class:`ming.datastore.Engine` instance - - authenticate - a dict ``{ name:str, password:str }`` with auth info All other keyword args are passed along to :meth:`create_engine`. @@ -54,7 +53,6 @@ def create_datastore(uri, **kwargs): - create_datastore('foo') - create_datastore('foo', bind=create_engine()) """ - auth = kwargs.pop('authenticate', None) bind = kwargs.pop('bind', None) if bind and kwargs: @@ -66,26 +64,19 @@ def create_datastore(uri, **kwargs): # Create the engine (if necessary) if parts.scheme: - if bind: raise exc.MingConfigError("bind not allowed with full URI") - bind_uri = parts._replace( - netloc=parts.netloc.split('@')[-1], - path='/').geturl() - bind = create_engine(bind_uri, **kwargs) - - # extract the auth information - if parts.username: - if auth: raise exc.MingConfigError( - "auth info supplied in uri and kwargs") - auth = dict( - name=parts.username, password=parts.password) + if bind: + raise exc.MingConfigError("bind not allowed with full URI") + bind = create_engine(uri, **kwargs) # extract the database database = parts.path - if database.startswith('/'): database = database[1:] + if database.startswith("/"): + database = database[1:] - if bind is None: bind = create_engine(**kwargs) + if bind is None: + bind = create_engine(**kwargs) - return DataStore(bind, database, authenticate=auth) + return DataStore(bind, database) class Engine(object): @@ -146,6 +137,7 @@ def connect(self): else: raise + class DataStore(object): """Represents a Database on a specific MongoDB Instance. @@ -189,7 +181,4 @@ def db(self): raise ValueError('Trying to access db of an unconnected DataStore') self._db = self.bind[self.name] - if self._authenticate: - self._db.authenticate(**self._authenticate) return self._db - diff --git a/ming/tests/test_datastore.py b/ming/tests/test_datastore.py index 53a5e06..251a8aa 100644 --- a/ming/tests/test_datastore.py +++ b/ming/tests/test_datastore.py @@ -1,3 +1,4 @@ +import sys from unittest import TestCase, main from mock import patch, Mock @@ -8,12 +9,15 @@ from ming import mim from ming import create_datastore, create_engine from ming.datastore import Engine +from ming.exc import MingConfigError + class DummyConnection(object): - def __init__(*args, **kwargs): pass + def __init__(*args, **kwargs): + pass -class TestEngineConnection(TestCase): +class TestEngineConnection(TestCase): @patch('ming.datastore.MongoClient', spec=True) def test_normal(self, MockConnection): from pymongo import MongoClient @@ -42,14 +46,16 @@ def Connection(*a,**kw): self.assertRaises(ConnectionFailure, engine.connect) self.assertEqual(failures[0], 18) + class TestEngineMim(TestCase): - + def test_mim(self): with patch('ming.datastore.mim.Connection', spec=True) as Connection: result = create_engine('mim:///') conn = result.connect() assert conn is Connection.get() + class TestReplicaSet(TestCase): @patch('ming.datastore.MongoClient', spec=True) @@ -61,8 +67,8 @@ def test_replica_set(self, MockConn): conn = result.connect() assert isinstance(conn, MongoClient) -class TestDatastore(TestCase): +class TestDatastore(TestCase): def setUp(self): self.patcher_conn = patch('ming.datastore.MongoClient') self.MockConn = self.patcher_conn.start() @@ -85,12 +91,31 @@ def test_database_only(self): create_datastore('test_db'), 'test_db') - def test_with_auth_in_uri(self): - ds = create_datastore('mongodb://user:pass@server/test_db') - self._check_datastore(ds, 'test_db') - self.assertEqual( - ds._authenticate, - dict(name='user', password='pass')) + @patch('ming.datastore.MongoClient', spec=True) + def test_configure_no_formencode(self, Connection): + with patch.dict(sys.modules, {"formencode": None}): + self.assertRaises( + MingConfigError, + ming.configure, + **{ + "ming.main.uri": "mongodb://localhost:27017/test_db", + "ming.main.connect_retry": 1, + "ming.main.tz_aware": False, + } + ) + + @patch('ming.datastore.MongoClient', spec=True) + def test_configure_no_formencode_variabledecode(self, Connection): + with patch.dict(sys.modules, {"formencode.variabledecode": None}): + self.assertRaises( + MingConfigError, + ming.configure, + **{ + "ming.main.uri": "mongodb://localhost:27017/test_db", + "ming.main.connect_retry": 1, + "ming.main.tz_aware": False, + } + ) @patch('ming.datastore.MongoClient', spec=True) def test_configure(self, Connection): @@ -106,6 +131,23 @@ def test_configure(self, Connection): args, kwargs = Connection.call_args assert 'database' not in kwargs + @patch('ming.datastore.MongoClient', spec=True) + def test_configure_with_database(self, Connection): + ming.configure( + **{ + "ming.main.uri": "mongodb://localhost:27017/test_db", + "ming.main.database": "another_test_db", + "ming.main.connect_retry": 1, + "ming.main.tz_aware": False, + } + ) + session = Session.by_name("main") + assert session.bind.conn is not None + assert session.bind.db is not None + assert session.bind.bind._auto_ensure_indexes + args, kwargs = Connection.call_args + assert "database" in kwargs + @patch('ming.datastore.MongoClient', spec=True) def test_configure_auto_ensure_indexes(self, Connection): ming.configure(**{ @@ -139,13 +181,6 @@ def test_no_kwargs_with_bind(self): create_datastore, 'test_db', bind=create_engine('master'), replicaSet='foo') - def test_no_double_auth(self): - self.assertRaises( - ming.exc.MingConfigError, - create_datastore, - 'mongodb://user:pass@server/test_db', - authenticate=dict(name='user', password='pass')) - def test_mim_ds(self): ds = create_datastore('mim:///test_db') conn = ds.bind.connect() @@ -155,5 +190,6 @@ def _check_datastore(self, ds, db_name): assert ds.db is self.MockConn()[db_name] assert ds.name == db_name + if __name__ == '__main__': main() diff --git a/setup.py b/setup.py index a671aa9..798d039 100644 --- a/setup.py +++ b/setup.py @@ -34,17 +34,18 @@ include_package_data=True, zip_safe=True, install_requires=[ - "FormEncode >= 1.2.1", + # "FormEncode >= 1.2.1", # required to use ``ming.configure`` "pymongo>=3.0,<3.7", "pytz", "six>=1.6.1" ], - tests_require = [ + tests_require=[ "nose", "mock >=0.8.0", "pytz", "WebOb", "webtest", + "FormEncode >= 1.2.1", # "python-spidermonkey >= 0.0.10", # required for full MIM functionality ], entry_points=""" diff --git a/tox.ini b/tox.ini index 4ffe6c1..59505de 100644 --- a/tox.ini +++ b/tox.ini @@ -8,6 +8,7 @@ deps = nose pytz WebOb webtest + formencode # python-spidermonkey commands = pip install coverage