OK, this should address everyone's comments.
=== modified file 'wadllib/application.py'
--- wadllib/application.py 2009-01-21 19:12:41 +0000
+++ wadllib/application.py 2009-01-22 15:33:42 +0000
@@ -43,6 +43,7 @@
'WADLError',
]
+from cStringIO import StringIO
import datetime
import time
import urllib
@@ -58,6 +59,9 @@
from iso_strptime import iso_strptime
from wadllib._utils import uri
+NS_MAP = "xmlns:map"
+XML_SCHEMA_NS_URI = 'http://www.w3.org/2001/XMLSchema'
+
def wadl_tag(tag_name):
"""Scope a tag name with the WADL namespace."""
return '{http://research.sun.com/wadl/2006/10}' + tag_name
@@ -411,7 +415,11 @@
return None
def get_parameter_value(self, parameter):
- """Find the value of a parameter, given the Parameter object."""
+ """Find the value of a parameter, given the Parameter object.
+
+ :raise ValueError: If the parameter value can't be converted into
+ its defined type.
+ """
if self.representation is None:
raise NoBoundRepresentationError(
@@ -430,25 +438,46 @@
"type %s." % parameter.style)
value = self.representation[parameter.name]
if value is not None:
- if parameter.type in ['xsd:dateTime', 'xsd:date']:
+ namespace_url, data_type = self._dereference_namespace(
+ parameter.tag, parameter.type)
+ if (namespace_url == XML_SCHEMA_NS_URI
+ and data_type in ['dateTime', 'date']):
try:
- # Parse it as an ISO 1601 date and time.
+ # Parse it as an ISO 8601 date and time.
value = iso_strptime(value)
except ValueError:
- # Parse it as an ISO 1601 date.
+ # Parse it as an ISO 8601 date.
try:
value = datetime.datetime(
*(time.strptime(value, "%Y-%m-%d")[0:6]))
except ValueError:
- # Oh well. Leave it as a string.
- pass
-
+ # Raise an unadored ValueError so the client
+ # can treat the value as a string if they
+ # want.
+ raise ValueError(value)
return value
raise NotImplementedError("Path traversal not implemented for "
"a representation of media type %s."
% self.media_type)
+
+ def _dereference_namespace(self, tag, value):
+ """Splits a value into namespace URI and value.
+
+ :param tag: A tag to use as context when mapping namespace
+ names to URIs.
+ """
+ namespace_url = None
+ if value is not None and ':' in value:
+ namespace, value = value.split(':', 1)
+ else:
+ namespace = ''
+ if namespace is not None:
+ ns_map = tag.get(NS_MAP)
+ namespace_url = ns_map.get(namespace)
+ return namespace_url, value
+
def _definition_factory(self, id):
"""Given an ID, find a ResourceType for that ID."""
return self.application.resource_types.get(id)
@@ -906,7 +935,7 @@
self.markup_url = markup_url
if hasattr(markup, 'read'):
markup = markup.read()
- self.doc = ET.fromstring(markup)
+ self.doc = self._from_string(markup)
self.resources = self.doc.find(wadl_xpath('resources'))
self.resource_base = self.resources.attrib.get('base')
self.representation_definitions = {}
@@ -921,6 +950,27 @@
id = resource_type.attrib['id']
self.resource_types[id] = ResourceType(resource_type)
+ def _from_string(self, markup):
+ """Turns markup into a document.
+
+ Just a wrapper around ElementTree which keeps track of namespaces.
+ """
+ events = "start", "start-ns", "end-ns"
+ root = None
+ ns_map = []
+
+ for event, elem in ET.iterparse(StringIO(markup), events):
+ if event == "start-ns":
+ ns_map.append(elem)
+ elif event == "end-ns":
+ ns_map.pop()
+ elif event == "start":
+ if root is None:
+ root = elem
+ elem.set(NS_MAP, dict(ns_map))
+ return ET.ElementTree(root)
+
+
def get_resource_type(self, resource_type_url):
"""Retrieve a resource type by the URL of its description."""
xml_id = self.lookup_xml_id(resource_type_url)
@@ -959,7 +1009,7 @@
# representation of a non-root resource to its definition at
# the server root.
raise NotImplementedError("Can't look up definition in another "
- "url (%s)" % (url))
+ "url (%s)" % url)
def get_resource_by_path(self, path):
"""Locate one of the resources described by this document.
=== modified file 'wadllib/docs/wadllib.txt'
--- wadllib/docs/wadllib.txt 2009-01-21 19:12:41 +0000
+++ wadllib/docs/wadllib.txt 2009-01-22 15:12:32 +0000
@@ -354,7 +354,7 @@
>>> print method.response.get_representation_definition('text/html')
None
-=== Data type converstion ===
+=== Data type conversion ===
The values of date and dateTime parameters are automatically converted to
Python datetime objects.
@@ -388,14 +388,17 @@
>>> bound_root.get_parameter('a_date').get_value()
datetime.datetime(2005, 6, 6, 8, ...)
-If a date or dateTime parameter has a value that can't be parsed to a
-datetime object, you get the original value.
+If a date or dateTime parameter has a null value, you get None. If the
+value is a string that can't be parsed to a datetime object, you get a
+ValueError.
>>> representation = simplejson.dumps(
... {'a_date': 'foo', 'a_datetime': None})
>>> bound_root = service_root.bind(representation, 'application/json')
>>> bound_root.get_parameter('a_date').get_value()
- u'foo'
+ Traceback (most recent call last):
+ ...
+ ValueError: foo
>>> print bound_root.get_parameter('a_datetime').get_value()
None
=== modified file 'wadllib/iso_strptime.py'
--- wadllib/iso_strptime.py 2009-01-21 15:39:44 +0000
+++ wadllib/iso_strptime.py 2009-01-22 16:04:19 +0000
@@ -16,7 +16,7 @@
# License along with wadllib. If not, see
# .
"""
-Parser for ISO 1601 time strings
+Parser for ISO 8601 time strings
================================
>>> d = iso_strptime("2008-01-07T05:30:30.345323+03:00")
@@ -41,23 +41,30 @@
import datetime
RE_TIME = re.compile(r"""^
- (?P\d{4})\-(?P\d{2})\-(?P\d{2}) # pattern matching date
- T # seperator
- (?P\d{2})\:(?P\d{2})\:(?P\d{2}) # pattern matching time
- (\.(?P\d{6}))? # pattern matching optional microseconds
- (?P[\-\+]\d{2}\:\d{2})? # pattern matching optional timezone offset
- $""", re.VERBOSE)
-
+ (?P\d{4})\-(?P\d{2})\-(?P\d{2})
+ # pattern matching date
+ T
+ # seperator
+ (?P\d{2})\:(?P\d{2})\:(?P\d{2})
+ # pattern matching time
+ (\.(?P\d{6}))?
+ # pattern matching optional microseconds
+ (?P[\-\+]\d{2}\:\d{2})?
+ # pattern matching optional timezone offset
+ $""", re.VERBOSE)
+
class TimeZone(datetime.tzinfo):
def __init__(self, tz_string):
hours, minutes = tz_string.lstrip("-+").split(":")
- self.stdoffset = datetime.timedelta(hours=int(hours), minutes=int(minutes))
+ self.stdoffset = datetime.timedelta(hours=int(hours),
+ minutes=int(minutes))
if tz_string.startswith("-"):
self.stdoffset *= -1
-
+
def __repr__(self):
- return "TimeZone(%s)" %(self.stdoffset.days*24*60*60 + self.stdoffset.seconds)
+ return "TimeZone(%s)" % (
+ self.stdoffset.days*24*60*60 + self.stdoffset.seconds)
def utcoffset(self, dt):
return self.stdoffset
@@ -79,7 +86,3 @@
if x.group("tz_offset"):
d = d.replace(tzinfo=TimeZone(x.group("tz_offset")))
return d
-
-if __name__ == '__main__':
- import doctest
- doctest.testmod()