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