Skip to content
This repository has been archived by the owner on Feb 13, 2020. It is now read-only.

Commit

Permalink
- improved dictify method, convert initial given SON data
Browse files Browse the repository at this point in the history
- added initial tests for helper methods (fake mongodb tests still missing)
- cleanup setup.py
- added MANIFEST.in
- prepare next release
  • Loading branch information
projekt01 committed Dec 9, 2012
1 parent 4264522 commit f5974d9
Show file tree
Hide file tree
Showing 9 changed files with 228 additions and 24 deletions.
6 changes: 4 additions & 2 deletions CHANGES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@
CHANGES
=======

0.1.1 (unreleased)
0.1.1 (2012-12-10)
------------------

- Nothing changed yet.
- inproved dictify method, also convert single SON object to dict

- add some initial tests


0.1.0 (2012-05-22)
Expand Down
3 changes: 3 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
global-include *.txt
include *.txt
recursive-include src *.txt
4 changes: 2 additions & 2 deletions README.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
This package provides a FAKE mongoDB implementation.
This is quite handy for testing.
This package provides a FAKE mongoDB implementation and is quite handy for
testing.
2 changes: 1 addition & 1 deletion TODO.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
TODO
====

add some tests
- add some fake mongodb tests
4 changes: 2 additions & 2 deletions buildout.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ parts = test checker coverage coveragereport

[test]
recipe = zc.recipe.testrunner
eggs = m01.mongofake [test]
eggs = m01.mongofake


[checker]
Expand All @@ -16,7 +16,7 @@ path = src/m01.mongofake

[coverage]
recipe = zc.recipe.testrunner
eggs = m01.mongofake [test]
eggs = m01.mongofake
defaults = ['--all', '--coverage', '../../coverage']


Expand Down
7 changes: 2 additions & 5 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def read(*rnames):

