Skip to content

Commit

Permalink
Add support for post processing decoded payloads
Browse files Browse the repository at this point in the history
  • Loading branch information
njoyce committed Jan 25, 2015
1 parent 9eac195 commit 6db5b03
Show file tree
Hide file tree
Showing 6 changed files with 152 additions and 2 deletions.
4 changes: 4 additions & 0 deletions cpyamf/codec.pxd
Expand Up @@ -74,6 +74,8 @@ cdef class Codec(object):


cdef class Decoder(Codec):
cdef unsigned int depth

cdef object readDate(self)
cpdef object readString(self)
cdef object readObject(self)
Expand All @@ -83,10 +85,12 @@ cdef class Decoder(Codec):
cdef object readList(self)
cdef object readXML(self)

cdef object _readElement(self)
cpdef object readElement(self)
cdef object readConcreteElement(self, char t)

cpdef int send(self, data) except -1
cdef int finalise(self, object payload) except? -1


cdef class Encoder(Codec):
Expand Down
32 changes: 31 additions & 1 deletion cpyamf/codec.pyx
Expand Up @@ -356,6 +356,9 @@ cdef class Decoder(Codec):
Base AMF decoder.
"""

def __cinit__(self):
self.depth = 0

cdef object readDate(self):
raise NotImplementedError

Expand All @@ -380,7 +383,7 @@ cdef class Decoder(Codec):
cdef object readXML(self):
raise NotImplementedError

cpdef object readElement(self):
cdef object _readElement(self):
"""
Reads an element from the data stream.
"""
Expand All @@ -399,6 +402,21 @@ cdef class Decoder(Codec):

raise

cpdef object readElement(self):
cdef object element

self.depth += 1

try:
element = self._readElement()
finally:
self.depth -= 1

if self.depth == 0:
self.finalise(element)

return element

cdef object readConcreteElement(self, char t):
"""
The workhorse function. Overridden in subclasses
Expand All @@ -424,6 +442,18 @@ cdef class Decoder(Codec):
def __iter__(self):
return self

cdef int finalise(self, object payload) except? -1:
"""
Finalise the payload.
This provides a useful hook to adapters to modify the payload that was
decoded.
"""
for c in pyamf.POST_DECODE_PROCESSORS:
c(payload, self.context.extra)

return 0


cdef class Encoder(Codec):
"""
Expand Down
19 changes: 19 additions & 0 deletions pyamf/__init__.py
Expand Up @@ -62,6 +62,9 @@
#: L{unregister_alias_type}
ALIAS_TYPES = {}

#: A list of callbacks to execute once a decode has been successful.
POST_DECODE_PROCESSORS = []

#: Specifies that objects are serialized using AMF for ActionScript 1.0
#: and 2.0 that were introduced in the Adobe Flash Player 6.
AMF0 = 0
Expand Down Expand Up @@ -917,6 +920,22 @@ def set_default_etree(etree):
return xml.set_default_interface(etree)


def add_post_decode_processor(func):
"""
Adds a function to be called when a payload has been successfully decoded.
This is useful for adapter as the last chance to modify the Python graph
before it enters user land.
@see: L{pyamf.codec.Decoder.finalise}
@since: 0.7.0
"""
if not python.callable(func):
raise TypeError('%r must be callable' % (func,))

POST_DECODE_PROCESSORS.append(func)


# setup some some standard class registrations and class loaders.
register_class(ASObject)
register_class_loader(flex_loader)
Expand Down
39 changes: 38 additions & 1 deletion pyamf/codec.py
Expand Up @@ -318,6 +318,11 @@ class Decoder(_Codec):
@type strict: C{bool}
"""

def __init__(self, *args, **kwargs):
_Codec.__init__(self, *args, **kwargs)

self.__depth = 0

def send(self, data):
"""
Add data for the decoder to work on.
Expand All @@ -334,7 +339,20 @@ def next(self):
# all data was successfully decoded from the stream
raise StopIteration

def readElement(self):
def finalise(self, payload):
"""
Finalise the payload.
This provides a useful hook to adapters to modify the payload that was
decoded.
Note that this is an advanced feature and is NOT directly called by the
decoder.
"""
for c in pyamf.POST_DECODE_PROCESSORS:
c(payload, self.context.extra)

def _readElement(self):
"""
Reads an AMF3 element from the data stream.
Expand Down Expand Up @@ -366,6 +384,25 @@ def readElement(self):

raise

def readElement(self):
"""
Reads an AMF3 element from the data stream.
@raise DecodeError: The ActionScript type is unsupported.
@raise EOStream: No more data left to decode.
"""
self.__depth += 1

try:
element = self._readElement()
finally:
self.__depth -= 1

if self.__depth == 0:
self.finalise(element)

return element

def __iter__(self):
return self

Expand Down
30 changes: 30 additions & 0 deletions pyamf/tests/test_amf0.py
Expand Up @@ -905,6 +905,36 @@ def test_numerical_keys_mixed_array(self):

self.assertEqual(d, [{10: u'foobar'}])

def test_post_process(self):
"""
Ensure that postprocessing happens when data has been decoded.
"""
self.executed = False

post_procs = pyamf.POST_DECODE_PROCESSORS[:]

def restore_post_procs():
pyamf.POST_DECODE_PROCESSORS = post_procs

self.addCleanup(restore_post_procs)
pyamf.POST_DECODE_PROCESSORS = []

def postprocess(payload, context):
self.assertEqual(payload, u'foo')
self.assertEqual(context, {})

self.executed = True

pyamf.add_post_decode_processor(postprocess)

# setup complete
bytes = pyamf.encode(u'foo', encoding=pyamf.AMF0).getvalue()

self.decoder.send(bytes)
self.decoder.next()

self.assertTrue(self.executed)


class RecordSetTestCase(unittest.TestCase, EncoderMixIn, DecoderMixIn):
"""
Expand Down
30 changes: 30 additions & 0 deletions pyamf/tests/test_amf3.py
Expand Up @@ -921,6 +921,36 @@ def f(**kwargs):

f(**kwargs)

def test_post_process(self):
"""
Ensure that postprocessing happens when data has been decoded.
"""
self.executed = False

post_procs = pyamf.POST_DECODE_PROCESSORS[:]

def restore_post_procs():
pyamf.POST_DECODE_PROCESSORS = post_procs

self.addCleanup(restore_post_procs)
pyamf.POST_DECODE_PROCESSORS = []

def postprocess(payload, context):
self.assertEqual(payload, u'foo')
self.assertEqual(context, {})

self.executed = True

pyamf.add_post_decode_processor(postprocess)

# setup complete
bytes = pyamf.encode(u'foo', encoding=pyamf.AMF3).getvalue()

self.decoder.send(bytes)
self.decoder.next()

self.assertTrue(self.executed)


class ObjectEncodingTestCase(ClassCacheClearingTestCase, EncoderMixIn):
"""
Expand Down

0 comments on commit 6db5b03

Please sign in to comment.