# $SnapHashLicense:
#
# SnapLogic - Open source data services
#
# Copyright (C) 2008, SnapLogic, Inc. All rights reserved.
#
# See http://www.snaplogic.org for more information about
# the SnapLogic project.
#
# This program is free software, distributed under the terms of
# the GNU General Public License Version 2. See the LEGAL file
# at the top of the source tree.
#
# "SnapLogic" is a trademark of SnapLogic, Inc.
#
#
# $
#$Id: json_rp.py 4819 2008-10-27 06:01:16Z grisha $
from decimal import Decimal
from datetime import datetime
from UserList import UserList
from StringIO import StringIO
from simplejson.encoder import JSONEncoder
from simplejson.decoder import JSONDecoder
import simplejson
from snaplogic.rp import _RPReader,_RPWriter
from snaplogic.common.snap_exceptions import *
EOS_MARKER = Decimal('0')
CONTENT_TYPE = 'application/json'
class _Encoder(JSONEncoder):
"""
Encoder for handling Snap types: L{datetime.datetime}, L{decimal.Decimal}, and L{UserList} --
see L{DataTypes._PassthroughList}.
"""
def __init__(self, *args, **kwargs):
kwargs['sort_keys'] = True
kwargs['indent'] = 4
super(_Encoder, self).__init__(*args, **kwargs)
# DRY -- similar code is in Asn1RP.
DATETIME_ATTRIBUTES = ['year','month','day','hour','minute','second','microsecond']
"""Attributes of datetime.datetime type that we will serialize."""
def default(self, obj):
"""
Overriding the template method. See default() method of L{JSONEncoder}.
"""
if type(obj) == datetime:
datetime_dict = {}
datetime_dict['__snaptype__'] = 'datetime'
datetime_dict['__values__'] = [obj.__getattribute__(attr) for attr in _Encoder.DATETIME_ATTRIBUTES]
retval = datetime_dict
elif isinstance(obj, UserList):
retval = obj.data
elif type(obj) == Decimal:
# if obj._isinteger():
# obj = obj.__long__()
# else:
# obj = obj.__float__()
retval = {'__snaptype__' : 'Decimal', '__values__' : [str(obj)]}
else:
retval = JSONEncoder.default(obj)
# print "%s of type %s aka %s" % (obj, type(obj), obj.__class__)
# print 'Becomes: %s' % retval
return retval
class Writer(_RPWriter):
"""
Writes out Python objects as JSON. All records are sent in the single JSON document representing
an array.
"""
def __init__(self, stream, version=None, human_request=False, options=False):
super(Writer, self).__init__(stream)
def initialize(self, header = ""):
"""Prepare the underlying stream for sending the data."""
s = '%s[' % header
self.stream.write(s)
return s
def write(self, raw_record, options=None):
"""
Write a Snap Record to the underlying stream.
"""
s = ''
if not hasattr(self, 'not_first'):
self.not_first = True
else:
s += ', '
s += simplejson.dumps(obj=raw_record, cls=_Encoder)
self.stream.write(s)
return s
def end(self, footer=""):
s = ']%s' % footer
self.stream.write(s)
return s
class Reader(_RPReader):
"""
Reads a JSON stream, returning Python objects.
"""
def __init__(self, stream, version=None):
super(Reader, self).__init__(stream)
self._iter = None
self._idx = 0
self._nb_buf = None
self._closed = False
def object_hook(self, dct):
"""
Object hook for simplejson's decoder, to decode complex types
(Decimal, datetime). See L{simplejson.loads}.
"""
if '__snaptype__' in dct:
t = dct['__snaptype__']
if t == 'datetime':
cls = datetime
elif t == 'Decimal':
cls = Decimal
if cls:
args = dct['__values__']
dct = cls(*args)
else:
pass
return dct
@classmethod
def supports_non_blocking_read(cls):
"""
Returns True if this Reader class supports non-blocking read (see L{read_nb})
@return: True if this Reader supports non-blocking read, False otherwise
@rtype: bool
"""
return True
def next(self):
return self.__iter__().next()
def read_nb(self):
"""
See L{_RPReader.read_nb}
"""
# Quick and dirty way for now.
if self._nb_buf is None:
self._nb_buf = StringIO()
self._nb_buf.seek(0, 2)
s = self.stream.read()
if s is None:
if self._closed:
return None
else:
return ''
if s:
self._nb_buf.write(s)
else:
return ''
try:
result = simplejson.loads(self._nb_buf.getvalue(), object_hook=self.object_hook)
self._nb_buf.truncate(0)
self._closed = True
return result
except Exception, e:
return ''
def __iter__(self):
# TODO:
# This is good enough for now (Beale), but in the future
# this needs to parse the streaming data, SAX-like, not collect the
# entire stream into a buffer and only then parse...
if not self._iter:
done = False
s = ''
while not done:
buf = self.stream.read(1024)
s += buf
if (len(buf)) < 1024:
done = True
list = simplejson.loads(s, object_hook=self.object_hook)
self._iter = list.__iter__()
return self._iter
|