setup(
name='m01.mongofake',
version='0.1.1.dev0',
version='0.1.1',
author='Zope Foundation and Contributors',
author_email='zope-dev@zope.org',
description="Fake MongoDB implementation",
Expand All @@ -45,10 +45,7 @@ def read(*rnames):
packages=find_packages('src'),
include_package_data=True,
package_dir={'': 'src'},
extras_require=dict(
test=[
'zope.testing',
]),
extras_require=dict(),
install_requires=[
'setuptools',
'pymongo',
Expand Down
157 changes: 157 additions & 0 deletions src/m01/mongofake/README.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
======
README
======

Let's test some mongofake helper methods.

>>> import re
>>> import datetime
>>> import bson.tz_util
>>> import m01.mongofake
>>> from m01.mongofake import pprint


RENormalizer
------------

The RENormalizer is able to normalize text and produce comparable output. You
can setup the RENormalizer with a list of input, output expressions. This is
usefull if you dump mongodb data which contains dates or other not so simple
reproducable data. Such a dump result can get normalized before the unit test
will compare the output. Also see zope.testing.renormalizing for the same
pattern which is useable as a doctest checker.

>>> normalizer = m01.mongofake.RENormalizer([
... (re.compile('[0-9]*[.][0-9]* seconds'), '... seconds'),
... (re.compile('at 0x[0-9a-f]+'), 'at ...'),
... ])

>>> text = """
... <object object at 0xb7f14438>
... completed in 1.234 seconds.
... ...
... <object object at 0xb7f14450>
... completed in 1.234 seconds.
... """

>>> print normalizer(text)
<BLANKLINE>
<object object at ...>
completed in ... seconds.
...
<object object at ...>
completed in ... seconds.
<BLANKLINE>

Now let's test some mongodb relevant stuff:

>>> from bson.dbref import DBRef
>>> from bson.min_key import MinKey
>>> from bson.max_key import MaxKey
>>> from bson.objectid import ObjectId
>>> from bson.timestamp import Timestamp

>>> import time
>>> import calendar
>>> import struct
>>> def getObjectId(secs=0):
... """Knows how to generate similar ObjectId based on integer (counter)"""
... time_tuple = time.gmtime(secs)
... ts = calendar.timegm(time_tuple)
... oid = struct.pack(">i", int(ts)) + "\x00" * 8
... return ObjectId(oid)

>>> oid = getObjectId(42)
>>> oid
ObjectId('0000002a0000000000000000')

>>> data = {'oid': oid,
... 'dbref': DBRef("foo", 5, "db"),
... 'date': datetime.datetime(2011, 5, 7, 1, 12),
... 'utc': datetime.datetime(2011, 5, 7, 1, 12, tzinfo=bson.tz_util.utc),
... 'min': MinKey(),
... 'max': MaxKey(),
... 'timestamp': Timestamp(4, 13),
... 're': re.compile("a*b", re.IGNORECASE),
... 'string': 'string',
... 'unicode': u'unicode',
... 'int': 42}

Now let's pretty print the data:

>>> pprint(data)
{'date': datetime.datetime(2011, 5, 7, 1, 12),
'dbref': DBRef('foo', 5, 'db'),
'int': 42,
'max': MaxKey(),
'min': MinKey(),
'oid': ObjectId('0000002a0000000000000000'),
're': <_sre.SRE_Pattern object at ...>,
'string': 'string',
'timestamp': Timestamp(4, 13),
'unicode': u'unicode',
'utc': datetime.datetime(2011, 5, 7, 1, 12, tzinfo=<bson.tz_util.FixedOffset object at ...>)}


reNormalizer
------------

As you can see our predefined reNormalizer will convert the values using our
given patterns:

>>> import m01.mongofake
>>> print m01.mongofake.reNormalizer(data)
{'date': datetime.datetime(...),
'dbref': DBRef('foo', 5, 'db'),
'int': 42,
'max': MaxKey(),
'min': MinKey(),
'oid': ObjectId('...'),
're': <_sre.SRE_Pattern object at ...>,
'string': 'string',
'timestamp': Timestamp('...'),
'unicode': u'unicode',
'utc': datetime(..., tzinfo=<bson.tz_util.FixedOffset ...>)}


pprint
------

>>> m01.mongofake.reNormalizer.pprint(data)
{'date': datetime.datetime(...),
'dbref': DBRef('foo', 5, 'db'),
'int': 42,
'max': MaxKey(),
'min': MinKey(),
'oid': ObjectId('...'),
're': <_sre.SRE_Pattern object at ...>,
'string': 'string',
'timestamp': Timestamp('...'),
'unicode': u'unicode',
'utc': datetime(..., tzinfo=<bson.tz_util.FixedOffset ...>)}


dictify
-------

>>> import bson.son
>>> son = bson.son.SON(data)
>>> type(son)
<class 'bson.son.SON'>

>>> res = m01.mongofake.dictify(son)
>>> type(res)
<type 'dict'>

>>> m01.mongofake.pprint(res)
{'date': datetime.datetime(2011, 5, 7, 1, 12),
'dbref': DBRef('foo', 5, 'db'),
'int': 42,
'max': MaxKey(),
'min': MinKey(),
'oid': ObjectId('0000002a0000000000000000'),
're': <_sre.SRE_Pattern object at ...>,
'string': 'string',
'timestamp': Timestamp(4, 13),
'unicode': u'unicode',
'utc': datetime.datetime(2011, 5, 7, 1, 12, tzinfo=<bson.tz_util.FixedOffset object at ...>)}
37 changes: 25 additions & 12 deletions src/m01/mongofake/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,19 @@
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""
$Id$
"""
__docformat__ = "reStructuredText"

import copy
import re
import types
import pprint as pp

from bson import objectid
from bson import son
from pymongo import cursor
import bson.objectid
import bson.son
import pymongo.cursor


###############################################################################
Expand All @@ -31,16 +34,26 @@

# SON to dict converter
def dictify(data):
"""Recursive replace SON instance with plain a dict"""
"""Recursive replace SON items with dict in the given data structure.
Compared to the SON.to_dict method, this method will also handle tuples
and keep them intact.
"""
if isinstance(data, bson.son.SON):
data = dict(data)
if isinstance(data, dict):
d = {}
for k, v in data.items():
for k, v in data.iteritems():
# replace nested SON items
d[k] = dictify(v)
elif isinstance(data, (tuple, list)):
d = []
for v in data:
# replace nested SON items
d.append(dictify(v))
if isinstance(data, tuple):
# keep tuples intact
d = tuple(d)
else:
d = data
Expand Down Expand Up @@ -81,7 +94,7 @@ def __call__(self, data):

def pprint(self, data):
"""Pretty print data"""
if isinstance(data, cursor.Cursor):
if isinstance(data, pymongo.cursor.Cursor):
for item in data:
print self(item)
else:
Expand Down Expand Up @@ -370,7 +383,7 @@ def insert(self, doc_or_docs, manipulate=True, safe=False, check_keys=True,
for doc in docs:
oid = doc.get('_id')
if oid is None:
oid = objectid.ObjectId()
oid = bson.objectid.ObjectId()
doc[u'_id'] = oid
d = {}
for k, v in list(doc.items()):
Expand All @@ -389,9 +402,9 @@ def find_one(self, spec_or_object_id=None, fields=None, slave_okay=True,
_sock=None, _must_use_master=False):
spec = spec_or_object_id
if spec is None:
spec = son.SON()
if isinstance(spec, objectid.ObjectId):
spec = son.SON({"_id": spec})
spec = bson.son.SON()
if isinstance(spec, bson.objectid.ObjectId):
spec = bson.son.SON({"_id": spec})

for result in self.find(spec, limit=-1, fields=fields,
slave_okay=slave_okay, _sock=_sock,
Expand All @@ -404,7 +417,7 @@ def find(self, spec=None, fields=None, skip=0, limit=0,
sort=None,
_sock=None, _must_use_master=False):
if spec is None:
spec = son.SON()
spec = bson.son.SON()

if not isinstance(spec, types.DictType):
raise TypeError("spec must be an instance of dict")
Expand Down Expand Up @@ -435,7 +448,7 @@ def find(self, spec=None, fields=None, skip=0, limit=0,

def remove(self, spec_or_object_id, safe=False):
spec = spec_or_object_id
if isinstance(spec, objectid.ObjectId):
if isinstance(spec, bson.objectid.ObjectId):
spec = {"_id": spec}

if not isinstance(spec, types.DictType):
Expand Down
32 changes: 32 additions & 0 deletions src/m01/mongofake/tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
##############################################################################
#
# Copyright (c) 2012 Zope Foundation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""
$Id$
"""
__docformat__ = "reStructuredText"

import unittest
import doctest


def test_suite():
return unittest.TestSuite((
doctest.DocFileSuite('README.txt',
optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS,
),
))


if __name__=='__main__':
unittest.main(defaultTest='test_suite')

0 comments on commit f5974d9

Please sign in to comment.