The Resource object in shared/dav.py is now able to send SOAP requests, with marshalled and unmarshalled Python objects.
This commit is contained in:
parent
ca6dd26906
commit
3d87036f85
25
__init__.py
25
__init__.py
|
@ -1,5 +1,9 @@
|
||||||
|
'''Appy allows you to create easily complete applications in Python.'''
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
import os.path
|
import os.path
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
def getPath(): return os.path.dirname(__file__)
|
def getPath(): return os.path.dirname(__file__)
|
||||||
def versionIsGreaterThanOrEquals(version):
|
def versionIsGreaterThanOrEquals(version):
|
||||||
'''This method returns True if the current Appy version is greater than or
|
'''This method returns True if the current Appy version is greater than or
|
||||||
|
@ -12,3 +16,24 @@ def versionIsGreaterThanOrEquals(version):
|
||||||
paramVersion = [int(i) for i in version.split('.')]
|
paramVersion = [int(i) for i in version.split('.')]
|
||||||
currentVersion = [int(i) for i in appy.version.short.split('.')]
|
currentVersion = [int(i) for i in appy.version.short.split('.')]
|
||||||
return currentVersion >= paramVersion
|
return currentVersion >= paramVersion
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
class Object:
|
||||||
|
'''At every place we need an object, but without any requirement on its
|
||||||
|
class (methods, attributes,...) we will use this minimalist class.'''
|
||||||
|
def __init__(self, **fields):
|
||||||
|
for k, v in fields.iteritems():
|
||||||
|
setattr(self, k, v)
|
||||||
|
def __repr__(self):
|
||||||
|
res = u'<Object '
|
||||||
|
for attrName, attrValue in self.__dict__.iteritems():
|
||||||
|
v = attrValue
|
||||||
|
if hasattr(v, '__repr__'):
|
||||||
|
v = v.__repr__()
|
||||||
|
try:
|
||||||
|
res += u'%s=%s ' % (attrName, v)
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
res += u'%s=<encoding problem> ' % attrName
|
||||||
|
res = res.strip() + '>'
|
||||||
|
return res.encode('utf-8')
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
|
@ -18,28 +18,9 @@ mimeTypesExts = {
|
||||||
'image/jpeg' : 'jpg',
|
'image/jpeg' : 'jpg',
|
||||||
'image/gif' : 'gif'
|
'image/gif' : 'gif'
|
||||||
}
|
}
|
||||||
xmlPrologue = '<?xml version="1.0" encoding="utf-8"?>\n'
|
xmlPrologue = '<?xml version="1.0" encoding="utf-8" ?>\n'
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
class UnmarshalledObject:
|
|
||||||
'''Used for producing objects from a marshalled Python object (in some files
|
|
||||||
like a CSV file or an XML file).'''
|
|
||||||
def __init__(self, **fields):
|
|
||||||
for k, v in fields.iteritems():
|
|
||||||
setattr(self, k, v)
|
|
||||||
def __repr__(self):
|
|
||||||
res = u'<PythonObject '
|
|
||||||
for attrName, attrValue in self.__dict__.iteritems():
|
|
||||||
v = attrValue
|
|
||||||
if hasattr(v, '__repr__'):
|
|
||||||
v = v.__repr__()
|
|
||||||
try:
|
|
||||||
res += u'%s = %s ' % (attrName, v)
|
|
||||||
except UnicodeDecodeError:
|
|
||||||
res += u'%s = <encoding problem> ' % attrName
|
|
||||||
res = res.strip() + '>'
|
|
||||||
return res.encode('utf-8')
|
|
||||||
|
|
||||||
class UnmarshalledFile:
|
class UnmarshalledFile:
|
||||||
'''Used for producing file objects from a marshalled Python object.'''
|
'''Used for producing file objects from a marshalled Python object.'''
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,USA.
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,USA.
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
from appy.shared import UnmarshalledObject
|
from appy import Object
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
WRONG_LINE = 'Line number %d in file %s does not have the right number of ' \
|
WRONG_LINE = 'Line number %d in file %s does not have the right number of ' \
|
||||||
|
@ -61,9 +61,9 @@ class CsvParser:
|
||||||
# list): string, integer, float, boolean.
|
# list): string, integer, float, boolean.
|
||||||
self.references = references
|
self.references = references
|
||||||
self.klass = klass # If a klass is given here, instead of creating
|
self.klass = klass # If a klass is given here, instead of creating
|
||||||
# UnmarshalledObject instances we will create instances of this class.
|
# Object instances we will create instances of this class.
|
||||||
# But be careful: we will not call the constructor of this class. We
|
# But be careful: we will not call the constructor of this class. We
|
||||||
# will simply create instances of UnmarshalledObject and dynamically
|
# will simply create instances of Object and dynamically
|
||||||
# change the class of created instances to this class.
|
# change the class of created instances to this class.
|
||||||
|
|
||||||
def identifySeparator(self, line):
|
def identifySeparator(self, line):
|
||||||
|
@ -125,10 +125,10 @@ class CsvParser:
|
||||||
|
|
||||||
def parse(self):
|
def parse(self):
|
||||||
'''Parses the CSV file named self.fileName and creates a list of
|
'''Parses the CSV file named self.fileName and creates a list of
|
||||||
corresponding Python objects (UnmarshalledObject instances). Among
|
corresponding Python objects (Object instances). Among object fields,
|
||||||
object fields, some may be references. If it is the case, you may
|
some may be references. If it is the case, you may specify in
|
||||||
specify in p_references a dict of referred objects. The parser will
|
p_references a dict of referred objects. The parser will then
|
||||||
then replace string values of some fields (which are supposed to be
|
replace string values of some fields (which are supposed to be
|
||||||
ids of referred objects) with corresponding objects in p_references.
|
ids of referred objects) with corresponding objects in p_references.
|
||||||
|
|
||||||
How does this work? p_references must be a dictionary:
|
How does this work? p_references must be a dictionary:
|
||||||
|
@ -154,7 +154,7 @@ class CsvParser:
|
||||||
firstLine = False
|
firstLine = False
|
||||||
else:
|
else:
|
||||||
# Add an object corresponding to this line.
|
# Add an object corresponding to this line.
|
||||||
lineObject = UnmarshalledObject()
|
lineObject = Object()
|
||||||
if self.klass:
|
if self.klass:
|
||||||
lineObject.__class__ = self.klass
|
lineObject.__class__ = self.klass
|
||||||
i = -1
|
i = -1
|
||||||
|
|
104
shared/dav.py
104
shared/dav.py
|
@ -4,11 +4,13 @@ from urllib import quote
|
||||||
from StringIO import StringIO
|
from StringIO import StringIO
|
||||||
from mimetypes import guess_type
|
from mimetypes import guess_type
|
||||||
from base64 import encodestring
|
from base64 import encodestring
|
||||||
|
from appy import Object
|
||||||
from appy.shared.utils import copyData
|
from appy.shared.utils import copyData
|
||||||
from appy.gen.utils import sequenceTypes
|
from appy.gen.utils import sequenceTypes
|
||||||
|
from appy.shared.xml_parser import XmlUnmarshaller, XmlMarshaller
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
class DataEncoder:
|
class FormDataEncoder:
|
||||||
'''Allows to encode form data for sending it through a HTTP request.'''
|
'''Allows to encode form data for sending it through a HTTP request.'''
|
||||||
def __init__(self, data):
|
def __init__(self, data):
|
||||||
self.data = data # The data to encode, as a dict
|
self.data = data # The data to encode, as a dict
|
||||||
|
@ -34,6 +36,33 @@ class DataEncoder:
|
||||||
res.append(self.marshalValue(name, value))
|
res.append(self.marshalValue(name, value))
|
||||||
return '&'.join(res)
|
return '&'.join(res)
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
class SoapDataEncoder:
|
||||||
|
'''Allows to encode SOAP data for sending it through a HTTP request.'''
|
||||||
|
namespaces = {'SOAP-ENV': 'http://schemas.xmlsoap.org/soap/envelope/',
|
||||||
|
'xsd' : 'http://www.w3.org/2001/XMLSchema',
|
||||||
|
'xsi' : 'http://www.w3.org/2001/XMLSchema-instance'}
|
||||||
|
namespacedTags = {'Envelope': 'SOAP-ENV', 'Body': 'SOAP-ENV', '*': 'py'}
|
||||||
|
|
||||||
|
def __init__(self, data, namespace='http://appyframework.org'):
|
||||||
|
self.data = data
|
||||||
|
# p_data can be:
|
||||||
|
# - a string already containing a complete SOAP message
|
||||||
|
# - a Python object, that we will convert to a SOAP message
|
||||||
|
# Define the namespaces for this request
|
||||||
|
self.ns = self.namespaces.copy()
|
||||||
|
self.ns['py'] = namespace
|
||||||
|
|
||||||
|
def encode(self):
|
||||||
|
# Do nothing if we have a SOAP message already
|
||||||
|
if isinstance(self.data, basestring): return self.data
|
||||||
|
# self.data is here a Python object. Wrap it a SOAP Body.
|
||||||
|
soap = Object(Body=self.data)
|
||||||
|
# Marshall it.
|
||||||
|
marshaller = XmlMarshaller(rootTag='Envelope', namespaces=self.ns,
|
||||||
|
namespacedTags=self.namespacedTags)
|
||||||
|
return marshaller.marshall(soap)
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
class HttpResponse:
|
class HttpResponse:
|
||||||
'''Stores information about a HTTP response.'''
|
'''Stores information about a HTTP response.'''
|
||||||
|
@ -42,13 +71,13 @@ class HttpResponse:
|
||||||
self.text = text # Textual description of the code
|
self.text = text # Textual description of the code
|
||||||
self.headers = headers # A dict-like object containing the headers
|
self.headers = headers # A dict-like object containing the headers
|
||||||
self.body = body # The body of the HTTP response
|
self.body = body # The body of the HTTP response
|
||||||
|
# p_duration, if given, is the time, in seconds, we have waited, before
|
||||||
|
# getting this response after having sent the request.
|
||||||
|
self.duration = duration
|
||||||
# The following attribute may contain specific data extracted from
|
# The following attribute may contain specific data extracted from
|
||||||
# the previous fields. For example, when response if 302 (Redirect),
|
# the previous fields. For example, when response if 302 (Redirect),
|
||||||
# self.data contains the URI where we must redirect the user to.
|
# self.data contains the URI where we must redirect the user to.
|
||||||
self.data = self.extractData()
|
self.data = self.extractData()
|
||||||
# p_duration, if given, is the time, in seconds, we have waited, before
|
|
||||||
# getting this response after having sent the request.
|
|
||||||
self.duration = duration
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
duration = ''
|
duration = ''
|
||||||
|
@ -58,9 +87,15 @@ class HttpResponse:
|
||||||
def extractData(self):
|
def extractData(self):
|
||||||
'''This method extracts, from the various parts of the HTTP response,
|
'''This method extracts, from the various parts of the HTTP response,
|
||||||
some useful information. For example, it will find the URI where to
|
some useful information. For example, it will find the URI where to
|
||||||
redirect the user to if self.code is 302.'''
|
redirect the user to if self.code is 302, or will unmarshall XML
|
||||||
|
data into Python objects.'''
|
||||||
if self.code == 302:
|
if self.code == 302:
|
||||||
return urlparse.urlparse(self.headers['location'])[2]
|
return urlparse.urlparse(self.headers['location'])[2]
|
||||||
|
elif self.headers.has_key('content-type') and \
|
||||||
|
self.headers['content-type'].startswith('text/xml'):
|
||||||
|
# Return an unmarshalled version of the XML content, for easy use
|
||||||
|
# in Python.
|
||||||
|
return XmlUnmarshaller().parse(self.body)
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
urlRex = re.compile(r'http://([^:/]+)(:[0-9]+)?(/.+)?', re.I)
|
urlRex = re.compile(r'http://([^:/]+)(:[0-9]+)?(/.+)?', re.I)
|
||||||
|
@ -109,7 +144,7 @@ class Resource:
|
||||||
headers['Accept'] = '*/*'
|
headers['Accept'] = '*/*'
|
||||||
return headers
|
return headers
|
||||||
|
|
||||||
def sendRequest(self, method, uri, body=None, headers={}, bodyType=None):
|
def send(self, method, uri, body=None, headers={}, bodyType=None):
|
||||||
'''Sends a HTTP request with p_method, for p_uri.'''
|
'''Sends a HTTP request with p_method, for p_uri.'''
|
||||||
conn = httplib.HTTP()
|
conn = httplib.HTTP()
|
||||||
conn.connect(self.host, self.port)
|
conn.connect(self.host, self.port)
|
||||||
|
@ -143,13 +178,13 @@ class Resource:
|
||||||
#body = '<d:propertyupdate xmlns:d="DAV:"><d:set><d:prop>' \
|
#body = '<d:propertyupdate xmlns:d="DAV:"><d:set><d:prop>' \
|
||||||
# '<d:displayname>%s</d:displayname></d:prop></d:set>' \
|
# '<d:displayname>%s</d:displayname></d:prop></d:set>' \
|
||||||
# '</d:propertyupdate>' % name
|
# '</d:propertyupdate>' % name
|
||||||
return self.sendRequest('MKCOL', folderUri)
|
return self.send('MKCOL', folderUri)
|
||||||
|
|
||||||
def delete(self, name):
|
def delete(self, name):
|
||||||
'''Deletes a file or a folder (and all contained files if any) named
|
'''Deletes a file or a folder (and all contained files if any) named
|
||||||
p_name within this resource.'''
|
p_name within this resource.'''
|
||||||
toDeleteUri = self.uri + '/' + name
|
toDeleteUri = self.uri + '/' + name
|
||||||
return self.sendRequest('DELETE', toDeleteUri)
|
return self.send('DELETE', toDeleteUri)
|
||||||
|
|
||||||
def add(self, content, type='fileName', name=''):
|
def add(self, content, type='fileName', name=''):
|
||||||
'''Adds a file in this resource. p_type can be:
|
'''Adds a file in this resource. p_type can be:
|
||||||
|
@ -178,45 +213,44 @@ class Resource:
|
||||||
headers = {'Content-Length': str(size)}
|
headers = {'Content-Length': str(size)}
|
||||||
if fileType: headers['Content-Type'] = fileType
|
if fileType: headers['Content-Type'] = fileType
|
||||||
if encoding: headers['Content-Encoding'] = encoding
|
if encoding: headers['Content-Encoding'] = encoding
|
||||||
res = self.sendRequest('PUT', fileUri, body, headers, bodyType=bodyType)
|
res = self.send('PUT', fileUri, body, headers, bodyType=bodyType)
|
||||||
# Close the file when relevant
|
# Close the file when relevant
|
||||||
if type =='fileName': body.close()
|
if type =='fileName': body.close()
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def _encodeFormData(self, data):
|
|
||||||
'''Returns the encoded form p_data.'''
|
|
||||||
res = []
|
|
||||||
for name, value in data.items():
|
|
||||||
n = name.rfind( '__')
|
|
||||||
if n > 0:
|
|
||||||
tag = name[n+2:]
|
|
||||||
key = name[:n]
|
|
||||||
else: tag = 'string'
|
|
||||||
func = varfuncs.get(tag, marshal_string)
|
|
||||||
res.append(func(name, value))
|
|
||||||
return '&'.join(res)
|
|
||||||
|
|
||||||
def get(self, uri=None, headers={}):
|
def get(self, uri=None, headers={}):
|
||||||
'''Perform a HTTP GET on the server.'''
|
'''Perform a HTTP GET on the server.'''
|
||||||
if not uri: uri = self.uri
|
if not uri: uri = self.uri
|
||||||
return self.sendRequest('GET', uri, headers=headers)
|
return self.send('GET', uri, headers=headers)
|
||||||
|
|
||||||
def post(self, data=None, uri=None, headers={}, type='form'):
|
def post(self, data=None, uri=None, headers={}, encode='form'):
|
||||||
'''Perform a HTTP POST on the server. If p_type is:
|
'''Perform a HTTP POST on the server. If p_encode is "form", p_data is
|
||||||
- "form", p_data is a dict representing form data that will be
|
considered to be a dict representing form data that will be
|
||||||
form-encoded;
|
form-encoded. Else, p_data will be considered as the ready-to-send
|
||||||
- "soap", p_data is a XML request that will be wrapped in a SOAP
|
body of the HTTP request.'''
|
||||||
message.'''
|
|
||||||
if not uri: uri = self.uri
|
if not uri: uri = self.uri
|
||||||
# Prepare the data to send
|
# Prepare the data to send
|
||||||
if type == 'form':
|
headers['Host'] = self.host
|
||||||
|
if encode == 'form':
|
||||||
# Format the form data and prepare headers
|
# Format the form data and prepare headers
|
||||||
body = DataEncoder(data).encode()
|
body = FormDataEncoder(data).encode()
|
||||||
headers['Content-Type'] = 'application/x-www-form-urlencoded'
|
headers['Content-Type'] = 'application/x-www-form-urlencoded'
|
||||||
elif type =='soap':
|
else:
|
||||||
body = data
|
body = data
|
||||||
headers['SOAPAction'] = self.url
|
|
||||||
headers['Content-Type'] = 'text/xml'
|
|
||||||
headers['Content-Length'] = str(len(body))
|
headers['Content-Length'] = str(len(body))
|
||||||
return self.sendRequest('POST', uri, headers=headers, body=body)
|
return self.send('POST', uri, headers=headers, body=body)
|
||||||
|
|
||||||
|
def soap(self, data, uri=None, headers={}, namespace=None):
|
||||||
|
'''Sends a SOAP message to this resource. p_namespace is the URL of the
|
||||||
|
server-specific namespace.'''
|
||||||
|
if not uri: uri = self.uri
|
||||||
|
# Prepare the data to send
|
||||||
|
data = SoapDataEncoder(data, namespace).encode()
|
||||||
|
headers['SOAPAction'] = self.url
|
||||||
|
headers['Content-Type'] = 'text/xml'
|
||||||
|
res = self.post(data, uri, headers=headers, encode=None)
|
||||||
|
# Unwrap content from the SOAP envelope
|
||||||
|
res.data = res.data.Body
|
||||||
|
return res
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
|
@ -140,7 +140,8 @@ class XmlParser(ContentHandler, ErrorHandler):
|
||||||
return self.res
|
return self.res
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
from appy.shared import UnmarshalledObject, UnmarshalledFile
|
from appy.shared import UnmarshalledFile
|
||||||
|
from appy import Object
|
||||||
try:
|
try:
|
||||||
from DateTime import DateTime
|
from DateTime import DateTime
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
@ -161,14 +162,14 @@ class XmlUnmarshaller(XmlParser):
|
||||||
XmlParser.__init__(self)
|
XmlParser.__init__(self)
|
||||||
# self.classes below is a dict whose keys are tag names and values are
|
# self.classes below is a dict whose keys are tag names and values are
|
||||||
# Python classes. During the unmarshalling process, when an object is
|
# Python classes. During the unmarshalling process, when an object is
|
||||||
# encountered, instead of creating an instance of UnmarshalledObject,
|
# encountered, instead of creating an instance of Object, we will create
|
||||||
# we will create an instance of the class specified in self.classes.
|
# an instance of the class specified in self.classes.
|
||||||
# Root tag is named "xmlPythonData" by default by the XmlMarshaller.
|
# Root tag is named "xmlPythonData" by default by the XmlMarshaller.
|
||||||
# This will not work if the object in the specified tag is not a
|
# This will not work if the object in the specified tag is not an
|
||||||
# UnmarshalledObject instance (ie it is a list or tuple or simple
|
# Object instance (ie it is a list or tuple or simple value). Note that
|
||||||
# value). Note that we will not call the constructor of the specified
|
# we will not call the constructor of the specified class. We will
|
||||||
# class. We will simply create an instance of UnmarshalledObject and
|
# simply create an instance of Objects and dynamically change the class
|
||||||
# dynamically change the class of the created instance to this class.
|
# of the created instance to this class.
|
||||||
if not isinstance(classes, dict) and classes:
|
if not isinstance(classes, dict) and classes:
|
||||||
# The user may only need to define a class for the root tag
|
# The user may only need to define a class for the root tag
|
||||||
self.classes = {'xmlPythonData': classes}
|
self.classes = {'xmlPythonData': classes}
|
||||||
|
@ -198,12 +199,14 @@ class XmlUnmarshaller(XmlParser):
|
||||||
def convertAttrs(self, attrs):
|
def convertAttrs(self, attrs):
|
||||||
'''Converts XML attrs to a dict.'''
|
'''Converts XML attrs to a dict.'''
|
||||||
res = {}
|
res = {}
|
||||||
for k, v in attrs.items(): res[str(k)] = v
|
for k, v in attrs.items():
|
||||||
|
if ':' in k: # An attr prefixed with a namespace. Remove this.
|
||||||
|
k = k.split(':')[-1]
|
||||||
|
res[str(k)] = v
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def startDocument(self):
|
def startDocument(self):
|
||||||
self.res = None # The resulting web of Python objects
|
self.res = None # The resulting web of Python objects (Object instances)
|
||||||
# (UnmarshalledObject instances).
|
|
||||||
self.env.containerStack = [] # The stack of current "containers" where
|
self.env.containerStack = [] # The stack of current "containers" where
|
||||||
# to store the next parsed element. A container can be a list, a tuple,
|
# to store the next parsed element. A container can be a list, a tuple,
|
||||||
# an object (the root object of the whole web or a sub-object).
|
# an object (the root object of the whole web or a sub-object).
|
||||||
|
@ -214,6 +217,10 @@ class XmlUnmarshaller(XmlParser):
|
||||||
containerTags = ('tuple', 'list', 'object', 'file')
|
containerTags = ('tuple', 'list', 'object', 'file')
|
||||||
numericTypes = ('bool', 'int', 'float', 'long')
|
numericTypes = ('bool', 'int', 'float', 'long')
|
||||||
def startElement(self, elem, attrs):
|
def startElement(self, elem, attrs):
|
||||||
|
# Remember the name of the previous element
|
||||||
|
previousElem = None
|
||||||
|
if self.env.currentElem:
|
||||||
|
previousElem = self.env.currentElem.name
|
||||||
e = XmlParser.startElement(self, elem, attrs)
|
e = XmlParser.startElement(self, elem, attrs)
|
||||||
# Determine the type of the element.
|
# Determine the type of the element.
|
||||||
elemType = 'unicode' # Default value
|
elemType = 'unicode' # Default value
|
||||||
|
@ -224,7 +231,7 @@ class XmlUnmarshaller(XmlParser):
|
||||||
if elemType in self.containerTags:
|
if elemType in self.containerTags:
|
||||||
# I must create a new container object.
|
# I must create a new container object.
|
||||||
if elemType == 'object':
|
if elemType == 'object':
|
||||||
newObject = UnmarshalledObject(**self.convertAttrs(attrs))
|
newObject = Object(**self.convertAttrs(attrs))
|
||||||
elif elemType == 'tuple': newObject = [] # Tuples become lists
|
elif elemType == 'tuple': newObject = [] # Tuples become lists
|
||||||
elif elemType == 'list': newObject = []
|
elif elemType == 'list': newObject = []
|
||||||
elif elemType == 'file':
|
elif elemType == 'file':
|
||||||
|
@ -233,20 +240,31 @@ class XmlUnmarshaller(XmlParser):
|
||||||
newObject.name = attrs['name']
|
newObject.name = attrs['name']
|
||||||
if attrs.has_key('mimeType'):
|
if attrs.has_key('mimeType'):
|
||||||
newObject.mimeType = attrs['mimeType']
|
newObject.mimeType = attrs['mimeType']
|
||||||
else: newObject = UnmarshalledObject(**self.convertAttrs(attrs))
|
else: newObject = Object(**self.convertAttrs(attrs))
|
||||||
# Store the value on the last container, or on the root object.
|
# Store the value on the last container, or on the root object.
|
||||||
self.storeValue(elem, newObject)
|
self.storeValue(elem, newObject)
|
||||||
# Push the new object on the container stack
|
# Push the new object on the container stack
|
||||||
e.containerStack.append(newObject)
|
e.containerStack.append(newObject)
|
||||||
else:
|
else:
|
||||||
|
# If we are already parsing a basic type, it means that we were
|
||||||
|
# wrong for our diagnotsic of the containing element: it was not
|
||||||
|
# basic. We will make the assumption that the containing element is
|
||||||
|
# then an object.
|
||||||
|
if e.currentBasicType:
|
||||||
|
# Previous elem was an object: create it on the stack.
|
||||||
|
newObject = Object()
|
||||||
|
self.storeValue(previousElem, newObject)
|
||||||
|
e.containerStack.append(newObject)
|
||||||
e.currentBasicType = elemType
|
e.currentBasicType = elemType
|
||||||
|
|
||||||
def storeValue(self, name, value):
|
def storeValue(self, name, value):
|
||||||
'''Stores the newly parsed p_value (contained in tag p_name) on the
|
'''Stores the newly parsed p_value (contained in tag p_name) on the
|
||||||
current container in environment self.env.'''
|
current container in environment self.env.'''
|
||||||
e = self.env
|
e = self.env
|
||||||
|
# Remove namespace prefix when relevant
|
||||||
|
if ':' in name: name = name.split(':')[-1]
|
||||||
# Change the class of the value if relevant
|
# Change the class of the value if relevant
|
||||||
if (name in self.classes) and isinstance(value, UnmarshalledObject):
|
if (name in self.classes) and isinstance(value, Object):
|
||||||
value.__class__ = self.classes[name]
|
value.__class__ = self.classes[name]
|
||||||
# Where must I store this value?
|
# Where must I store this value?
|
||||||
if not e.containerStack:
|
if not e.containerStack:
|
||||||
|
@ -344,7 +362,8 @@ class XmlMarshaller:
|
||||||
atFiles = ('image', 'file') # Types of archetypes fields that contain files.
|
atFiles = ('image', 'file') # Types of archetypes fields that contain files.
|
||||||
|
|
||||||
def __init__(self, cdata=False, dumpUnicode=False, conversionFunctions={},
|
def __init__(self, cdata=False, dumpUnicode=False, conversionFunctions={},
|
||||||
dumpXmlPrologue=True, rootTag='xmlPythonData'):
|
dumpXmlPrologue=True, rootTag='xmlPythonData', namespaces={},
|
||||||
|
namespacedTags={}):
|
||||||
# If p_cdata is True, all string values will be dumped as XML CDATA.
|
# If p_cdata is True, all string values will be dumped as XML CDATA.
|
||||||
self.cdata = cdata
|
self.cdata = cdata
|
||||||
# If p_dumpUnicode is True, the result will be unicode.
|
# If p_dumpUnicode is True, the result will be unicode.
|
||||||
|
@ -363,6 +382,41 @@ class XmlMarshaller:
|
||||||
self.dumpXmlPrologue = dumpXmlPrologue
|
self.dumpXmlPrologue = dumpXmlPrologue
|
||||||
# The name of the root tag
|
# The name of the root tag
|
||||||
self.rootElementName = rootTag
|
self.rootElementName = rootTag
|
||||||
|
# The namespaces that will be defined at the root of the XML message.
|
||||||
|
# It is a dict whose keys are namespace prefixes and whose values are
|
||||||
|
# namespace URLs.
|
||||||
|
self.namespaces = namespaces
|
||||||
|
# The following dict will tell which XML tags will get which namespace
|
||||||
|
# prefix ({s_tagName: s_prefix}). Special optional dict entry
|
||||||
|
# '*':s_prefix will indicate a default prefix that will be applied to
|
||||||
|
# any tag that does not have it own key in this dict.
|
||||||
|
self.namespacedTags = namespacedTags
|
||||||
|
self.objectType = None # Will be given by method m_marshal
|
||||||
|
|
||||||
|
def getTagName(self, name):
|
||||||
|
'''Returns the name of tag p_name as will be dumped. It can be p_name,
|
||||||
|
or p_name prefixed with a namespace prefix (will depend on
|
||||||
|
self.prefixedTags).'''
|
||||||
|
# Determine the prefix
|
||||||
|
prefix = ''
|
||||||
|
if name in self.namespacedTags: prefix = self.namespacedTags[name]
|
||||||
|
elif '*' in self.namespacedTags: prefix = self.namespacedTags['*']
|
||||||
|
if prefix: return '%s:%s' % (prefix, name)
|
||||||
|
return name
|
||||||
|
|
||||||
|
def dumpRootTag(self, res, instance):
|
||||||
|
'''Dumps the root tag.'''
|
||||||
|
# Dumps the name of the tag.
|
||||||
|
tagName = self.getTagName(self.rootElementName)
|
||||||
|
res.write('<'); res.write(tagName)
|
||||||
|
# Dumps namespace definitions if any
|
||||||
|
for prefix, url in self.namespaces.iteritems():
|
||||||
|
res.write(' xmlns:%s="%s"' % (prefix, url))
|
||||||
|
# Dumps Appy- or Plone-specific attributed
|
||||||
|
if self.objectType != 'popo':
|
||||||
|
res.write(' type="object" id="%s"' % instance.UID())
|
||||||
|
res.write('>')
|
||||||
|
return tagName
|
||||||
|
|
||||||
def dumpString(self, res, s):
|
def dumpString(self, res, s):
|
||||||
'''Dumps a string into the result.'''
|
'''Dumps a string into the result.'''
|
||||||
|
@ -382,7 +436,8 @@ class XmlMarshaller:
|
||||||
if not v: return
|
if not v: return
|
||||||
# p_value contains the (possibly binary) content of a file. We will
|
# p_value contains the (possibly binary) content of a file. We will
|
||||||
# encode it in Base64, in one or several parts.
|
# encode it in Base64, in one or several parts.
|
||||||
res.write('<part type="base64" number="1">')
|
partTag = self.getTagName('part')
|
||||||
|
res.write('<%s type="base64" number="1">' % partTag)
|
||||||
if hasattr(v, 'data'):
|
if hasattr(v, 'data'):
|
||||||
# The file is an Archetypes file.
|
# The file is an Archetypes file.
|
||||||
valueType = v.data.__class__.__name__
|
valueType = v.data.__class__.__name__
|
||||||
|
@ -393,8 +448,9 @@ class XmlMarshaller:
|
||||||
nextPart = v.data.next
|
nextPart = v.data.next
|
||||||
nextPartNumber = 2
|
nextPartNumber = 2
|
||||||
while nextPart:
|
while nextPart:
|
||||||
res.write('</part>') # Close the previous part
|
res.write('</%s>' % partTag) # Close the previous part
|
||||||
res.write('<part type="base64" number="%d">'%nextPartNumber)
|
res.write('<%s type="base64" number="%d">' % \
|
||||||
|
(partTag, nextPartNumber))
|
||||||
res.write(nextPart.data.encode('base64'))
|
res.write(nextPart.data.encode('base64'))
|
||||||
nextPart = nextPart.next
|
nextPart = nextPart.next
|
||||||
nextPartNumber += 1
|
nextPartNumber += 1
|
||||||
|
@ -402,7 +458,7 @@ class XmlMarshaller:
|
||||||
res.write(v.data.encode('base64'))
|
res.write(v.data.encode('base64'))
|
||||||
else:
|
else:
|
||||||
res.write(v.encode('base64'))
|
res.write(v.encode('base64'))
|
||||||
res.write('</part>')
|
res.write('</%s>' % partTag)
|
||||||
|
|
||||||
def dumpValue(self, res, value, fieldType, isRef=False):
|
def dumpValue(self, res, value, fieldType, isRef=False):
|
||||||
'''Dumps the XML version of p_value to p_res.'''
|
'''Dumps the XML version of p_value to p_res.'''
|
||||||
|
@ -432,19 +488,23 @@ class XmlMarshaller:
|
||||||
res.write(self.trueFalse[value])
|
res.write(self.trueFalse[value])
|
||||||
elif fieldType == 'object':
|
elif fieldType == 'object':
|
||||||
if hasattr(value, 'absolute_url'):
|
if hasattr(value, 'absolute_url'):
|
||||||
|
# Dump the URL to the object only
|
||||||
res.write(value.absolute_url())
|
res.write(value.absolute_url())
|
||||||
else:
|
else:
|
||||||
res.write(value)
|
# Dump the entire object content
|
||||||
# We could dump the entire object content, too. Maybe we could add a
|
for k, v in value.__dict__.iteritems():
|
||||||
# parameter to the marshaller to know how to marshall objects
|
if not k.startswith('__'):
|
||||||
# (produce an ID, an URL, include the entire tag but we need to take
|
self.dumpField(res, k, v)
|
||||||
# care of circular references,...)
|
# Maybe we could add a parameter to the marshaller to know how
|
||||||
|
# to marshall objects (produce an ID, an URL, include the entire
|
||||||
|
# tag but we need to take care of circular references,...)
|
||||||
else:
|
else:
|
||||||
res.write(value)
|
res.write(value)
|
||||||
|
|
||||||
def dumpField(self, res, fieldName, fieldValue, fieldType='basic'):
|
def dumpField(self, res, fieldName, fieldValue, fieldType='basic'):
|
||||||
'''Dumps in p_res, the value of the p_field for p_instance.'''
|
'''Dumps in p_res, the value of the p_field for p_instance.'''
|
||||||
res.write('<'); res.write(fieldName);
|
fieldTag = self.getTagName(fieldName)
|
||||||
|
res.write('<'); res.write(fieldTag)
|
||||||
# Dump the type of the field as an XML attribute
|
# Dump the type of the field as an XML attribute
|
||||||
fType = None # No type will mean "unicode".
|
fType = None # No type will mean "unicode".
|
||||||
if fieldType == 'file': fType = 'file'
|
if fieldType == 'file': fType = 'file'
|
||||||
|
@ -457,10 +517,11 @@ class XmlMarshaller:
|
||||||
elif isinstance(fieldValue, list): fType = 'list'
|
elif isinstance(fieldValue, list): fType = 'list'
|
||||||
elif fieldValue.__class__.__name__ == 'DateTime': fType = 'DateTime'
|
elif fieldValue.__class__.__name__ == 'DateTime': fType = 'DateTime'
|
||||||
elif self.isAnObject(fieldValue): fType = 'object'
|
elif self.isAnObject(fieldValue): fType = 'object'
|
||||||
if fType: res.write(' type="%s"' % fType)
|
if self.objectType != 'popo':
|
||||||
# Dump other attributes if needed
|
if fType: res.write(' type="%s"' % fType)
|
||||||
if type(fieldValue) in self.sequenceTypes:
|
# Dump other attributes if needed
|
||||||
res.write(' count="%d"' % len(fieldValue))
|
if type(fieldValue) in self.sequenceTypes:
|
||||||
|
res.write(' count="%d"' % len(fieldValue))
|
||||||
if fType == 'file':
|
if fType == 'file':
|
||||||
if hasattr(fieldValue, 'content_type'):
|
if hasattr(fieldValue, 'content_type'):
|
||||||
res.write(' mimeType="%s"' % fieldValue.content_type)
|
res.write(' mimeType="%s"' % fieldValue.content_type)
|
||||||
|
@ -471,7 +532,7 @@ class XmlMarshaller:
|
||||||
res.write('>')
|
res.write('>')
|
||||||
# Dump the field value
|
# Dump the field value
|
||||||
self.dumpValue(res, fieldValue, fType, isRef=(fieldType=='ref'))
|
self.dumpValue(res, fieldValue, fType, isRef=(fieldType=='ref'))
|
||||||
res.write('</'); res.write(fieldName); res.write('>')
|
res.write('</'); res.write(fieldTag); res.write('>')
|
||||||
|
|
||||||
def isAnObject(self, instance):
|
def isAnObject(self, instance):
|
||||||
'''Returns True if p_instance is a class instance, False if it is a
|
'''Returns True if p_instance is a class instance, False if it is a
|
||||||
|
@ -484,6 +545,7 @@ class XmlMarshaller:
|
||||||
return True
|
return True
|
||||||
elif iType.__class__.__name__ == 'ExtensionClass':
|
elif iType.__class__.__name__ == 'ExtensionClass':
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def marshall(self, instance, objectType='popo', conversionFunctions={}):
|
def marshall(self, instance, objectType='popo', conversionFunctions={}):
|
||||||
|
@ -494,6 +556,7 @@ class XmlMarshaller:
|
||||||
a Appy object, specify "appy" as p_objectType. If p_instance is not
|
a Appy object, specify "appy" as p_objectType. If p_instance is not
|
||||||
an instance at all, but another Python data structure or basic type,
|
an instance at all, but another Python data structure or basic type,
|
||||||
p_objectType is ignored.'''
|
p_objectType is ignored.'''
|
||||||
|
self.objectType = objectType
|
||||||
# Call the XmlMarshaller constructor if it hasn't been called yet.
|
# Call the XmlMarshaller constructor if it hasn't been called yet.
|
||||||
if not hasattr(self, 'cdata'):
|
if not hasattr(self, 'cdata'):
|
||||||
XmlMarshaller.__init__(self)
|
XmlMarshaller.__init__(self)
|
||||||
|
@ -505,14 +568,9 @@ class XmlMarshaller:
|
||||||
if self.dumpXmlPrologue:
|
if self.dumpXmlPrologue:
|
||||||
res.write(xmlPrologue)
|
res.write(xmlPrologue)
|
||||||
if self.isAnObject(instance):
|
if self.isAnObject(instance):
|
||||||
# Determine object ID
|
# Dump the root tag
|
||||||
if objectType in ('archetype', 'appy'):
|
rootTagName = self.dumpRootTag(res, instance)
|
||||||
objectId = instance.UID() # ID in DB
|
# Dump the fields of this root object
|
||||||
else:
|
|
||||||
objectId = str(id(instance)) # ID in RAM
|
|
||||||
res.write('<'); res.write(self.rootElementName)
|
|
||||||
res.write(' type="object" id="');res.write(objectId);res.write('">')
|
|
||||||
# Dump the object ID and the value of the fields that must be dumped
|
|
||||||
if objectType == 'popo':
|
if objectType == 'popo':
|
||||||
for fieldName, fieldValue in instance.__dict__.iteritems():
|
for fieldName, fieldValue in instance.__dict__.iteritems():
|
||||||
mustDump = False
|
mustDump = False
|
||||||
|
@ -565,18 +623,20 @@ class XmlMarshaller:
|
||||||
self.dumpField(res, field.name,field.getValue(instance),
|
self.dumpField(res, field.name,field.getValue(instance),
|
||||||
fieldType=fieldType)
|
fieldType=fieldType)
|
||||||
# Dump the object history.
|
# Dump the object history.
|
||||||
res.write('<history type="list">')
|
histTag = self.getTagName('history')
|
||||||
|
eventTag = self.getTagName('event')
|
||||||
|
res.write('<%s type="list">' % histTag)
|
||||||
wfInfo = instance.portal_workflow.getWorkflowsFor(instance)
|
wfInfo = instance.portal_workflow.getWorkflowsFor(instance)
|
||||||
if wfInfo:
|
if wfInfo:
|
||||||
history = instance.workflow_history[wfInfo[0].id]
|
history = instance.workflow_history[wfInfo[0].id]
|
||||||
for event in history:
|
for event in history:
|
||||||
res.write('<event type="object">')
|
res.write('<%s type="object">' % eventTag)
|
||||||
for k, v in event.iteritems():
|
for k, v in event.iteritems():
|
||||||
self.dumpField(res, k, v)
|
self.dumpField(res, k, v)
|
||||||
res.write('</event>')
|
res.write('</%s>' % eventTag)
|
||||||
res.write('</history>')
|
res.write('</%s>' % histTag)
|
||||||
self.marshallSpecificElements(instance, res)
|
self.marshallSpecificElements(instance, res)
|
||||||
res.write('</'); res.write(self.rootElementName); res.write('>')
|
res.write('</'); res.write(rootTagName); res.write('>')
|
||||||
else:
|
else:
|
||||||
self.dumpField(res, self.rootElementName, instance)
|
self.dumpField(res, self.rootElementName, instance)
|
||||||
# Return the result
|
# Return the result
|
||||||
|
|
Loading…
Reference in a new issue