Browse files

added json serializer for a record

added two methods on the Record class (pymarc/record.py)

1) ``as_dict(self)`` - which turns a record into a simplified dict,
   which will be used by ``as_json(self)``

2) ``as_json(self, **kwargs)`` - which serializes a record and returns a
   JSON string.

In order for this to work, python 2.6 is preferable because of the
stdlib's ``json`` module and some ``itertools`` functions. However,
I added some safety checks, whether ``json`` is actually available.
If it's not, the ``simplejson`` package will be used (available for python 2.3+).

On the ``itertools`` side, I added the necessary function
``izip_longest``.

``setup.py`` will check the python version and will add simplejson as
dependency if a version below 2.6 is used.

Added some basic tests in a new file: test/json_test.py

Tested on OS X with python versions 2.5, 2.6 and 2.7
  • Loading branch information...
1 parent 558a70f commit 245ea6d7bceaec7215abe788d61a0b34a6cd849e @miku miku committed Nov 11, 2011
Showing with 150 additions and 0 deletions.
  1. +82 −0 pymarc/record.py
  2. +5 −0 setup.py
  3. +2 −0 test.py
  4. +61 −0 test/json_test.py
View
82 pymarc/record.py
@@ -8,6 +8,65 @@
map_marc8_field
from pymarc.marc8 import marc8_to_unicode
+try:
+ # the json module was included in the stdlib in python 2.6
+ # http://docs.python.org/library/json.html
+ import json
+except ImportError:
+ # simplejson 2.0.9 is available for python 2.4+
+ # http://pypi.python.org/pypi/simplejson/2.0.9
+ # simplejson 1.7.3 is available for python 2.3+
+ # http://pypi.python.org/pypi/simplejson/1.7.3
+ import simplejson as json
+
+try:
+ # izip_longest first appeared in python 2.6
+ # http://docs.python.org/library/itertools.html#itertools.izip_longest
+ from itertools import izip_longest
+except ImportError:
+ # itertools was introducted in python 2.3
+ # we just define the required classes and functions
+ # for 2.3 <= python < 2.6 here
+ class ZipExhausted(Exception):
+ pass
+
+ def _next(obj):
+ """
+ ``next`` (http://docs.python.org/library/functions.html#next)
+ was introduced in python 2.6 - and if we are here
+ (no ``izip_longest``), than we need to define this."""
+ return obj.next()
+
+ def izip_longest(*args, **kwds):
+ """
+ Make an iterator that aggregates elements from each of the iterables.
+ If the iterables are of uneven length, missing values are filled-in
+ with fillvalue.
+ Iteration continues until the longest iterable is exhausted.
+
+ This function is available in the standard lib since 2.6.
+ """
+ # chain and repeat are available since python 2.3
+ from itertools import chain, repeat
+
+ # izip_longest('ABCD', 'xy', fillvalue='-') --> Ax By C- D-
+ fillvalue = kwds.get('fillvalue', '')
+ counter = [len(args) - 1]
+ def sentinel():
+ if not counter[0]:
+ raise ZipExhausted
+ counter[0] -= 1
+ yield fillvalue
+ fillers = repeat(fillvalue)
+ iterators = [chain(it, sentinel(), fillers) for it in args]
+ try:
+ while iterators:
+ yield tuple(map(_next, iterators))
+ except ZipExhausted:
+ pass
+ finally:
+ del chain
+
isbn_regex = re.compile(r'([0-9\-]+)')
class Record(object):
@@ -251,6 +310,29 @@ def as_marc(self):
# alias for backwards compatability
as_marc21 = as_marc
+ def as_dict(self):
+ """
+ Turn a MARC record into a dict, which is used for ``as_json``.
+ """
+ _dict = {}
+ _dict['leader'] = self.leader
+ _dict['fields'] = {}
+ for field in self:
+ if hasattr(field, 'subfields'):
+ _dict['fields'][field.tag] = {}
+ _dict['fields'][field.tag]['indicators'] = field.indicators
+ _dict['fields'][field.tag]['subfields'] = dict(
+ izip_longest(*[iter(field.subfields)] * 2))
+ else:
+ _dict['fields'][field.tag] = field.data
+ return _dict
+
+ def as_json(self, **kwargs):
+ """
+ Serialize a record as JSON.
+ """
+ return json.dumps(self.as_dict(), **kwargs)
+
def title(self):
"""
Returns the title of the record (245 $a an $b).
View
5 setup.py
@@ -13,6 +13,11 @@
except ImportError:
install_requires.append('elementtree>=1.2.6')
+import sys
+if sys.version_info < (2 , 6):
+ install_requires.append('simplejson>=1.7.3')
+del sys
+
classifiers = """
Intended Audience :: Education
Intended Audience :: Developers
View
2 test.py
@@ -6,6 +6,7 @@
from test import writer
from test import marc8
from test import xml_test
+from test import json_test
def suite():
@@ -17,6 +18,7 @@ def suite():
test_suite.addTest(writer.suite())
test_suite.addTest(marc8.suite())
test_suite.addTest(xml_test.suite())
+ test_suite.addTest(json_test.suite())
return test_suite
runner = unittest.TextTestRunner()
View
61 test/json_test.py
@@ -0,0 +1,61 @@
+# -*- coding: utf-8 -*-
+
+import unittest
+import pymarc
+
+try:
+ import json
+except ImportError:
+ import simplejson as json
+
+class JsonTest(unittest.TestCase):
+
+ def setUp(self):
+ self.reader = pymarc.MARCReader(file('test/test.dat'))
+ self._record = pymarc.Record()
+ field = pymarc.Field(
+ tag = '245',
+ indicators = ['1', '0'],
+ subfields = ['a', 'Python', 'c', 'Guido'])
+ self._record.add_field(field)
+
+ def test_as_dict_single(self):
+ _expected = {
+ 'fields': {
+ '245': {
+ 'indicators': ['1', '0'],
+ 'subfields': {'a': 'Python', 'c': 'Guido'}
+ }
+ },
+ 'leader': ' 22 4500'
+ }
+ self.assertEqual(_expected, self._record.as_dict())
+
+ def test_as_dict_multiple(self):
+ for record in self.reader:
+ self.assertTrue(dict, record.as_dict().__class__)
+ self.assertTrue('fields' in record.as_dict())
+ self.assertTrue('leader' in record.as_dict())
+
+ def test_as_json_simple(self):
+ self.assertTrue('leader' in json.loads(self._record.as_json()))
+ self.assertTrue('fields' in json.loads(self._record.as_json()))
+ self.assertTrue('245' in json.loads(self._record.as_json())['fields'])
+ self.assertTrue('indicators' in json.loads(self._record.as_json())['fields']['245'])
+ self.assertTrue('subfields' in json.loads(self._record.as_json())['fields']['245'])
+ self.assertEquals(dict, json.loads(self._record.as_json())['fields']['245'].__class__)
+ self.assertEquals(list, json.loads(self._record.as_json())['fields']['245']['indicators'].__class__)
+ self.assertEquals(dict, json.loads(self._record.as_json())['fields']['245']['subfields'].__class__)
+
+ def test_as_json_multiple(self):
+ for record in self.reader:
+ self.assertTrue(basestring in record.as_json().__class__.__bases__)
+ self.assertEquals(dict, json.loads(record.as_json()).__class__)
+
+def suite():
+ test_suite = unittest.makeSuite(JsonTest, 'test')
+ return test_suite
+
+if __name__ == '__main__':
+ unittest.main()
+

0 comments on commit 245ea6d

Please sign in to comment.