diff -r 261778de26ff -r 620f9b141567 thirdparty/google_appengine/google/appengine/api/urlfetch.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/thirdparty/google_appengine/google/appengine/api/urlfetch.py Tue Aug 26 21:49:54 2008 +0000 @@ -0,0 +1,243 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""URL downloading API. + +Methods defined in this module: + Fetch(): fetchs a given URL using an HTTP GET or POST +""" + + + + + +import UserDict + +from google.appengine.api import apiproxy_stub_map +from google.appengine.api import urlfetch_service_pb +from google.appengine.api.urlfetch_errors import * +from google.appengine.runtime import apiproxy_errors + + +GET = 1 +POST = 2 +HEAD = 3 +PUT = 4 +DELETE = 5 + + +_URL_STRING_MAP = { + 'GET': GET, + 'POST': POST, + 'HEAD': HEAD, + 'PUT': PUT, + 'DELETE': DELETE, +} + + +_VALID_METHODS = frozenset(_URL_STRING_MAP.values()) + + +class _CaselessDict(UserDict.IterableUserDict): + """Case insensitive dictionary. + + This class was lifted from os.py and slightly modified. + """ + + def __init__(self): + UserDict.IterableUserDict.__init__(self) + self.caseless_keys = {} + + def __setitem__(self, key, item): + """Set dictionary item. + + Args: + key: Key of new item. Key is case insensitive, so "d['Key'] = value " + will replace previous values set by "d['key'] = old_value". + item: Item to store. + """ + caseless_key = key.lower() + if caseless_key in self.caseless_keys: + del self.data[self.caseless_keys[caseless_key]] + self.caseless_keys[caseless_key] = key + self.data[key] = item + + def __getitem__(self, key): + """Get dictionary item. + + Args: + key: Key of item to get. Key is case insensitive, so "d['Key']" is the + same as "d['key']". + + Returns: + Item associated with key. + """ + return self.data[self.caseless_keys[key.lower()]] + + def __delitem__(self, key): + """Remove item from dictionary. + + Args: + key: Key of item to remove. Key is case insensitive, so "del d['Key']" is + the same as "del d['key']" + """ + caseless_key = key.lower() + del self.data[self.caseless_keys[caseless_key]] + del self.caseless_keys[caseless_key] + + def has_key(self, key): + """Determine if dictionary has item with specific key. + + Args: + key: Key to check for presence. Key is case insensitive, so + "d.has_key('Key')" evaluates to the same value as "d.has_key('key')". + + Returns: + True if dictionary contains key, else False. + """ + return key.lower() in self.caseless_keys + + def __contains__(self, key): + """Same as 'has_key', but used for 'in' operator.'""" + return self.has_key(key) + + def get(self, key, failobj=None): + """Get dictionary item, defaulting to another value if it does not exist. + + Args: + key: Key of item to get. Key is case insensitive, so "d['Key']" is the + same as "d['key']". + failobj: Value to return if key not in dictionary. + """ + try: + cased_key = self.caseless_keys[key.lower()] + except KeyError: + return failobj + return self.data[cased_key] + + def update(self, dict=None, **kwargs): + """Update dictionary using values from another dictionary and keywords. + + Args: + dict: Dictionary to update from. + kwargs: Keyword arguments to update from. + """ + if dict: + try: + keys = dict.keys() + except AttributeError: + for k, v in dict: + self[k] = v + else: + for k in keys: + self[k] = dict[k] + if kwargs: + self.update(kwargs) + + def copy(self): + """Make a shallow, case sensitive copy of self.""" + return dict(self) + + +def fetch(url, payload=None, method=GET, headers={}, allow_truncated=False): + """Fetches the given HTTP URL, blocking until the result is returned. + + Other optional parameters are: + method: GET, POST, HEAD, PUT, or DELETE + payload: POST or PUT payload (implies method is not GET, HEAD, or DELETE) + headers: dictionary of HTTP headers to send with the request + allow_truncated: if true, truncate large responses and return them without + error. otherwise, ResponseTooLargeError will be thrown when a response is + truncated. + + We use a HTTP/1.1 compliant proxy to fetch the result. + + The returned data structure has the following fields: + content: string containing the response from the server + status_code: HTTP status code returned by the server + headers: dictionary of headers returned by the server + + If the URL is an empty string or obviously invalid, we throw an + urlfetch.InvalidURLError. If the server cannot be contacted, we throw a + urlfetch.DownloadError. Note that HTTP errors are returned as a part + of the returned structure, so HTTP errors like 404 do not result in an + exception. + """ + request = urlfetch_service_pb.URLFetchRequest() + response = urlfetch_service_pb.URLFetchResponse() + request.set_url(url) + + if isinstance(method, basestring): + method = method.upper() + method = _URL_STRING_MAP.get(method, method) + if method not in _VALID_METHODS: + raise InvalidMethodError('Invalid method %s.' % str(method)) + if method == GET: + request.set_method(urlfetch_service_pb.URLFetchRequest.GET) + elif method == POST: + request.set_method(urlfetch_service_pb.URLFetchRequest.POST) + elif method == HEAD: + request.set_method(urlfetch_service_pb.URLFetchRequest.HEAD) + elif method == PUT: + request.set_method(urlfetch_service_pb.URLFetchRequest.PUT) + elif method == DELETE: + request.set_method(urlfetch_service_pb.URLFetchRequest.DELETE) + + if payload and (method == POST or method == PUT): + request.set_payload(payload) + + for key, value in headers.iteritems(): + header_proto = request.add_header() + header_proto.set_key(key) + header_proto.set_value(value) + + try: + apiproxy_stub_map.MakeSyncCall('urlfetch', 'Fetch', request, response) + except apiproxy_errors.ApplicationError, e: + if (e.application_error == + urlfetch_service_pb.URLFetchServiceError.INVALID_URL): + raise InvalidURLError(str(e)) + if (e.application_error == + urlfetch_service_pb.URLFetchServiceError.UNSPECIFIED_ERROR): + raise DownloadError(str(e)) + if (e.application_error == + urlfetch_service_pb.URLFetchServiceError.FETCH_ERROR): + raise DownloadError(str(e)) + if (e.application_error == + urlfetch_service_pb.URLFetchServiceError.RESPONSE_TOO_LARGE): + raise ResponseTooLargeError(None) + raise e + result = _URLFetchResult(response) + + if not allow_truncated and response.contentwastruncated(): + raise ResponseTooLargeError(result) + + return result + +Fetch = fetch + + +class _URLFetchResult(object): + """A Pythonic representation of our fetch response protocol buffer.""" + def __init__(self, response_proto): + self.__pb = response_proto + self.content = response_proto.content() + self.status_code = response_proto.statuscode() + self.content_was_truncated = response_proto.contentwastruncated() + self.headers = _CaselessDict() + for header_proto in response_proto.header_list(): + self.headers[header_proto.key()] = header_proto.value()