Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ dist/
minimongo.egg-info/
minimongo/app_config.py
pymongo-*/
venv
3 changes: 2 additions & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,11 @@ you can use ``easy_install -U minimongo``. Otherwise, you can download the
source from `GitHub <http://github.com/slacy/minimongo>`_ and run ``python
setup.py install``.

Instead of installing as above, you should use `pip install -e .`.

Dependencies
============
- pymongo_ 1.9+
- pymongo_ 2.8+
- `sphinx <http://sphinx.pocoo.org>`_ (optional -- for documentation generation)


Expand Down
1 change: 1 addition & 0 deletions minimongo/collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ class Cursor(PyMongoCursor):

def __init__(self, *args, **kwargs):
self._wrapper_class = kwargs.pop('wrap')
kwargs['projection'] = kwargs.pop('fields', None)
super(Cursor, self).__init__(*args, **kwargs)

def next(self):
Expand Down
78 changes: 40 additions & 38 deletions minimongo/model.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,26 @@
# -*- coding: utf-8 -*-
import copy
from __future__ import absolute_import

import copy
import logging
import re

import six
from bson import DBRef, ObjectId
from minimongo.collection import DummyCollection
from minimongo.options import _Options
from pymongo import Connection
from pymongo import MongoClient, MongoReplicaSetClient
from pymongo.read_preferences import ReadPreference

from .collection import DummyCollection
from .options import _Options


class ModelBase(type):
"""Metaclass for all models.

.. todo:: add Meta inheritance -- so that missing attributes are
populated from the parrent's Meta if any.
"""

# A very rudimentary connection pool.
# A very rudimentary connection pool, keyed by replicaSet name.
_connections = {}

def __new__(mcs, name, bases, attrs):
Expand All @@ -32,8 +37,8 @@ def __new__(mcs, name, bases, attrs):
except AttributeError:
meta = None
else:
delattr(new_class, 'Meta') # Won't need the original metadata
# container anymore.
# Won't need the original metadata container anymore.
delattr(new_class, 'Meta')

options = _Options(meta)
options.collection = options.collection or to_underscore(name)
Expand All @@ -49,21 +54,30 @@ def __new__(mcs, name, bases, attrs):
'Model %r improperly configured: %s %s %s' % (
name, options.host, options.port, options.database))

# Checking connection pool for an existing connection.
hostport = options.host, options.port
if hostport in mcs._connections:
connection = mcs._connections[hostport]
# Checking connection / client pool for an existing connection / client.
pool_key = options.host,options.port
if options.replica_set_name:
logging.debug("Using replica_set_name=%s as database pool key." % options.replica_set_name)
pool_key = options.replica_set_name

if pool_key in mcs._connections:
client = mcs._connections[pool_key]
logging.debug("Got database client from pool for pool_key=%s" % (pool_key,))
else:
# _connect=False option
# creates :class:`pymongo.connection.Connection` object without
# establishing connection. It's required if there is no running
# mongodb at this time but we want to create :class:`Model`.
connection = Connection(*hostport, _connect=False)
mcs._connections[hostport] = connection
logging.debug("Creating new database client for pool_key=%s" % (pool_key,))
if options.replica_set_name:
logging.debug("Setting up a replica set client...")
client = MongoReplicaSetClient(options.replica_set_uri, replicaSet=options.replica_set_name)
client.read_preference = ReadPreference.SECONDARY_PREFERRED
else:
logging.debug("Setting up a normal client...")
client = MongoClient(options.host, options.port)

mcs._connections[pool_key] = client

new_class._meta = options
new_class.connection = connection
new_class.database = connection[options.database]
new_class.connection = client
new_class.database = client[options.database]
if options.username and options.password:
new_class.database.authenticate(options.username, options.password)
new_class.collection = options.collection_class(
Expand All @@ -76,13 +90,11 @@ def __new__(mcs, name, bases, attrs):

def auto_index(mcs):
"""Builds all indices, listed in model's Meta class.

>>> class SomeModel(Model)
... class Meta:
... indices = (
... Index('foo'),
... )

.. note:: this will result in calls to
:meth:`pymongo.collection.Collection.ensure_index`
method at import time, so import all your models up
Expand All @@ -98,13 +110,13 @@ def __init__(self, initial=None, **kwargs):
# AttrDict. Maybe this could be better done with the builtin
# defaultdict?
if initial:
for key, value in initial.iteritems():
for key, value in six.iteritems(initial):
# Can't just say self[k] = v here b/c of recursion.
self.__setitem__(key, value)

# Process the other arguments (assume they are also default values).
# This is the same behavior as the regular dict constructor.
for key, value in kwargs.iteritems():
for key, value in six.iteritems(kwargs):
self.__setitem__(key, value)

super(AttrDict, self).__init__()
Expand Down Expand Up @@ -141,9 +153,10 @@ def __setitem__(self, key, value):
return super(AttrDict, self).__setitem__(key, new_value)


@six.python_2_unicode_compatible
@six.add_metaclass(ModelBase)
class Model(AttrDict):
"""Base class for all Minimongo objects.

>>> class Foo(Model):
... class Meta:
... database = 'somewhere'
Expand All @@ -157,16 +170,10 @@ class Model(AttrDict):
>>> foo.bar == 42
True
"""

__metaclass__ = ModelBase

def __str__(self):
return '%s(%s)' % (self.__class__.__name__,
super(Model, self).__str__())

def __unicode__(self):
return str(self).decode('utf-8')

def __setitem__(self, key, value):
# Go through the defined list of field mappers. If the fild
# matches, then modify the field value by calling the function in
Expand All @@ -186,10 +193,8 @@ def __setitem__(self, key, value):

def dbref(self, with_database=True, **kwargs):
"""Returns a DBRef for the current object.

If `with_database` is False, the resulting :class:`pymongo.dbref.DBRef`
won't have a :attr:`database` field.

Any other parameters will be passed to the DBRef constructor, as per
the mongo specs.
"""
Expand Down Expand Up @@ -217,15 +222,14 @@ def mongo_update(self, values=None, **kwargs):

def save(self, *args, **kwargs):
"""Save this object to it's mongo collection."""
kwargs.pop('safe', None)
self.collection.save(self, *args, **kwargs)
return self

def load(self, fields=None, **kwargs):
"""Allow partial loading of a document.
:attr:fields is a dictionary as per the pymongo specs

self.collection.find_one( self._id, fields={'name': 1} )

"""
values = self.collection.find_one({'_id': self._id},
fields=fields, **kwargs)
Expand All @@ -238,10 +242,8 @@ def load(self, fields=None, **kwargs):

def to_underscore(string):
"""Converts a given string from CamelCase to under_score.

>>> to_underscore('FooBar')
'foo_bar'
"""
new_string = re.sub(r'([A-Z]+)([A-Z][a-z])', r'\1_\2', string)
new_string = re.sub(r'([a-z\d])([A-Z])', r'\1_\2', new_string)
return new_string.lower()
new_string = re.sub(r'([a-z\d])([A-Z])', r'\1_\2', new_string)
4 changes: 4 additions & 0 deletions minimongo/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ class _Options(object):
username = None
password = None

# Replica set details.
replica_set_name = None
replica_set_uri = None

# Should indices be created at startup?
auto_index = True

Expand Down
Loading