Blinker signals added
This commit is contained in:
parent
3246cf8bdd
commit
c3a8840435
@ -2,6 +2,8 @@ from queryset import QuerySet, QuerySetManager
|
|||||||
from queryset import DoesNotExist, MultipleObjectsReturned
|
from queryset import DoesNotExist, MultipleObjectsReturned
|
||||||
from queryset import DO_NOTHING
|
from queryset import DO_NOTHING
|
||||||
|
|
||||||
|
from mongoengine import signals
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import pymongo
|
import pymongo
|
||||||
import pymongo.objectid
|
import pymongo.objectid
|
||||||
@ -382,6 +384,8 @@ class TopLevelDocumentMetaclass(DocumentMetaclass):
|
|||||||
class BaseDocument(object):
|
class BaseDocument(object):
|
||||||
|
|
||||||
def __init__(self, **values):
|
def __init__(self, **values):
|
||||||
|
signals.pre_init.send(self, values=values)
|
||||||
|
|
||||||
self._data = {}
|
self._data = {}
|
||||||
# Assign default values to instance
|
# Assign default values to instance
|
||||||
for attr_name in self._fields.keys():
|
for attr_name in self._fields.keys():
|
||||||
@ -395,6 +399,8 @@ class BaseDocument(object):
|
|||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
signals.post_init.send(self)
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
"""Ensure that all fields' values are valid and that required fields
|
"""Ensure that all fields' values are valid and that required fields
|
||||||
are present.
|
are present.
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
from mongoengine import signals
|
||||||
from base import (DocumentMetaclass, TopLevelDocumentMetaclass, BaseDocument,
|
from base import (DocumentMetaclass, TopLevelDocumentMetaclass, BaseDocument,
|
||||||
ValidationError)
|
ValidationError)
|
||||||
from queryset import OperationError
|
from queryset import OperationError
|
||||||
@ -75,6 +76,8 @@ class Document(BaseDocument):
|
|||||||
For example, ``save(..., w=2, fsync=True)`` will wait until at least two servers
|
For example, ``save(..., w=2, fsync=True)`` will wait until at least two servers
|
||||||
have recorded the write and will force an fsync on each server being written to.
|
have recorded the write and will force an fsync on each server being written to.
|
||||||
"""
|
"""
|
||||||
|
signals.pre_save.send(self)
|
||||||
|
|
||||||
if validate:
|
if validate:
|
||||||
self.validate()
|
self.validate()
|
||||||
|
|
||||||
@ -82,6 +85,7 @@ class Document(BaseDocument):
|
|||||||
write_options = {}
|
write_options = {}
|
||||||
|
|
||||||
doc = self.to_mongo()
|
doc = self.to_mongo()
|
||||||
|
created = '_id' not in doc
|
||||||
try:
|
try:
|
||||||
collection = self.__class__.objects._collection
|
collection = self.__class__.objects._collection
|
||||||
if force_insert:
|
if force_insert:
|
||||||
@ -96,12 +100,16 @@ class Document(BaseDocument):
|
|||||||
id_field = self._meta['id_field']
|
id_field = self._meta['id_field']
|
||||||
self[id_field] = self._fields[id_field].to_python(object_id)
|
self[id_field] = self._fields[id_field].to_python(object_id)
|
||||||
|
|
||||||
|
signals.post_save.send(self, created=created)
|
||||||
|
|
||||||
def delete(self, safe=False):
|
def delete(self, safe=False):
|
||||||
"""Delete the :class:`~mongoengine.Document` from the database. This
|
"""Delete the :class:`~mongoengine.Document` from the database. This
|
||||||
will only take effect if the document has been previously saved.
|
will only take effect if the document has been previously saved.
|
||||||
|
|
||||||
:param safe: check if the operation succeeded before returning
|
:param safe: check if the operation succeeded before returning
|
||||||
"""
|
"""
|
||||||
|
signals.pre_delete.send(self)
|
||||||
|
|
||||||
id_field = self._meta['id_field']
|
id_field = self._meta['id_field']
|
||||||
object_id = self._fields[id_field].to_mongo(self[id_field])
|
object_id = self._fields[id_field].to_mongo(self[id_field])
|
||||||
try:
|
try:
|
||||||
@ -110,6 +118,8 @@ class Document(BaseDocument):
|
|||||||
message = u'Could not delete document (%s)' % err.message
|
message = u'Could not delete document (%s)' % err.message
|
||||||
raise OperationError(message)
|
raise OperationError(message)
|
||||||
|
|
||||||
|
signals.post_delete.send(self)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def register_delete_rule(cls, document_cls, field_name, rule):
|
def register_delete_rule(cls, document_cls, field_name, rule):
|
||||||
"""This method registers the delete rules to apply when removing this
|
"""This method registers the delete rules to apply when removing this
|
||||||
|
41
mongoengine/signals.py
Normal file
41
mongoengine/signals.py
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
signals_available = False
|
||||||
|
try:
|
||||||
|
from blinker import Namespace
|
||||||
|
signals_available = True
|
||||||
|
except ImportError:
|
||||||
|
class Namespace(object):
|
||||||
|
def signal(self, name, doc=None):
|
||||||
|
return _FakeSignal(name, doc)
|
||||||
|
|
||||||
|
class _FakeSignal(object):
|
||||||
|
"""If blinker is unavailable, create a fake class with the same
|
||||||
|
interface that allows sending of signals but will fail with an
|
||||||
|
error on anything else. Instead of doing anything on send, it
|
||||||
|
will just ignore the arguments and do nothing instead.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, name, doc=None):
|
||||||
|
self.name = name
|
||||||
|
self.__doc__ = doc
|
||||||
|
|
||||||
|
def _fail(self, *args, **kwargs):
|
||||||
|
raise RuntimeError('signalling support is unavailable '
|
||||||
|
'because the blinker library is '
|
||||||
|
'not installed.')
|
||||||
|
send = lambda *a, **kw: None
|
||||||
|
connect = disconnect = has_receivers_for = receivers_for = \
|
||||||
|
temporarily_connected_to = _fail
|
||||||
|
del _fail
|
||||||
|
|
||||||
|
# the namespace for code signals. If you are not mongoengine code, do
|
||||||
|
# not put signals in here. Create your own namespace instead.
|
||||||
|
_signals = Namespace()
|
||||||
|
|
||||||
|
pre_init = _signals.signal('pre_init')
|
||||||
|
post_init = _signals.signal('post_init')
|
||||||
|
pre_save = _signals.signal('pre_save')
|
||||||
|
post_save = _signals.signal('post_save')
|
||||||
|
pre_delete = _signals.signal('pre_delete')
|
||||||
|
post_delete = _signals.signal('post_delete')
|
6
setup.py
6
setup.py
@ -15,7 +15,7 @@ def get_version(version_tuple):
|
|||||||
version = '%s.%s' % (version, version_tuple[2])
|
version = '%s.%s' % (version, version_tuple[2])
|
||||||
return version
|
return version
|
||||||
|
|
||||||
# Dirty hack to get version number from monogengine/__init__.py - we can't
|
# Dirty hack to get version number from monogengine/__init__.py - we can't
|
||||||
# import it as it depends on PyMongo and PyMongo isn't installed until this
|
# import it as it depends on PyMongo and PyMongo isn't installed until this
|
||||||
# file is read
|
# file is read
|
||||||
init = os.path.join(os.path.dirname(__file__), 'mongoengine', '__init__.py')
|
init = os.path.join(os.path.dirname(__file__), 'mongoengine', '__init__.py')
|
||||||
@ -45,6 +45,6 @@ setup(name='mongoengine',
|
|||||||
long_description=LONG_DESCRIPTION,
|
long_description=LONG_DESCRIPTION,
|
||||||
platforms=['any'],
|
platforms=['any'],
|
||||||
classifiers=CLASSIFIERS,
|
classifiers=CLASSIFIERS,
|
||||||
install_requires=['pymongo'],
|
install_requires=['pymongo', 'blinker'],
|
||||||
test_suite='tests',
|
test_suite='tests.signals',
|
||||||
)
|
)
|
||||||
|
130
tests/signals.py
Normal file
130
tests/signals.py
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from mongoengine import *
|
||||||
|
from mongoengine import signals
|
||||||
|
|
||||||
|
signal_output = []
|
||||||
|
|
||||||
|
|
||||||
|
class SignalTests(unittest.TestCase):
|
||||||
|
"""
|
||||||
|
Testing signals before/after saving and deleting.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_signal_output(self, fn, *args, **kwargs):
|
||||||
|
# Flush any existing signal output
|
||||||
|
global signal_output
|
||||||
|
signal_output = []
|
||||||
|
fn(*args, **kwargs)
|
||||||
|
return signal_output
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
connect(db='mongoenginetest')
|
||||||
|
class Author(Document):
|
||||||
|
name = StringField()
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def pre_init(cls, instance, **kwargs):
|
||||||
|
signal_output.append('pre_init signal, %s' % cls.__name__)
|
||||||
|
signal_output.append(str(kwargs['values']))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def post_init(cls, instance, **kwargs):
|
||||||
|
signal_output.append('post_init signal, %s' % instance)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def pre_save(cls, instance, **kwargs):
|
||||||
|
signal_output.append('pre_save signal, %s' % instance)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def post_save(cls, instance, **kwargs):
|
||||||
|
signal_output.append('post_save signal, %s' % instance)
|
||||||
|
if 'created' in kwargs:
|
||||||
|
if kwargs['created']:
|
||||||
|
signal_output.append('Is created')
|
||||||
|
else:
|
||||||
|
signal_output.append('Is updated')
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def pre_delete(cls, instance, **kwargs):
|
||||||
|
signal_output.append('pre_delete signal, %s' % instance)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def post_delete(cls, instance, **kwargs):
|
||||||
|
signal_output.append('post_delete signal, %s' % instance)
|
||||||
|
|
||||||
|
self.Author = Author
|
||||||
|
|
||||||
|
# Save up the number of connected signals so that we can check at the end
|
||||||
|
# that all the signals we register get properly unregistered
|
||||||
|
self.pre_signals = (
|
||||||
|
len(signals.pre_init.receivers),
|
||||||
|
len(signals.post_init.receivers),
|
||||||
|
len(signals.pre_save.receivers),
|
||||||
|
len(signals.post_save.receivers),
|
||||||
|
len(signals.pre_delete.receivers),
|
||||||
|
len(signals.post_delete.receivers)
|
||||||
|
)
|
||||||
|
|
||||||
|
signals.pre_init.connect(Author.pre_init)
|
||||||
|
signals.post_init.connect(Author.post_init)
|
||||||
|
signals.pre_save.connect(Author.pre_save)
|
||||||
|
signals.post_save.connect(Author.post_save)
|
||||||
|
signals.pre_delete.connect(Author.pre_delete)
|
||||||
|
signals.post_delete.connect(Author.post_delete)
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
signals.pre_init.disconnect(self.Author.pre_init)
|
||||||
|
signals.post_init.disconnect(self.Author.post_init)
|
||||||
|
signals.post_delete.disconnect(self.Author.post_delete)
|
||||||
|
signals.pre_delete.disconnect(self.Author.pre_delete)
|
||||||
|
signals.post_save.disconnect(self.Author.post_save)
|
||||||
|
signals.pre_save.disconnect(self.Author.pre_save)
|
||||||
|
|
||||||
|
# Check that all our signals got disconnected properly.
|
||||||
|
post_signals = (
|
||||||
|
len(signals.pre_init.receivers),
|
||||||
|
len(signals.post_init.receivers),
|
||||||
|
len(signals.pre_save.receivers),
|
||||||
|
len(signals.post_save.receivers),
|
||||||
|
len(signals.pre_delete.receivers),
|
||||||
|
len(signals.post_delete.receivers)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(self.pre_signals, post_signals)
|
||||||
|
|
||||||
|
def test_model_signals(self):
|
||||||
|
""" Model saves should throw some signals. """
|
||||||
|
|
||||||
|
def create_author():
|
||||||
|
a1 = self.Author(name='Bill Shakespeare')
|
||||||
|
|
||||||
|
self.assertEqual(self.get_signal_output(create_author), [
|
||||||
|
"pre_init signal, Author",
|
||||||
|
"{'name': 'Bill Shakespeare'}",
|
||||||
|
"post_init signal, Bill Shakespeare",
|
||||||
|
])
|
||||||
|
|
||||||
|
a1 = self.Author(name='Bill Shakespeare')
|
||||||
|
self.assertEqual(self.get_signal_output(a1.save), [
|
||||||
|
"pre_save signal, Bill Shakespeare",
|
||||||
|
"post_save signal, Bill Shakespeare",
|
||||||
|
"Is created"
|
||||||
|
])
|
||||||
|
|
||||||
|
a1.reload()
|
||||||
|
a1.name='William Shakespeare'
|
||||||
|
self.assertEqual(self.get_signal_output(a1.save), [
|
||||||
|
"pre_save signal, William Shakespeare",
|
||||||
|
"post_save signal, William Shakespeare",
|
||||||
|
"Is updated"
|
||||||
|
])
|
||||||
|
|
||||||
|
self.assertEqual(self.get_signal_output(a1.delete), [
|
||||||
|
'pre_delete signal, William Shakespeare',
|
||||||
|
'post_delete signal, William Shakespeare',
|
||||||
|
])
|
Loading…
x
Reference in New Issue
Block a user