Skip to content

Commit

Permalink
make FormEncode optional and add support for seedlist urls
Browse files Browse the repository at this point in the history
* Added support for mongodb  seedlist URL

* Make formencode optional, only required for people relying on `ming.configure`
  • Loading branch information
devilicecream authored and amol- committed Jan 10, 2019
1 parent 082e380 commit 7c78d3b
Show file tree
Hide file tree
Showing 5 changed files with 98 additions and 59 deletions.
52 changes: 32 additions & 20 deletions ming/config.py
Original file line number Diff line number Diff line change
@@ -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

29 changes: 9 additions & 20 deletions ming/datastore.py
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand All @@ -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:
Expand All @@ -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):
Expand Down Expand Up @@ -146,6 +137,7 @@ def connect(self):
else:
raise


class DataStore(object):
"""Represents a Database on a specific MongoDB Instance.
Expand Down Expand Up @@ -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

70 changes: 53 additions & 17 deletions ming/tests/test_datastore.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import sys
from unittest import TestCase, main

from mock import patch, Mock
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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()
Expand All @@ -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):
Expand All @@ -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(**{
Expand Down Expand Up @@ -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()
Expand All @@ -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()
5 changes: 3 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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="""
Expand Down
1 change: 1 addition & 0 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ deps = nose
pytz
WebOb
webtest
formencode
# python-spidermonkey
commands =
pip install coverage
Expand Down

0 comments on commit 7c78d3b

Please sign in to comment.