Commit 7f80dfe8 authored by Jacek Furmankiewicz's avatar Jacek Furmankiewicz

serializing of classes to XML,JSON,YAML

parent 6051d011
...@@ -9,4 +9,7 @@ class Response: ...@@ -9,4 +9,7 @@ class Response:
def __init__(self,code=200,entity=None,headers={}): def __init__(self,code=200,entity=None,headers={}):
self.code = code self.code = code
self.entity=entity self.entity=entity
self.headers=headers self.headers=headers
\ No newline at end of file
def __str__(self):
return str(self.__dict__)
\ No newline at end of file
'''
Created on 2011-10-11
@author: jacekf
Responsible for converting return values into cleanly serializable dict/tuples/lists
for JSON/XML/YAML output
'''
import inspect, collections
from jinja2 import Template
xmlListTemplate = Template("""<list>{% for item in items %}<item>{% for prop,val in item.iteritems() %}<{{prop}}>{{val}}</{{prop}}>{% endfor %}</item>{% endfor %}</list>""")
xmlTemplate = Template("""<item>{% for prop,val in item.iteritems() %}<{{prop}}>{{val}}</{{prop}}>{% endfor %}</item>""")
def convertForSerialization(object):
"""Converts anything (clas,tuples,list) to the safe serializable equivalent"""
if isinstance(object, dict):
return traverseDict(object)
elif isClassInstance(object):
return convertClassToDict(object)
elif isinstance(object,collections.Iterable):
# iterable
values = []
for val in object:
values.append(convertForSerialization(val))
return values
else:
# return as-is
return object
def convertClassToDict(clazz):
"""Converts a class to a dictionary"""
properties = {}
for prop,val in clazz.__dict__.iteritems():
#omit private fields
if not prop.startswith("_"):
properties[prop] = val
return traverseDict(properties)
def traverseDict(dictObject):
"""Traverses a dict recursively to convertForSerialization any nested classes"""
newDict = {}
for prop,val in dictObject.iteritems():
if inspect.isclass(val):
# call itself recursively
val = convertClassToDict(val)
newDict[prop] = val
return newDict
def generateXml(object):
"""Generates basic XML from an object that has already been converted for serialization"""
if isinstance(object,dict):
return str(xmlTemplate.render(item=object.keys()))
elif isinstance(object,collections.Iterable):
return str(xmlListTemplate.render(items=object))
else:
raise RuntimeError("Unable to convert to XML: %s" % object)
def isClassInstance(object):
"""Checks if a given object is a class instance"""
return getattr(object, "__class__",None) != None and not isinstance(object,dict) and not isinstance(object,tuple) and not isinstance(object,list)
\ No newline at end of file
...@@ -7,6 +7,7 @@ Common routing classes, regardless of whether used in HTTP or ZeroMQ context ...@@ -7,6 +7,7 @@ Common routing classes, regardless of whether used in HTTP or ZeroMQ context
from collections import defaultdict from collections import defaultdict
from corepost.enums import Http, HttpHeader from corepost.enums import Http, HttpHeader
from corepost.utils import getMandatoryArgumentNames, convertToJson from corepost.utils import getMandatoryArgumentNames, convertToJson
from corepost.convert import convertForSerialization, generateXml
from corepost import Response from corepost import Response
from enums import MediaType from enums import MediaType
from formencode import FancyValidator, Invalid from formencode import FancyValidator, Invalid
...@@ -251,6 +252,8 @@ class RequestRouter: ...@@ -251,6 +252,8 @@ class RequestRouter:
Takes care of converting an object (non-String) response to the appropriate format, based on the what the caller can accept. Takes care of converting an object (non-String) response to the appropriate format, based on the what the caller can accept.
Returns a tuple of (content,contentType) Returns a tuple of (content,contentType)
""" """
obj = convertForSerialization(obj)
if HttpHeader.ACCEPT in request.received_headers: if HttpHeader.ACCEPT in request.received_headers:
accept = request.received_headers[HttpHeader.ACCEPT] accept = request.received_headers[HttpHeader.ACCEPT]
if MediaType.APPLICATION_JSON in accept: if MediaType.APPLICATION_JSON in accept:
...@@ -258,10 +261,7 @@ class RequestRouter: ...@@ -258,10 +261,7 @@ class RequestRouter:
elif MediaType.TEXT_YAML in accept: elif MediaType.TEXT_YAML in accept:
return (yaml.dump(obj),MediaType.TEXT_YAML) return (yaml.dump(obj),MediaType.TEXT_YAML)
elif MediaType.APPLICATION_XML in accept or MediaType.TEXT_XML in accept: elif MediaType.APPLICATION_XML in accept or MediaType.TEXT_XML in accept:
if isinstance(obj,Element): return (generateXml(obj),MediaType.APPLICATION_XML+";charset=utf-8")
return (ElementTree.tostring(obj, encoding='utf-8'),MediaType.APPLICATION_XML)
else:
raise RuntimeError("Unable to convert String response to XML automatically")
else: else:
# no idea, let's do JSON # no idea, let's do JSON
return (convertToJson(obj),MediaType.APPLICATION_JSON) return (convertToJson(obj),MediaType.APPLICATION_JSON)
...@@ -271,7 +271,9 @@ class RequestRouter: ...@@ -271,7 +271,9 @@ class RequestRouter:
def __finishDeferred(self,val,request): def __finishDeferred(self,val,request):
"""Finishes any Defered/inlineCallback methods. Returns Response""" """Finishes any Defered/inlineCallback methods. Returns Response"""
if val != None: if isinstance(val,Response):
return val
elif val != None:
try: try:
return self.__generateResponse(request,val) return self.__generateResponse(request,val)
except Exception as ex: except Exception as ex:
......
...@@ -181,12 +181,12 @@ total: 4443.52 ...@@ -181,12 +181,12 @@ total: 4443.52
And I expect content contains '<content>' And I expect content contains '<content>'
Examples: Examples:
| content | accept | code | | content | accept | code |
| [{"test1": "Test1"}, {"test2": "Test2"}] | application/json | 200 | | [{"test1": "Test1"}, {"test2": "Test2"}] | application/json | 200 |
| Unable to convert String response to XML automatically | application/xml | 500 | # not supported yet | <list><item><test1>Test1</test1></item><item><test2>Test2</test2></item></list> | application/xml | 200 |
| - {test1: Test1}\n- {test2: Test2} | text/yaml | 200 | | - {test1: Test1}\n- {test2: Test2} | text/yaml | 200 |
@json @yaml @xml @return_accept_deferred @tmp @json @yaml @xml @return_accept_deferred
Scenario Outline: Return content type based on caller's Accept from Deferred methods Scenario Outline: Return content type based on caller's Accept from Deferred methods
When I prepare HTTP header 'Accept' = '<accept>' When I prepare HTTP header 'Accept' = '<accept>'
When as user 'None:None' I GET 'http://127.0.0.1:8080/return/by/accept/deferred' When as user 'None:None' I GET 'http://127.0.0.1:8080/return/by/accept/deferred'
...@@ -194,12 +194,12 @@ total: 4443.52 ...@@ -194,12 +194,12 @@ total: 4443.52
And I expect content contains '<content>' And I expect content contains '<content>'
Examples: Examples:
| content | accept | code | | content | accept | code |
| [{"test1": "Test1"}, {"test2": "Test2"}] | application/json | 200 | | [{"test1": "Test1"}, {"test2": "Test2"}] | application/json | 200 |
#| Unable to convert String response to XML automatically | application/xml | 500 | # not supported yet | <list><item><test1>Test1</test1></item><item><test2>Test2</test2></item></list> | application/xml | 200 |
#| - {test1: Test1}\n- {test2: Test2} | text/yaml | 200 | | - {test1: Test1}\n- {test2: Test2} | text/yaml | 200 |
@json @yaml @xml @return_accept @json @yaml @xml @return_accept @tmp
Scenario Outline: Return class content type based on caller's Accept Scenario Outline: Return class content type based on caller's Accept
When I prepare HTTP header 'Accept' = '<accept>' When I prepare HTTP header 'Accept' = '<accept>'
When as user 'None:None' I GET 'http://127.0.0.1:8080/return/by/accept/class' When as user 'None:None' I GET 'http://127.0.0.1:8080/return/by/accept/class'
...@@ -207,7 +207,8 @@ total: 4443.52 ...@@ -207,7 +207,8 @@ total: 4443.52
And I expect content contains '<content>' And I expect content contains '<content>'
Examples: Examples:
| content | accept | code | | content | accept | code |
| [{}, {}] | application/json | 200 | # not supported yet | [{"test1": "Test1"}, {"test2": "Test2"}] | application/json | 200 | # not supported yet
| Unable to convert String response to XML automatically | application/xml | 500 | # not supported yet | <list><item><test1>Test1</test1></item><item><test2>Test2</test2></item></list> | application/xml | 200 | # not supported yet
| - {test1: Test1}\n- {test2: Test2} | text/yaml | 200 | # not supported yet
\ No newline at end of file
...@@ -92,14 +92,17 @@ class HomeApp(CorePost): ...@@ -92,14 +92,17 @@ class HomeApp(CorePost):
@route("/return/by/accept/class") @route("/return/by/accept/class")
def test_return_class_content_by_accepts(self,request,**kwargs): def test_return_class_content_by_accepts(self,request,**kwargs):
"""Uses Python class instead of dict/list""" """Uses Python class instead of dict/list"""
class Test(UserDict,dict):
pass
t1 = Test() class TestReturn:
t1.test1="Test1" """Test return class"""
t2 = Test() def __init__(self):
self.__t1 = 'Test'
t1 = TestReturn()
t1.test1 = 'Test1'
t2 = TestReturn()
t2.test2="Test2" t2.test2="Test2"
val = [t1,t2]
return (t1,t2) return (t1,t2)
......
...@@ -3,7 +3,6 @@ Various CorePost utilities ...@@ -3,7 +3,6 @@ Various CorePost utilities
''' '''
from inspect import getargspec from inspect import getargspec
import json import json
from corepost.enums import MediaType
def getMandatoryArgumentNames(f): def getMandatoryArgumentNames(f):
'''Returns a tuple of the mandatory arguments required in a function''' '''Returns a tuple of the mandatory arguments required in a function'''
...@@ -23,10 +22,3 @@ def convertToJson(obj): ...@@ -23,10 +22,3 @@ def convertToJson(obj):
return json.dumps(obj) return json.dumps(obj)
except Exception as ex: except Exception as ex:
raise RuntimeError(str(ex)) raise RuntimeError(str(ex))
def applyResponse(self,request,code,headers={"content-type":MediaType.TEXT_PLAIN}):
"""Applies response to current request"""
request.setResponseCode(code)
if headers != None:
for header,value in headers.iteritems():
request.setHeader(header, value)
...@@ -51,6 +51,7 @@ class CorePost(Resource): ...@@ -51,6 +51,7 @@ class CorePost(Resource):
def __renderUrl(self,request): def __renderUrl(self,request):
try: try:
val = self.__router.getResponse(request) val = self.__router.getResponse(request)
# return can be Deferred or Response # return can be Deferred or Response
if isinstance(val,Deferred): if isinstance(val,Deferred):
val.addCallback(self.__finishRequest,request) val.addCallback(self.__finishRequest,request)
......
...@@ -63,7 +63,7 @@ def read(fname): ...@@ -63,7 +63,7 @@ def read(fname):
setup( setup(
name="CorePost", name="CorePost",
version="0.0.7", version="0.0.8",
author="Jacek Furmankiewicz", author="Jacek Furmankiewicz",
author_email="jacekeadE99@gmail.com", author_email="jacekeadE99@gmail.com",
description=("A Twisted Web REST micro-framework"), description=("A Twisted Web REST micro-framework"),
...@@ -88,6 +88,7 @@ setup( ...@@ -88,6 +88,7 @@ setup(
'freshen>=0.2', 'freshen>=0.2',
'formencode>=1.2.4', 'formencode>=1.2.4',
'pyyaml>=3.1.0', 'pyyaml>=3.1.0',
'jinja2>=2.6'
], ],
) )
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment