/usr/share/pyshared/lazr/restful/marshallers.py is in python-lazr.restful 0.9.29-0ubuntu2.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 | # Copyright 2008 Canonical Ltd. All rights reserved.
"""Marshallers for fields used in HTTP resources."""
__metaclass__ = type
__all__ = [
'AbstractCollectionFieldMarshaller',
'BoolFieldMarshaller',
'BytesFieldMarshaller',
'CollectionFieldMarshaller',
'DateTimeFieldMarshaller',
'FloatFieldMarshaller',
'IntFieldMarshaller',
'ObjectLookupFieldMarshaller',
'SetFieldMarshaller',
'SimpleFieldMarshaller',
'SimpleVocabularyLookupFieldMarshaller',
'TextFieldMarshaller',
'TokenizedVocabularyFieldMarshaller',
'URLDereferencingMixin',
'VocabularyLookupFieldMarshaller',
]
from datetime import datetime
import pytz
from StringIO import StringIO
import urllib
import simplejson
from zope.datetime import DateTimeError, DateTimeParser
from zope.component import getMultiAdapter, getUtility
from zope.interface import implements
from zope.publisher.interfaces import NotFound
from zope.security.proxy import removeSecurityProxy
from zope.traversing.browser import absoluteURL
from lazr.uri import URI, InvalidURIError
from lazr.restful.interfaces import (
IFieldMarshaller, IUnmarshallingDoesntNeedValue, IWebServiceConfiguration)
from lazr.restful.utils import safe_hasattr
class URLDereferencingMixin:
"""A mixin for any class that dereferences URLs into objects."""
def dereference_url(self, url):
"""Look up a resource in the web service by URL.
Representations and custom operations use URLs to refer to
resources in the web service. When processing an incoming
representation or custom operation it's often necessary to see
which object a URL refers to. This method calls the URL
traversal code to dereference a URL into a published object.
:param url: The URL to a resource.
:raise NotFound: If the URL does not designate a
published object.
"""
config = getUtility(IWebServiceConfiguration)
if config.use_https:
site_protocol = 'https'
default_port = '443'
else:
site_protocol = 'http'
default_port = '80'
request_host = self.request.get('HTTP_HOST', 'localhost')
if ':' in request_host:
request_host, request_port = request_host.split(':', 2)
else:
request_port = default_port
if not isinstance(url, basestring):
raise ValueError("got '%s', expected string: %r" % (
type(url).__name__, url))
uri = URI(url)
protocol = uri.scheme
host = uri.host
port = uri.port or default_port
path = uri.path
query = uri.query
fragment = uri.fragment
url_host_and_http_host_are_identical = (
host == request_host and port == request_port)
if (not url_host_and_http_host_are_identical
or protocol != site_protocol or query is not None
or fragment is not None):
raise NotFound(self, url, self.request)
path_parts = [urllib.unquote(part) for part in path.split('/')]
path_parts.pop(0)
path_parts.reverse()
request = config.createRequest(StringIO(), {'PATH_INFO' : path})
request.setTraversalStack(path_parts)
root = request.publication.getApplication(self.request)
return request.traverse(root)
class SimpleFieldMarshaller:
"""A marshaller that returns the same value it's served.
This implementation is meant to be subclassed.
"""
implements(IFieldMarshaller)
# Set this to type or tuple of types that the JSON value must be of.
_type = None
def __init__(self, field, request):
self.field = field
self.request = request
def marshall_from_json_data(self, value):
"""See `IFieldMarshaller`.
When value is None, return None, otherwise call
_marshall_from_json_data().
"""
if value is None:
return None
return self._marshall_from_json_data(value)
def marshall_from_request(self, value):
"""See `IFieldMarshaller`.
Try to decode value as a JSON-encoded string and pass it on to
_marshall_from_request() if it's not None. If value isn't a
JSON-encoded string, interpret it as string literal.
"""
if value != '':
try:
v = value
if isinstance (v, str):
v = v.decode('utf8') # assume utf8
# XXX gary 2009-03-28
# The use of the enclosing brackets is a hack to work around
# simplejson bug 43:
# http://code.google.com/p/simplejson/issues/detail?id=43
v = simplejson.loads(u'[%s]' % (v,))
except (ValueError, TypeError):
# Pass the value as is. This saves client from having to encode
# strings.
pass
else:
# see comment about simplejson bug above
value = v[0]
if value is None:
return None
return self._marshall_from_request(value)
def _marshall_from_request(self, value):
"""Hook method to marshall a non-null JSON value.
Default is to just call _marshall_from_json_data() with the value.
"""
return self._marshall_from_json_data(value)
def _marshall_from_json_data(self, value):
"""Hook method to marshall a no-null value.
Default is to return the value unchanged.
"""
if self._type is not None:
if not isinstance(value, self._type):
if isinstance(self._type, (tuple, list)):
expected_name = ", ".join(
a_type.__name__ for a_type in self._type)
else:
expected_name = self._type.__name__
raise ValueError(
"got '%s', expected %s: %r" % (
type(value).__name__, expected_name, value))
return value
@property
def representation_name(self):
"""See `IFieldMarshaller`.
Return the field name as is.
"""
return self.field.__name__
def unmarshall(self, entry, value):
"""See `IFieldMarshaller`.
Return the value as is.
"""
return value
def unmarshall_to_closeup(self, entry, value):
"""See `IFieldMarshaller`.
Return the value as is.
"""
return self.unmarshall(entry, value)
class BoolFieldMarshaller(SimpleFieldMarshaller):
"""A marshaller that transforms its value into an integer."""
_type = bool
class IntFieldMarshaller(SimpleFieldMarshaller):
"""A marshaller that transforms its value into an integer."""
_type = int
class FloatFieldMarshaller(SimpleFieldMarshaller):
"""A marshaller that transforms its value into an integer."""
_type = (float, int)
def _marshall_from_json_data(self, value):
"""See `SimpleFieldMarshaller`.
Converts the value to a float.
"""
return float(
super(FloatFieldMarshaller, self)._marshall_from_json_data(value))
class BytesFieldMarshaller(SimpleFieldMarshaller):
"""FieldMarshaller for IBytes field."""
_type = str
_type_error_message = 'not a string: %r'
@property
def representation_name(self):
"""See `IFieldMarshaller`.
Represent as a link to another resource.
"""
return "%s_link" % self.field.__name__
def unmarshall(self, entry, bytestorage):
"""See `IFieldMarshaller`.
Marshall as a link to the byte storage resource.
"""
return "%s/%s" % (absoluteURL(entry.context, self.request),
self.field.__name__)
def _marshall_from_request(self, value):
"""See `SimpleFieldMarshaller`.
Reads the data from file-like object, and converts non-strings into
one.
"""
if safe_hasattr(value, 'seek'):
value.seek(0)
value = value.read()
elif not isinstance(value, basestring):
value = str(value)
else:
# Leave string conversion to _marshall_from_json_data.
pass
return super(BytesFieldMarshaller, self)._marshall_from_request(value)
def _marshall_from_json_data(self, value):
"""See `SimpleFieldMarshaller`.
Convert all strings to byte strings.
"""
if isinstance(value, unicode):
value = value.encode('utf-8')
return super(
BytesFieldMarshaller, self)._marshall_from_json_data(value)
class TextFieldMarshaller(SimpleFieldMarshaller):
"""FieldMarshaller for IText fields."""
_type = unicode
_type_error_message = 'not a unicode string: %r'
def _marshall_from_request(self, value):
"""See `SimpleFieldMarshaller`.
Converts the value to unicode.
"""
value = unicode(value)
return super(TextFieldMarshaller, self)._marshall_from_request(value)
class FixedVocabularyFieldMarshaller(SimpleFieldMarshaller):
"""A marshaller for vocabulary fields whose vocabularies are fixed."""
def __init__(self, field, request, vocabulary):
"""Initialize with respect to a field.
:vocabulary: This argument is ignored; field.vocabulary is the
same object. This argument is passed because the
VocabularyLookupFieldMarshaller uses the vocabulary as
part of a multiadapter lookup of the appropriate
marshaller.
"""
super(FixedVocabularyFieldMarshaller, self).__init__(
field, request)
def unmarshall_to_closeup(self, entry, value):
"""Describe all values, not just the selected value."""
unmarshalled = []
for item in self.field.vocabulary:
item_dict = {'token' : item.token, 'title' : item.title}
if value.title == item.title:
item_dict['selected'] = True
unmarshalled.append(item_dict)
return unmarshalled
class TokenizedVocabularyFieldMarshaller(FixedVocabularyFieldMarshaller):
"""A marshaller that looks up value using a token in a vocabulary."""
def __init__(self, field, request, vocabulary):
super(TokenizedVocabularyFieldMarshaller, self).__init__(
field, request, vocabulary)
def _marshall_from_json_data(self, value):
"""See `SimpleFieldMarshaller`.
Looks up the value as a token in the vocabulary.
"""
try:
return self.field.vocabulary.getTermByToken(str(value)).value
except LookupError:
raise ValueError("%r isn't a valid token" % value)
class DateTimeFieldMarshaller(SimpleFieldMarshaller):
"""A marshaller that transforms its value into a datetime object."""
def _marshall_from_json_data(self, value):
"""Parse the value as a datetime object."""
try:
value = DateTimeParser().parse(value)
(year, month, day, hours, minutes, secondsAndMicroseconds,
timezone) = value
seconds = int(secondsAndMicroseconds)
microseconds = int(
round((secondsAndMicroseconds - seconds) * 1000000))
if timezone not in ['Z', '+0000', '-0000']:
raise ValueError("Time not in UTC.")
return datetime(year, month, day, hours, minutes,
seconds, microseconds, pytz.utc)
except DateTimeError:
raise ValueError("Value doesn't look like a date.")
except TypeError:
# JSON will serialize '20090131' as a number
raise ValueError("Value doesn't look like a date.")
class DateFieldMarshaller(DateTimeFieldMarshaller):
"""A marshaller that transforms its value into a date object."""
def _marshall_from_json_data(self, value):
"""Parse the value as a datetime.date object."""
super_class = super(DateFieldMarshaller, self)
date_time = super_class._marshall_from_json_data(value)
return date_time.date()
class AbstractCollectionFieldMarshaller(SimpleFieldMarshaller):
"""A marshaller for AbstractCollections.
It looks up the marshaller for its value-type, to handle its contained
elements.
"""
# The only valid JSON representation is a list.
_type = list
_type_error_message = 'not a list: %r'
def __init__(self, field, request):
"""See `SimpleFieldMarshaller`.
This also looks for the appropriate marshaller for value_type.
"""
super(AbstractCollectionFieldMarshaller, self).__init__(
field, request)
self.value_marshaller = getMultiAdapter(
(field.value_type, request), IFieldMarshaller)
def _marshall_from_json_data(self, value):
"""See `SimpleFieldMarshaller`.
Marshall every elements of the list using the appropriate
marshaller.
"""
value = super(
AbstractCollectionFieldMarshaller,
self)._marshall_from_json_data(value)
# In AbstractCollection subclasses, _type contains the type object,
# which can be used as a factory.
return self._python_collection_factory(
self.value_marshaller.marshall_from_json_data(item)
for item in value)
def _marshall_from_request(self, value):
"""See `SimpleFieldMarshaller`.
If the value isn't a list, transform it into a one-element list. That
allows web client to submit one-element list of strings
without having to JSON-encode it.
Additionally, all items in the list are marshalled using the
appropriate `IFieldMarshaller` for the value_type.
"""
if not isinstance(value, list):
value = [value]
return self._python_collection_factory(
self.value_marshaller.marshall_from_request(item)
for item in value)
@property
def _python_collection_factory(self):
"""Create the appropriate python collection from a list."""
# In AbstractCollection subclasses, _type contains the type object,
# which can be used as a factory.
return self.field._type
def unmarshall(self, entry, value):
"""See `SimpleFieldMarshaller`.
The collection is unmarshalled into a list and all its items are
unmarshalled using the appropriate FieldMarshaller.
"""
return [self.value_marshaller.unmarshall(entry, item)
for item in value]
class SetFieldMarshaller(AbstractCollectionFieldMarshaller):
"""Marshaller for sets."""
@property
def _python_collection_factory(self):
return set
class CollectionFieldMarshaller(SimpleFieldMarshaller):
"""A marshaller for collection fields."""
implements(IUnmarshallingDoesntNeedValue)
@property
def representation_name(self):
"""See `IFieldMarshaller`.
Make it clear that the value is a link to a collection.
"""
return "%s_collection_link" % self.field.__name__
def unmarshall(self, entry, value):
"""See `IFieldMarshaller`.
This returns a link to the scoped collection.
"""
return "%s/%s" % (absoluteURL(entry.context, self.request),
self.field.__name__)
def VocabularyLookupFieldMarshaller(field, request):
"""A marshaller that uses the underlying vocabulary.
This is just a factory function that does another adapter lookup
for a marshaller, one that can take into account the vocabulary
in addition to the field type (presumably Choice) and the request.
"""
return getMultiAdapter((field, request, field.vocabulary),
IFieldMarshaller)
class SimpleVocabularyLookupFieldMarshaller(FixedVocabularyFieldMarshaller):
"""A marshaller for vocabulary lookup by title."""
def __init__(self, field, request, vocabulary):
"""Initialize the marshaller with the vocabulary it'll use."""
super(SimpleVocabularyLookupFieldMarshaller, self).__init__(
field, request, vocabulary)
self.vocabulary = vocabulary
def _marshall_from_json_data(self, value):
"""Find an item in the vocabulary by title."""
valid_titles = []
for item in self.field.vocabulary.items:
if item.title == value:
return item
valid_titles.append(item.title)
raise ValueError(
('Invalid value "%s". Acceptable values are: %s' %
(value, ', '.join(valid_titles))).encode("utf-8"))
def unmarshall(self, entry, value):
if value is None:
return None
return value.title
class ObjectLookupFieldMarshaller(SimpleFieldMarshaller,
URLDereferencingMixin):
"""A marshaller that turns URLs into data model objects.
This marshaller can be used with a IChoice field (initialized
with a vocabulary) or with an IObject field (no vocabulary).
"""
def __init__(self, field, request, vocabulary=None):
super(ObjectLookupFieldMarshaller, self).__init__(field, request)
self.vocabulary = vocabulary
@property
def representation_name(self):
"""See `IFieldMarshaller`.
Make it clear that the value is a link to an object, not an object.
"""
return "%s_link" % self.field.__name__
def unmarshall(self, entry, value):
"""See `IFieldMarshaller`.
Represent an object as the URL to that object
"""
repr_value = None
if value is not None:
repr_value = absoluteURL(value, self.request)
return repr_value
def _marshall_from_json_data(self, value):
"""See `IFieldMarshaller`.
Look up the data model object by URL.
"""
try:
resource = self.dereference_url(value)
except NotFound:
# The URL doesn't correspond to any real object.
raise ValueError('No such object "%s".' % value)
except InvalidURIError:
raise ValueError('"%s" is not a valid URI.' % value)
# We looked up the URL and got the thing at the other end of
# the URL: a resource. But internally, a resource isn't a
# valid value for any schema field. Instead we want the object
# that serves as a resource's context. Any time we want to get
# to the object underlying a resource, we need to strip its
# security proxy.
return removeSecurityProxy(resource).context
|