/gdata/alt/appengine.py
http://radioappz.googlecode.com/ · Python · 321 lines · 232 code · 13 blank · 76 comment · 29 complexity · 9b1d6fbc0e0187b36a21185669ca445d MD5 · raw file
- #!/usr/bin/python
- #
- # Copyright (C) 2008 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.
- """Provides HTTP functions for gdata.service to use on Google App Engine
- AppEngineHttpClient: Provides an HTTP request method which uses App Engine's
- urlfetch API. Set the http_client member of a GDataService object to an
- instance of an AppEngineHttpClient to allow the gdata library to run on
- Google App Engine.
- run_on_appengine: Function which will modify an existing GDataService object
- to allow it to run on App Engine. It works by creating a new instance of
- the AppEngineHttpClient and replacing the GDataService object's
- http_client.
- """
- __author__ = 'api.jscudder (Jeff Scudder)'
- import StringIO
- import pickle
- import atom.http_interface
- import atom.token_store
- from google.appengine.api import urlfetch
- from google.appengine.ext import db
- from google.appengine.api import users
- from google.appengine.api import memcache
- def run_on_appengine(gdata_service, store_tokens=True,
- single_user_mode=False, deadline=None):
- """Modifies a GDataService object to allow it to run on App Engine.
- Args:
- gdata_service: An instance of AtomService, GDataService, or any
- of their subclasses which has an http_client member and a
- token_store member.
- store_tokens: Boolean, defaults to True. If True, the gdata_service
- will attempt to add each token to it's token_store when
- SetClientLoginToken or SetAuthSubToken is called. If False
- the tokens will not automatically be added to the
- token_store.
- single_user_mode: Boolean, defaults to False. If True, the current_token
- member of gdata_service will be set when
- SetClientLoginToken or SetAuthTubToken is called. If set
- to True, the current_token is set in the gdata_service
- and anyone who accesses the object will use the same
- token.
-
- Note: If store_tokens is set to False and
- single_user_mode is set to False, all tokens will be
- ignored, since the library assumes: the tokens should not
- be stored in the datastore and they should not be stored
- in the gdata_service object. This will make it
- impossible to make requests which require authorization.
- deadline: int (optional) The number of seconds to wait for a response
- before timing out on the HTTP request. If no deadline is
- specified, the deafault deadline for HTTP requests from App
- Engine is used. The maximum is currently 10 (for 10 seconds).
- The default deadline for App Engine is 5 seconds.
- """
- gdata_service.http_client = AppEngineHttpClient(deadline=deadline)
- gdata_service.token_store = AppEngineTokenStore()
- gdata_service.auto_store_tokens = store_tokens
- gdata_service.auto_set_current_token = single_user_mode
- return gdata_service
- class AppEngineHttpClient(atom.http_interface.GenericHttpClient):
- def __init__(self, headers=None, deadline=None):
- self.debug = False
- self.headers = headers or {}
- self.deadline = deadline
- def request(self, operation, url, data=None, headers=None):
- """Performs an HTTP call to the server, supports GET, POST, PUT, and
- DELETE.
- Usage example, perform and HTTP GET on http://www.google.com/:
- import atom.http
- client = atom.http.HttpClient()
- http_response = client.request('GET', 'http://www.google.com/')
- Args:
- operation: str The HTTP operation to be performed. This is usually one
- of 'GET', 'POST', 'PUT', or 'DELETE'
- data: filestream, list of parts, or other object which can be converted
- to a string. Should be set to None when performing a GET or DELETE.
- If data is a file-like object which can be read, this method will
- read a chunk of 100K bytes at a time and send them.
- If the data is a list of parts to be sent, each part will be
- evaluated and sent.
- url: The full URL to which the request should be sent. Can be a string
- or atom.url.Url.
- headers: dict of strings. HTTP headers which should be sent
- in the request.
- """
- all_headers = self.headers.copy()
- if headers:
- all_headers.update(headers)
- # Construct the full payload.
- # Assume that data is None or a string.
- data_str = data
- if data:
- if isinstance(data, list):
- # If data is a list of different objects, convert them all to strings
- # and join them together.
- converted_parts = [_convert_data_part(x) for x in data]
- data_str = ''.join(converted_parts)
- else:
- data_str = _convert_data_part(data)
- # If the list of headers does not include a Content-Length, attempt to
- # calculate it based on the data object.
- if data and 'Content-Length' not in all_headers:
- all_headers['Content-Length'] = str(len(data_str))
- # Set the content type to the default value if none was set.
- if 'Content-Type' not in all_headers:
- all_headers['Content-Type'] = 'application/atom+xml'
- # Lookup the urlfetch operation which corresponds to the desired HTTP verb.
- if operation == 'GET':
- method = urlfetch.GET
- elif operation == 'POST':
- method = urlfetch.POST
- elif operation == 'PUT':
- method = urlfetch.PUT
- elif operation == 'DELETE':
- method = urlfetch.DELETE
- else:
- method = None
- if self.deadline is None:
- return HttpResponse(urlfetch.Fetch(url=str(url), payload=data_str,
- method=method, headers=all_headers, follow_redirects=False))
- return HttpResponse(urlfetch.Fetch(url=str(url), payload=data_str,
- method=method, headers=all_headers, follow_redirects=False,
- deadline=self.deadline))
- def _convert_data_part(data):
- if not data or isinstance(data, str):
- return data
- elif hasattr(data, 'read'):
- # data is a file like object, so read it completely.
- return data.read()
- # The data object was not a file.
- # Try to convert to a string and send the data.
- return str(data)
- class HttpResponse(object):
- """Translates a urlfetch resoinse to look like an hhtplib resoinse.
- Used to allow the resoinse from HttpRequest to be usable by gdata.service
- methods.
- """
- def __init__(self, urlfetch_response):
- self.body = StringIO.StringIO(urlfetch_response.content)
- self.headers = urlfetch_response.headers
- self.status = urlfetch_response.status_code
- self.reason = ''
- def read(self, length=None):
- if not length:
- return self.body.read()
- else:
- return self.body.read(length)
- def getheader(self, name):
- if not self.headers.has_key(name):
- return self.headers[name.lower()]
- return self.headers[name]
- class TokenCollection(db.Model):
- """Datastore Model which associates auth tokens with the current user."""
- user = db.UserProperty()
- pickled_tokens = db.BlobProperty()
- class AppEngineTokenStore(atom.token_store.TokenStore):
- """Stores the user's auth tokens in the App Engine datastore.
- Tokens are only written to the datastore if a user is signed in (if
- users.get_current_user() returns a user object).
- """
- def __init__(self):
- self.user = None
- def add_token(self, token):
- """Associates the token with the current user and stores it.
-
- If there is no current user, the token will not be stored.
- Returns:
- False if the token was not stored.
- """
- tokens = load_auth_tokens(self.user)
- if not hasattr(token, 'scopes') or not token.scopes:
- return False
- for scope in token.scopes:
- tokens[str(scope)] = token
- key = save_auth_tokens(tokens, self.user)
- if key:
- return True
- return False
- def find_token(self, url):
- """Searches the current user's collection of token for a token which can
- be used for a request to the url.
- Returns:
- The stored token which belongs to the current user and is valid for the
- desired URL. If there is no current user, or there is no valid user
- token in the datastore, a atom.http_interface.GenericToken is returned.
- """
- if url is None:
- return None
- if isinstance(url, (str, unicode)):
- url = atom.url.parse_url(url)
- tokens = load_auth_tokens(self.user)
- if url in tokens:
- token = tokens[url]
- if token.valid_for_scope(url):
- return token
- else:
- del tokens[url]
- save_auth_tokens(tokens, self.user)
- for scope, token in tokens.iteritems():
- if token.valid_for_scope(url):
- return token
- return atom.http_interface.GenericToken()
- def remove_token(self, token):
- """Removes the token from the current user's collection in the datastore.
-
- Returns:
- False if the token was not removed, this could be because the token was
- not in the datastore, or because there is no current user.
- """
- token_found = False
- scopes_to_delete = []
- tokens = load_auth_tokens(self.user)
- for scope, stored_token in tokens.iteritems():
- if stored_token == token:
- scopes_to_delete.append(scope)
- token_found = True
- for scope in scopes_to_delete:
- del tokens[scope]
- if token_found:
- save_auth_tokens(tokens, self.user)
- return token_found
- def remove_all_tokens(self):
- """Removes all of the current user's tokens from the datastore."""
- save_auth_tokens({}, self.user)
- def save_auth_tokens(token_dict, user=None):
- """Associates the tokens with the current user and writes to the datastore.
-
- If there us no current user, the tokens are not written and this function
- returns None.
- Returns:
- The key of the datastore entity containing the user's tokens, or None if
- there was no current user.
- """
- if user is None:
- user = users.get_current_user()
- if user is None:
- return None
- memcache.set('gdata_pickled_tokens:%s' % user, pickle.dumps(token_dict))
- user_tokens = TokenCollection.all().filter('user =', user).get()
- if user_tokens:
- user_tokens.pickled_tokens = pickle.dumps(token_dict)
- return user_tokens.put()
- else:
- user_tokens = TokenCollection(
- user=user,
- pickled_tokens=pickle.dumps(token_dict))
- return user_tokens.put()
-
- def load_auth_tokens(user=None):
- """Reads a dictionary of the current user's tokens from the datastore.
-
- If there is no current user (a user is not signed in to the app) or the user
- does not have any tokens, an empty dictionary is returned.
- """
- if user is None:
- user = users.get_current_user()
- if user is None:
- return {}
- pickled_tokens = memcache.get('gdata_pickled_tokens:%s' % user)
- if pickled_tokens:
- return pickle.loads(pickled_tokens)
- user_tokens = TokenCollection.all().filter('user =', user).get()
- if user_tokens:
- memcache.set('gdata_pickled_tokens:%s' % user, user_tokens.pickled_tokens)
- return pickle.loads(user_tokens.pickled_tokens)
- return {}