741 lines
28 KiB
Python
741 lines
28 KiB
Python
|
#!/usr/bin/python
|
||
|
#
|
||
|
# Copyright (C) 2006, 2007, 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.
|
||
|
|
||
|
|
||
|
"""AtomService provides CRUD ops. in line with the Atom Publishing Protocol.
|
||
|
|
||
|
AtomService: Encapsulates the ability to perform insert, update and delete
|
||
|
operations with the Atom Publishing Protocol on which GData is
|
||
|
based. An instance can perform query, insertion, deletion, and
|
||
|
update.
|
||
|
|
||
|
HttpRequest: Function that performs a GET, POST, PUT, or DELETE HTTP request
|
||
|
to the specified end point. An AtomService object or a subclass can be
|
||
|
used to specify information about the request.
|
||
|
"""
|
||
|
|
||
|
__author__ = 'api.jscudder (Jeff Scudder)'
|
||
|
|
||
|
|
||
|
import atom.http_interface
|
||
|
import atom.url
|
||
|
import atom.http
|
||
|
import atom.token_store
|
||
|
|
||
|
import os
|
||
|
import httplib
|
||
|
import urllib
|
||
|
import re
|
||
|
import base64
|
||
|
import socket
|
||
|
import warnings
|
||
|
try:
|
||
|
from xml.etree import cElementTree as ElementTree
|
||
|
except ImportError:
|
||
|
try:
|
||
|
import cElementTree as ElementTree
|
||
|
except ImportError:
|
||
|
try:
|
||
|
from xml.etree import ElementTree
|
||
|
except ImportError:
|
||
|
from elementtree import ElementTree
|
||
|
import atom
|
||
|
|
||
|
|
||
|
class AtomService(object):
|
||
|
"""Performs Atom Publishing Protocol CRUD operations.
|
||
|
|
||
|
The AtomService contains methods to perform HTTP CRUD operations.
|
||
|
"""
|
||
|
|
||
|
# Default values for members
|
||
|
port = 80
|
||
|
ssl = False
|
||
|
# Set the current_token to force the AtomService to use this token
|
||
|
# instead of searching for an appropriate token in the token_store.
|
||
|
current_token = None
|
||
|
auto_store_tokens = True
|
||
|
auto_set_current_token = True
|
||
|
|
||
|
def _get_override_token(self):
|
||
|
return self.current_token
|
||
|
|
||
|
def _set_override_token(self, token):
|
||
|
self.current_token = token
|
||
|
|
||
|
override_token = property(_get_override_token, _set_override_token)
|
||
|
|
||
|
#@atom.v1_deprecated('Please use atom.client.AtomPubClient instead.')
|
||
|
def __init__(self, server=None, additional_headers=None,
|
||
|
application_name='', http_client=None, token_store=None):
|
||
|
"""Creates a new AtomService client.
|
||
|
|
||
|
Args:
|
||
|
server: string (optional) The start of a URL for the server
|
||
|
to which all operations should be directed. Example:
|
||
|
'www.google.com'
|
||
|
additional_headers: dict (optional) Any additional HTTP headers which
|
||
|
should be included with CRUD operations.
|
||
|
http_client: An object responsible for making HTTP requests using a
|
||
|
request method. If none is provided, a new instance of
|
||
|
atom.http.ProxiedHttpClient will be used.
|
||
|
token_store: Keeps a collection of authorization tokens which can be
|
||
|
applied to requests for a specific URLs. Critical methods are
|
||
|
find_token based on a URL (atom.url.Url or a string), add_token,
|
||
|
and remove_token.
|
||
|
"""
|
||
|
self.http_client = http_client or atom.http.ProxiedHttpClient()
|
||
|
self.token_store = token_store or atom.token_store.TokenStore()
|
||
|
self.server = server
|
||
|
self.additional_headers = additional_headers or {}
|
||
|
self.additional_headers['User-Agent'] = atom.http_interface.USER_AGENT % (
|
||
|
application_name,)
|
||
|
# If debug is True, the HTTPConnection will display debug information
|
||
|
self._set_debug(False)
|
||
|
|
||
|
__init__ = atom.v1_deprecated(
|
||
|
'Please use atom.client.AtomPubClient instead.')(
|
||
|
__init__)
|
||
|
|
||
|
def _get_debug(self):
|
||
|
return self.http_client.debug
|
||
|
|
||
|
def _set_debug(self, value):
|
||
|
self.http_client.debug = value
|
||
|
|
||
|
debug = property(_get_debug, _set_debug,
|
||
|
doc='If True, HTTP debug information is printed.')
|
||
|
|
||
|
def use_basic_auth(self, username, password, scopes=None):
|
||
|
if username is not None and password is not None:
|
||
|
if scopes is None:
|
||
|
scopes = [atom.token_store.SCOPE_ALL]
|
||
|
base_64_string = base64.encodestring('%s:%s' % (username, password))
|
||
|
token = BasicAuthToken('Basic %s' % base_64_string.strip(),
|
||
|
scopes=[atom.token_store.SCOPE_ALL])
|
||
|
if self.auto_set_current_token:
|
||
|
self.current_token = token
|
||
|
if self.auto_store_tokens:
|
||
|
return self.token_store.add_token(token)
|
||
|
return True
|
||
|
return False
|
||
|
|
||
|
def UseBasicAuth(self, username, password, for_proxy=False):
|
||
|
"""Sets an Authenticaiton: Basic HTTP header containing plaintext.
|
||
|
|
||
|
Deprecated, use use_basic_auth instead.
|
||
|
|
||
|
The username and password are base64 encoded and added to an HTTP header
|
||
|
which will be included in each request. Note that your username and
|
||
|
password are sent in plaintext.
|
||
|
|
||
|
Args:
|
||
|
username: str
|
||
|
password: str
|
||
|
"""
|
||
|
self.use_basic_auth(username, password)
|
||
|
|
||
|
#@atom.v1_deprecated('Please use atom.client.AtomPubClient for requests.')
|
||
|
def request(self, operation, url, data=None, headers=None,
|
||
|
url_params=None):
|
||
|
if isinstance(url, (str, unicode)):
|
||
|
if url.startswith('http:') and self.ssl:
|
||
|
# Force all requests to be https if self.ssl is True.
|
||
|
url = atom.url.parse_url('https:' + url[5:])
|
||
|
elif not url.startswith('http') and self.ssl:
|
||
|
url = atom.url.parse_url('https://%s%s' % (self.server, url))
|
||
|
elif not url.startswith('http'):
|
||
|
url = atom.url.parse_url('http://%s%s' % (self.server, url))
|
||
|
else:
|
||
|
url = atom.url.parse_url(url)
|
||
|
|
||
|
if url_params:
|
||
|
for name, value in url_params.iteritems():
|
||
|
url.params[name] = value
|
||
|
|
||
|
all_headers = self.additional_headers.copy()
|
||
|
if headers:
|
||
|
all_headers.update(headers)
|
||
|
|
||
|
# 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:
|
||
|
content_length = CalculateDataLength(data)
|
||
|
if content_length:
|
||
|
all_headers['Content-Length'] = str(content_length)
|
||
|
|
||
|
# Find an Authorization token for this URL if one is available.
|
||
|
if self.override_token:
|
||
|
auth_token = self.override_token
|
||
|
else:
|
||
|
auth_token = self.token_store.find_token(url)
|
||
|
return auth_token.perform_request(self.http_client, operation, url,
|
||
|
data=data, headers=all_headers)
|
||
|
|
||
|
request = atom.v1_deprecated(
|
||
|
'Please use atom.client.AtomPubClient for requests.')(
|
||
|
request)
|
||
|
|
||
|
# CRUD operations
|
||
|
def Get(self, uri, extra_headers=None, url_params=None, escape_params=True):
|
||
|
"""Query the APP server with the given URI
|
||
|
|
||
|
The uri is the portion of the URI after the server value
|
||
|
(server example: 'www.google.com').
|
||
|
|
||
|
Example use:
|
||
|
To perform a query against Google Base, set the server to
|
||
|
'base.google.com' and set the uri to '/base/feeds/...', where ... is
|
||
|
your query. For example, to find snippets for all digital cameras uri
|
||
|
should be set to: '/base/feeds/snippets?bq=digital+camera'
|
||
|
|
||
|
Args:
|
||
|
uri: string The query in the form of a URI. Example:
|
||
|
'/base/feeds/snippets?bq=digital+camera'.
|
||
|
extra_headers: dicty (optional) Extra HTTP headers to be included
|
||
|
in the GET request. These headers are in addition to
|
||
|
those stored in the client's additional_headers property.
|
||
|
The client automatically sets the Content-Type and
|
||
|
Authorization headers.
|
||
|
url_params: dict (optional) Additional URL parameters to be included
|
||
|
in the query. These are translated into query arguments
|
||
|
in the form '&dict_key=value&...'.
|
||
|
Example: {'max-results': '250'} becomes &max-results=250
|
||
|
escape_params: boolean (optional) If false, the calling code has already
|
||
|
ensured that the query will form a valid URL (all
|
||
|
reserved characters have been escaped). If true, this
|
||
|
method will escape the query and any URL parameters
|
||
|
provided.
|
||
|
|
||
|
Returns:
|
||
|
httplib.HTTPResponse The server's response to the GET request.
|
||
|
"""
|
||
|
return self.request('GET', uri, data=None, headers=extra_headers,
|
||
|
url_params=url_params)
|
||
|
|
||
|
def Post(self, data, uri, extra_headers=None, url_params=None,
|
||
|
escape_params=True, content_type='application/atom+xml'):
|
||
|
"""Insert data into an APP server at the given URI.
|
||
|
|
||
|
Args:
|
||
|
data: string, ElementTree._Element, or something with a __str__ method
|
||
|
The XML to be sent to the uri.
|
||
|
uri: string The location (feed) to which the data should be inserted.
|
||
|
Example: '/base/feeds/items'.
|
||
|
extra_headers: dict (optional) HTTP headers which are to be included.
|
||
|
The client automatically sets the Content-Type,
|
||
|
Authorization, and Content-Length headers.
|
||
|
url_params: dict (optional) Additional URL parameters to be included
|
||
|
in the URI. These are translated into query arguments
|
||
|
in the form '&dict_key=value&...'.
|
||
|
Example: {'max-results': '250'} becomes &max-results=250
|
||
|
escape_params: boolean (optional) If false, the calling code has already
|
||
|
ensured that the query will form a valid URL (all
|
||
|
reserved characters have been escaped). If true, this
|
||
|
method will escape the query and any URL parameters
|
||
|
provided.
|
||
|
|
||
|
Returns:
|
||
|
httplib.HTTPResponse Server's response to the POST request.
|
||
|
"""
|
||
|
if extra_headers is None:
|
||
|
extra_headers = {}
|
||
|
if content_type:
|
||
|
extra_headers['Content-Type'] = content_type
|
||
|
return self.request('POST', uri, data=data, headers=extra_headers,
|
||
|
url_params=url_params)
|
||
|
|
||
|
def Put(self, data, uri, extra_headers=None, url_params=None,
|
||
|
escape_params=True, content_type='application/atom+xml'):
|
||
|
"""Updates an entry at the given URI.
|
||
|
|
||
|
Args:
|
||
|
data: string, ElementTree._Element, or xml_wrapper.ElementWrapper The
|
||
|
XML containing the updated data.
|
||
|
uri: string A URI indicating entry to which the update will be applied.
|
||
|
Example: '/base/feeds/items/ITEM-ID'
|
||
|
extra_headers: dict (optional) HTTP headers which are to be included.
|
||
|
The client automatically sets the Content-Type,
|
||
|
Authorization, and Content-Length headers.
|
||
|
url_params: dict (optional) Additional URL parameters to be included
|
||
|
in the URI. These are translated into query arguments
|
||
|
in the form '&dict_key=value&...'.
|
||
|
Example: {'max-results': '250'} becomes &max-results=250
|
||
|
escape_params: boolean (optional) If false, the calling code has already
|
||
|
ensured that the query will form a valid URL (all
|
||
|
reserved characters have been escaped). If true, this
|
||
|
method will escape the query and any URL parameters
|
||
|
provided.
|
||
|
|
||
|
Returns:
|
||
|
httplib.HTTPResponse Server's response to the PUT request.
|
||
|
"""
|
||
|
if extra_headers is None:
|
||
|
extra_headers = {}
|
||
|
if content_type:
|
||
|
extra_headers['Content-Type'] = content_type
|
||
|
return self.request('PUT', uri, data=data, headers=extra_headers,
|
||
|
url_params=url_params)
|
||
|
|
||
|
def Delete(self, uri, extra_headers=None, url_params=None,
|
||
|
escape_params=True):
|
||
|
"""Deletes the entry at the given URI.
|
||
|
|
||
|
Args:
|
||
|
uri: string The URI of the entry to be deleted. Example:
|
||
|
'/base/feeds/items/ITEM-ID'
|
||
|
extra_headers: dict (optional) HTTP headers which are to be included.
|
||
|
The client automatically sets the Content-Type and
|
||
|
Authorization headers.
|
||
|
url_params: dict (optional) Additional URL parameters to be included
|
||
|
in the URI. These are translated into query arguments
|
||
|
in the form '&dict_key=value&...'.
|
||
|
Example: {'max-results': '250'} becomes &max-results=250
|
||
|
escape_params: boolean (optional) If false, the calling code has already
|
||
|
ensured that the query will form a valid URL (all
|
||
|
reserved characters have been escaped). If true, this
|
||
|
method will escape the query and any URL parameters
|
||
|
provided.
|
||
|
|
||
|
Returns:
|
||
|
httplib.HTTPResponse Server's response to the DELETE request.
|
||
|
"""
|
||
|
return self.request('DELETE', uri, data=None, headers=extra_headers,
|
||
|
url_params=url_params)
|
||
|
|
||
|
|
||
|
class BasicAuthToken(atom.http_interface.GenericToken):
|
||
|
def __init__(self, auth_header, scopes=None):
|
||
|
"""Creates a token used to add Basic Auth headers to HTTP requests.
|
||
|
|
||
|
Args:
|
||
|
auth_header: str The value for the Authorization header.
|
||
|
scopes: list of str or atom.url.Url specifying the beginnings of URLs
|
||
|
for which this token can be used. For example, if scopes contains
|
||
|
'http://example.com/foo', then this token can be used for a request to
|
||
|
'http://example.com/foo/bar' but it cannot be used for a request to
|
||
|
'http://example.com/baz'
|
||
|
"""
|
||
|
self.auth_header = auth_header
|
||
|
self.scopes = scopes or []
|
||
|
|
||
|
def perform_request(self, http_client, operation, url, data=None,
|
||
|
headers=None):
|
||
|
"""Sets the Authorization header to the basic auth string."""
|
||
|
if headers is None:
|
||
|
headers = {'Authorization':self.auth_header}
|
||
|
else:
|
||
|
headers['Authorization'] = self.auth_header
|
||
|
return http_client.request(operation, url, data=data, headers=headers)
|
||
|
|
||
|
def __str__(self):
|
||
|
return self.auth_header
|
||
|
|
||
|
def valid_for_scope(self, url):
|
||
|
"""Tells the caller if the token authorizes access to the desired URL.
|
||
|
"""
|
||
|
if isinstance(url, (str, unicode)):
|
||
|
url = atom.url.parse_url(url)
|
||
|
for scope in self.scopes:
|
||
|
if scope == atom.token_store.SCOPE_ALL:
|
||
|
return True
|
||
|
if isinstance(scope, (str, unicode)):
|
||
|
scope = atom.url.parse_url(scope)
|
||
|
if scope == url:
|
||
|
return True
|
||
|
# Check the host and the path, but ignore the port and protocol.
|
||
|
elif scope.host == url.host and not scope.path:
|
||
|
return True
|
||
|
elif scope.host == url.host and scope.path and not url.path:
|
||
|
continue
|
||
|
elif scope.host == url.host and url.path.startswith(scope.path):
|
||
|
return True
|
||
|
return False
|
||
|
|
||
|
|
||
|
def PrepareConnection(service, full_uri):
|
||
|
"""Opens a connection to the server based on the full URI.
|
||
|
|
||
|
This method is deprecated, instead use atom.http.HttpClient.request.
|
||
|
|
||
|
Examines the target URI and the proxy settings, which are set as
|
||
|
environment variables, to open a connection with the server. This
|
||
|
connection is used to make an HTTP request.
|
||
|
|
||
|
Args:
|
||
|
service: atom.AtomService or a subclass. It must have a server string which
|
||
|
represents the server host to which the request should be made. It may also
|
||
|
have a dictionary of additional_headers to send in the HTTP request.
|
||
|
full_uri: str Which is the target relative (lacks protocol and host) or
|
||
|
absolute URL to be opened. Example:
|
||
|
'https://www.google.com/accounts/ClientLogin' or
|
||
|
'base/feeds/snippets' where the server is set to www.google.com.
|
||
|
|
||
|
Returns:
|
||
|
A tuple containing the httplib.HTTPConnection and the full_uri for the
|
||
|
request.
|
||
|
"""
|
||
|
deprecation('calling deprecated function PrepareConnection')
|
||
|
(server, port, ssl, partial_uri) = ProcessUrl(service, full_uri)
|
||
|
if ssl:
|
||
|
# destination is https
|
||
|
proxy = os.environ.get('https_proxy')
|
||
|
if proxy:
|
||
|
(p_server, p_port, p_ssl, p_uri) = ProcessUrl(service, proxy, True)
|
||
|
proxy_username = os.environ.get('proxy-username')
|
||
|
if not proxy_username:
|
||
|
proxy_username = os.environ.get('proxy_username')
|
||
|
proxy_password = os.environ.get('proxy-password')
|
||
|
if not proxy_password:
|
||
|
proxy_password = os.environ.get('proxy_password')
|
||
|
if proxy_username:
|
||
|
user_auth = base64.encodestring('%s:%s' % (proxy_username,
|
||
|
proxy_password))
|
||
|
proxy_authorization = ('Proxy-authorization: Basic %s\r\n' % (
|
||
|
user_auth.strip()))
|
||
|
else:
|
||
|
proxy_authorization = ''
|
||
|
proxy_connect = 'CONNECT %s:%s HTTP/1.0\r\n' % (server, port)
|
||
|
user_agent = 'User-Agent: %s\r\n' % (
|
||
|
service.additional_headers['User-Agent'])
|
||
|
proxy_pieces = (proxy_connect + proxy_authorization + user_agent
|
||
|
+ '\r\n')
|
||
|
|
||
|
#now connect, very simple recv and error checking
|
||
|
p_sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
|
||
|
p_sock.connect((p_server,p_port))
|
||
|
p_sock.sendall(proxy_pieces)
|
||
|
response = ''
|
||
|
|
||
|
# Wait for the full response.
|
||
|
while response.find("\r\n\r\n") == -1:
|
||
|
response += p_sock.recv(8192)
|
||
|
|
||
|
p_status=response.split()[1]
|
||
|
if p_status!=str(200):
|
||
|
raise 'Error status=',str(p_status)
|
||
|
|
||
|
# Trivial setup for ssl socket.
|
||
|
ssl = socket.ssl(p_sock, None, None)
|
||
|
fake_sock = httplib.FakeSocket(p_sock, ssl)
|
||
|
|
||
|
# Initalize httplib and replace with the proxy socket.
|
||
|
connection = httplib.HTTPConnection(server)
|
||
|
connection.sock=fake_sock
|
||
|
full_uri = partial_uri
|
||
|
|
||
|
else:
|
||
|
connection = httplib.HTTPSConnection(server, port)
|
||
|
full_uri = partial_uri
|
||
|
|
||
|
else:
|
||
|
# destination is http
|
||
|
proxy = os.environ.get('http_proxy')
|
||
|
if proxy:
|
||
|
(p_server, p_port, p_ssl, p_uri) = ProcessUrl(service.server, proxy, True)
|
||
|
proxy_username = os.environ.get('proxy-username')
|
||
|
if not proxy_username:
|
||
|
proxy_username = os.environ.get('proxy_username')
|
||
|
proxy_password = os.environ.get('proxy-password')
|
||
|
if not proxy_password:
|
||
|
proxy_password = os.environ.get('proxy_password')
|
||
|
if proxy_username:
|
||
|
UseBasicAuth(service, proxy_username, proxy_password, True)
|
||
|
connection = httplib.HTTPConnection(p_server, p_port)
|
||
|
if not full_uri.startswith("http://"):
|
||
|
if full_uri.startswith("/"):
|
||
|
full_uri = "http://%s%s" % (service.server, full_uri)
|
||
|
else:
|
||
|
full_uri = "http://%s/%s" % (service.server, full_uri)
|
||
|
else:
|
||
|
connection = httplib.HTTPConnection(server, port)
|
||
|
full_uri = partial_uri
|
||
|
|
||
|
return (connection, full_uri)
|
||
|
|
||
|
|
||
|
def UseBasicAuth(service, username, password, for_proxy=False):
|
||
|
"""Sets an Authenticaiton: Basic HTTP header containing plaintext.
|
||
|
|
||
|
Deprecated, use AtomService.use_basic_auth insread.
|
||
|
|
||
|
The username and password are base64 encoded and added to an HTTP header
|
||
|
which will be included in each request. Note that your username and
|
||
|
password are sent in plaintext. The auth header is added to the
|
||
|
additional_headers dictionary in the service object.
|
||
|
|
||
|
Args:
|
||
|
service: atom.AtomService or a subclass which has an
|
||
|
additional_headers dict as a member.
|
||
|
username: str
|
||
|
password: str
|
||
|
"""
|
||
|
deprecation('calling deprecated function UseBasicAuth')
|
||
|
base_64_string = base64.encodestring('%s:%s' % (username, password))
|
||
|
base_64_string = base_64_string.strip()
|
||
|
if for_proxy:
|
||
|
header_name = 'Proxy-Authorization'
|
||
|
else:
|
||
|
header_name = 'Authorization'
|
||
|
service.additional_headers[header_name] = 'Basic %s' % (base_64_string,)
|
||
|
|
||
|
|
||
|
def ProcessUrl(service, url, for_proxy=False):
|
||
|
"""Processes a passed URL. If the URL does not begin with https?, then
|
||
|
the default value for server is used
|
||
|
|
||
|
This method is deprecated, use atom.url.parse_url instead.
|
||
|
"""
|
||
|
if not isinstance(url, atom.url.Url):
|
||
|
url = atom.url.parse_url(url)
|
||
|
|
||
|
server = url.host
|
||
|
ssl = False
|
||
|
port = 80
|
||
|
|
||
|
if not server:
|
||
|
if hasattr(service, 'server'):
|
||
|
server = service.server
|
||
|
else:
|
||
|
server = service
|
||
|
if not url.protocol and hasattr(service, 'ssl'):
|
||
|
ssl = service.ssl
|
||
|
if hasattr(service, 'port'):
|
||
|
port = service.port
|
||
|
else:
|
||
|
if url.protocol == 'https':
|
||
|
ssl = True
|
||
|
elif url.protocol == 'http':
|
||
|
ssl = False
|
||
|
if url.port:
|
||
|
port = int(url.port)
|
||
|
elif port == 80 and ssl:
|
||
|
port = 443
|
||
|
|
||
|
return (server, port, ssl, url.get_request_uri())
|
||
|
|
||
|
def DictionaryToParamList(url_parameters, escape_params=True):
|
||
|
"""Convert a dictionary of URL arguments into a URL parameter string.
|
||
|
|
||
|
This function is deprcated, use atom.url.Url instead.
|
||
|
|
||
|
Args:
|
||
|
url_parameters: The dictionaty of key-value pairs which will be converted
|
||
|
into URL parameters. For example,
|
||
|
{'dry-run': 'true', 'foo': 'bar'}
|
||
|
will become ['dry-run=true', 'foo=bar'].
|
||
|
|
||
|
Returns:
|
||
|
A list which contains a string for each key-value pair. The strings are
|
||
|
ready to be incorporated into a URL by using '&'.join([] + parameter_list)
|
||
|
"""
|
||
|
# Choose which function to use when modifying the query and parameters.
|
||
|
# Use quote_plus when escape_params is true.
|
||
|
transform_op = [str, urllib.quote_plus][bool(escape_params)]
|
||
|
# Create a list of tuples containing the escaped version of the
|
||
|
# parameter-value pairs.
|
||
|
parameter_tuples = [(transform_op(param), transform_op(value))
|
||
|
for param, value in (url_parameters or {}).items()]
|
||
|
# Turn parameter-value tuples into a list of strings in the form
|
||
|
# 'PARAMETER=VALUE'.
|
||
|
return ['='.join(x) for x in parameter_tuples]
|
||
|
|
||
|
|
||
|
def BuildUri(uri, url_params=None, escape_params=True):
|
||
|
"""Converts a uri string and a collection of parameters into a URI.
|
||
|
|
||
|
This function is deprcated, use atom.url.Url instead.
|
||
|
|
||
|
Args:
|
||
|
uri: string
|
||
|
url_params: dict (optional)
|
||
|
escape_params: boolean (optional)
|
||
|
uri: string The start of the desired URI. This string can alrady contain
|
||
|
URL parameters. Examples: '/base/feeds/snippets',
|
||
|
'/base/feeds/snippets?bq=digital+camera'
|
||
|
url_parameters: dict (optional) Additional URL parameters to be included
|
||
|
in the query. These are translated into query arguments
|
||
|
in the form '&dict_key=value&...'.
|
||
|
Example: {'max-results': '250'} becomes &max-results=250
|
||
|
escape_params: boolean (optional) If false, the calling code has already
|
||
|
ensured that the query will form a valid URL (all
|
||
|
reserved characters have been escaped). If true, this
|
||
|
method will escape the query and any URL parameters
|
||
|
provided.
|
||
|
|
||
|
Returns:
|
||
|
string The URI consisting of the escaped URL parameters appended to the
|
||
|
initial uri string.
|
||
|
"""
|
||
|
# Prepare URL parameters for inclusion into the GET request.
|
||
|
parameter_list = DictionaryToParamList(url_params, escape_params)
|
||
|
|
||
|
# Append the URL parameters to the URL.
|
||
|
if parameter_list:
|
||
|
if uri.find('?') != -1:
|
||
|
# If there are already URL parameters in the uri string, add the
|
||
|
# parameters after a new & character.
|
||
|
full_uri = '&'.join([uri] + parameter_list)
|
||
|
else:
|
||
|
# The uri string did not have any URL parameters (no ? character)
|
||
|
# so put a ? between the uri and URL parameters.
|
||
|
full_uri = '%s%s' % (uri, '?%s' % ('&'.join([] + parameter_list)))
|
||
|
else:
|
||
|
full_uri = uri
|
||
|
|
||
|
return full_uri
|
||
|
|
||
|
|
||
|
def HttpRequest(service, operation, data, uri, extra_headers=None,
|
||
|
url_params=None, escape_params=True, content_type='application/atom+xml'):
|
||
|
"""Performs an HTTP call to the server, supports GET, POST, PUT, and DELETE.
|
||
|
|
||
|
This method is deprecated, use atom.http.HttpClient.request instead.
|
||
|
|
||
|
Usage example, perform and HTTP GET on http://www.google.com/:
|
||
|
import atom.service
|
||
|
client = atom.service.AtomService()
|
||
|
http_response = client.Get('http://www.google.com/')
|
||
|
or you could set the client.server to 'www.google.com' and use the
|
||
|
following:
|
||
|
client.server = 'www.google.com'
|
||
|
http_response = client.Get('/')
|
||
|
|
||
|
Args:
|
||
|
service: atom.AtomService object which contains some of the parameters
|
||
|
needed to make the request. The following members are used to
|
||
|
construct the HTTP call: server (str), additional_headers (dict),
|
||
|
port (int), and ssl (bool).
|
||
|
operation: str The HTTP operation to be performed. This is usually one of
|
||
|
'GET', 'POST', 'PUT', or 'DELETE'
|
||
|
data: ElementTree, filestream, list of parts, or other object which can be
|
||
|
converted to a string.
|
||
|
Should be set to None when performing a GET or PUT.
|
||
|
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.
|
||
|
uri: The beginning of the URL to which the request should be sent.
|
||
|
Examples: '/', '/base/feeds/snippets',
|
||
|
'/m8/feeds/contacts/default/base'
|
||
|
extra_headers: dict of strings. HTTP headers which should be sent
|
||
|
in the request. These headers are in addition to those stored in
|
||
|
service.additional_headers.
|
||
|
url_params: dict of strings. Key value pairs to be added to the URL as
|
||
|
URL parameters. For example {'foo':'bar', 'test':'param'} will
|
||
|
become ?foo=bar&test=param.
|
||
|
escape_params: bool default True. If true, the keys and values in
|
||
|
url_params will be URL escaped when the form is constructed
|
||
|
(Special characters converted to %XX form.)
|
||
|
content_type: str The MIME type for the data being sent. Defaults to
|
||
|
'application/atom+xml', this is only used if data is set.
|
||
|
"""
|
||
|
deprecation('call to deprecated function HttpRequest')
|
||
|
full_uri = BuildUri(uri, url_params, escape_params)
|
||
|
(connection, full_uri) = PrepareConnection(service, full_uri)
|
||
|
|
||
|
if extra_headers is None:
|
||
|
extra_headers = {}
|
||
|
|
||
|
# Turn on debug mode if the debug member is set.
|
||
|
if service.debug:
|
||
|
connection.debuglevel = 1
|
||
|
|
||
|
connection.putrequest(operation, full_uri)
|
||
|
|
||
|
# If the list of headers does not include a Content-Length, attempt to
|
||
|
# calculate it based on the data object.
|
||
|
if (data and not service.additional_headers.has_key('Content-Length') and
|
||
|
not extra_headers.has_key('Content-Length')):
|
||
|
content_length = CalculateDataLength(data)
|
||
|
if content_length:
|
||
|
extra_headers['Content-Length'] = str(content_length)
|
||
|
|
||
|
if content_type:
|
||
|
extra_headers['Content-Type'] = content_type
|
||
|
|
||
|
# Send the HTTP headers.
|
||
|
if isinstance(service.additional_headers, dict):
|
||
|
for header in service.additional_headers:
|
||
|
connection.putheader(header, service.additional_headers[header])
|
||
|
if isinstance(extra_headers, dict):
|
||
|
for header in extra_headers:
|
||
|
connection.putheader(header, extra_headers[header])
|
||
|
connection.endheaders()
|
||
|
|
||
|
# If there is data, send it in the request.
|
||
|
if data:
|
||
|
if isinstance(data, list):
|
||
|
for data_part in data:
|
||
|
__SendDataPart(data_part, connection)
|
||
|
else:
|
||
|
__SendDataPart(data, connection)
|
||
|
|
||
|
# Return the HTTP Response from the server.
|
||
|
return connection.getresponse()
|
||
|
|
||
|
|
||
|
def __SendDataPart(data, connection):
|
||
|
"""This method is deprecated, use atom.http._send_data_part"""
|
||
|
deprecated('call to deprecated function __SendDataPart')
|
||
|
if isinstance(data, str):
|
||
|
#TODO add handling for unicode.
|
||
|
connection.send(data)
|
||
|
return
|
||
|
elif ElementTree.iselement(data):
|
||
|
connection.send(ElementTree.tostring(data))
|
||
|
return
|
||
|
# Check to see if data is a file-like object that has a read method.
|
||
|
elif hasattr(data, 'read'):
|
||
|
# Read the file and send it a chunk at a time.
|
||
|
while 1:
|
||
|
binarydata = data.read(100000)
|
||
|
if binarydata == '': break
|
||
|
connection.send(binarydata)
|
||
|
return
|
||
|
else:
|
||
|
# The data object was not a file.
|
||
|
# Try to convert to a string and send the data.
|
||
|
connection.send(str(data))
|
||
|
return
|
||
|
|
||
|
|
||
|
def CalculateDataLength(data):
|
||
|
"""Attempts to determine the length of the data to send.
|
||
|
|
||
|
This method will respond with a length only if the data is a string or
|
||
|
and ElementTree element.
|
||
|
|
||
|
Args:
|
||
|
data: object If this is not a string or ElementTree element this funtion
|
||
|
will return None.
|
||
|
"""
|
||
|
if isinstance(data, str):
|
||
|
return len(data)
|
||
|
elif isinstance(data, list):
|
||
|
return None
|
||
|
elif ElementTree.iselement(data):
|
||
|
return len(ElementTree.tostring(data))
|
||
|
elif hasattr(data, 'read'):
|
||
|
# If this is a file-like object, don't try to guess the length.
|
||
|
return None
|
||
|
else:
|
||
|
return len(str(data))
|
||
|
|
||
|
|
||
|
def deprecation(message):
|
||
|
warnings.warn(message, DeprecationWarning, stacklevel=2)
|