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()