/atom/service.py
Python | 740 lines | 681 code | 16 blank | 43 comment | 6 complexity | 4dfc1acbbc1a42a1d4226c2edf18155e MD5 | raw file
1#!/usr/bin/python 2# 3# Copyright (C) 2006, 2007, 2008 Google Inc. 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17 18"""AtomService provides CRUD ops. in line with the Atom Publishing Protocol. 19 20 AtomService: Encapsulates the ability to perform insert, update and delete 21 operations with the Atom Publishing Protocol on which GData is 22 based. An instance can perform query, insertion, deletion, and 23 update. 24 25 HttpRequest: Function that performs a GET, POST, PUT, or DELETE HTTP request 26 to the specified end point. An AtomService object or a subclass can be 27 used to specify information about the request. 28""" 29 30__author__ = 'api.jscudder (Jeff Scudder)' 31 32 33import atom.http_interface 34import atom.url 35import atom.http 36import atom.token_store 37 38import os 39import httplib 40import urllib 41import re 42import base64 43import socket 44import warnings 45try: 46 from xml.etree import cElementTree as ElementTree 47except ImportError: 48 try: 49 import cElementTree as ElementTree 50 except ImportError: 51 try: 52 from xml.etree import ElementTree 53 except ImportError: 54 from elementtree import ElementTree 55import atom 56 57 58class AtomService(object): 59 """Performs Atom Publishing Protocol CRUD operations. 60 61 The AtomService contains methods to perform HTTP CRUD operations. 62 """ 63 64 # Default values for members 65 port = 80 66 ssl = False 67 # Set the current_token to force the AtomService to use this token 68 # instead of searching for an appropriate token in the token_store. 69 current_token = None 70 auto_store_tokens = True 71 auto_set_current_token = True 72 73 def _get_override_token(self): 74 return self.current_token 75 76 def _set_override_token(self, token): 77 self.current_token = token 78 79 override_token = property(_get_override_token, _set_override_token) 80 81 #@atom.v1_deprecated('Please use atom.client.AtomPubClient instead.') 82 def __init__(self, server=None, additional_headers=None, 83 application_name='', http_client=None, token_store=None): 84 """Creates a new AtomService client. 85 86 Args: 87 server: string (optional) The start of a URL for the server 88 to which all operations should be directed. Example: 89 'www.google.com' 90 additional_headers: dict (optional) Any additional HTTP headers which 91 should be included with CRUD operations. 92 http_client: An object responsible for making HTTP requests using a 93 request method. If none is provided, a new instance of 94 atom.http.ProxiedHttpClient will be used. 95 token_store: Keeps a collection of authorization tokens which can be 96 applied to requests for a specific URLs. Critical methods are 97 find_token based on a URL (atom.url.Url or a string), add_token, 98 and remove_token. 99 """ 100 self.http_client = http_client or atom.http.ProxiedHttpClient() 101 self.token_store = token_store or atom.token_store.TokenStore() 102 self.server = server 103 self.additional_headers = additional_headers or {} 104 self.additional_headers['User-Agent'] = atom.http_interface.USER_AGENT % ( 105 application_name,) 106 # If debug is True, the HTTPConnection will display debug information 107 self._set_debug(False) 108 109 __init__ = atom.v1_deprecated( 110 'Please use atom.client.AtomPubClient instead.')( 111 __init__) 112 113 def _get_debug(self): 114 return self.http_client.debug 115 116 def _set_debug(self, value): 117 self.http_client.debug = value 118 119 debug = property(_get_debug, _set_debug, 120 doc='If True, HTTP debug information is printed.') 121 122 def use_basic_auth(self, username, password, scopes=None): 123 if username is not None and password is not None: 124 if scopes is None: 125 scopes = [atom.token_store.SCOPE_ALL] 126 base_64_string = base64.encodestring('%s:%s' % (username, password)) 127 token = BasicAuthToken('Basic %s' % base_64_string.strip(), 128 scopes=[atom.token_store.SCOPE_ALL]) 129 if self.auto_set_current_token: 130 self.current_token = token 131 if self.auto_store_tokens: 132 return self.token_store.add_token(token) 133 return True 134 return False 135 136 def UseBasicAuth(self, username, password, for_proxy=False): 137 """Sets an Authenticaiton: Basic HTTP header containing plaintext. 138 139 Deprecated, use use_basic_auth instead. 140 141 The username and password are base64 encoded and added to an HTTP header 142 which will be included in each request. Note that your username and 143 password are sent in plaintext. 144 145 Args: 146 username: str 147 password: str 148 """ 149 self.use_basic_auth(username, password) 150 151 #@atom.v1_deprecated('Please use atom.client.AtomPubClient for requests.') 152 def request(self, operation, url, data=None, headers=None, 153 url_params=None): 154 if isinstance(url, (str, unicode)): 155 if url.startswith('http:') and self.ssl: 156 # Force all requests to be https if self.ssl is True. 157 url = atom.url.parse_url('https:' + url[5:]) 158 elif not url.startswith('http') and self.ssl: 159 url = atom.url.parse_url('https://%s%s' % (self.server, url)) 160 elif not url.startswith('http'): 161 url = atom.url.parse_url('http://%s%s' % (self.server, url)) 162 else: 163 url = atom.url.parse_url(url) 164 165 if url_params: 166 for name, value in url_params.iteritems(): 167 url.params[name] = value 168 169 all_headers = self.additional_headers.copy() 170 if headers: 171 all_headers.update(headers) 172 173 # If the list of headers does not include a Content-Length, attempt to 174 # calculate it based on the data object. 175 if data and 'Content-Length' not in all_headers: 176 content_length = CalculateDataLength(data) 177 if content_length: 178 all_headers['Content-Length'] = str(content_length) 179 180 # Find an Authorization token for this URL if one is available. 181 if self.override_token: 182 auth_token = self.override_token 183 else: 184 auth_token = self.token_store.find_token(url) 185 return auth_token.perform_request(self.http_client, operation, url, 186 data=data, headers=all_headers) 187 188 request = atom.v1_deprecated( 189 'Please use atom.client.AtomPubClient for requests.')( 190 request) 191 192 # CRUD operations 193 def Get(self, uri, extra_headers=None, url_params=None, escape_params=True): 194 """Query the APP server with the given URI 195 196 The uri is the portion of the URI after the server value 197 (server example: 'www.google.com'). 198 199 Example use: 200 To perform a query against Google Base, set the server to 201 'base.google.com' and set the uri to '/base/feeds/...', where ... is 202 your query. For example, to find snippets for all digital cameras uri 203 should be set to: '/base/feeds/snippets?bq=digital+camera' 204 205 Args: 206 uri: string The query in the form of a URI. Example: 207 '/base/feeds/snippets?bq=digital+camera'. 208 extra_headers: dicty (optional) Extra HTTP headers to be included 209 in the GET request. These headers are in addition to 210 those stored in the client's additional_headers property. 211 The client automatically sets the Content-Type and 212 Authorization headers. 213 url_params: dict (optional) Additional URL parameters to be included 214 in the query. These are translated into query arguments 215 in the form '&dict_key=value&...'. 216 Example: {'max-results': '250'} becomes &max-results=250 217 escape_params: boolean (optional) If false, the calling code has already 218 ensured that the query will form a valid URL (all 219 reserved characters have been escaped). If true, this 220 method will escape the query and any URL parameters 221 provided. 222 223 Returns: 224 httplib.HTTPResponse The server's response to the GET request. 225 """ 226 return self.request('GET', uri, data=None, headers=extra_headers, 227 url_params=url_params) 228 229 def Post(self, data, uri, extra_headers=None, url_params=None, 230 escape_params=True, content_type='application/atom+xml'): 231 """Insert data into an APP server at the given URI. 232 233 Args: 234 data: string, ElementTree._Element, or something with a __str__ method 235 The XML to be sent to the uri. 236 uri: string The location (feed) to which the data should be inserted. 237 Example: '/base/feeds/items'. 238 extra_headers: dict (optional) HTTP headers which are to be included. 239 The client automatically sets the Content-Type, 240 Authorization, and Content-Length headers. 241 url_params: dict (optional) Additional URL parameters to be included 242 in the URI. These are translated into query arguments 243 in the form '&dict_key=value&...'. 244 Example: {'max-results': '250'} becomes &max-results=250 245 escape_params: boolean (optional) If false, the calling code has already 246 ensured that the query will form a valid URL (all 247 reserved characters have been escaped). If true, this 248 method will escape the query and any URL parameters 249 provided. 250 251 Returns: 252 httplib.HTTPResponse Server's response to the POST request. 253 """ 254 if extra_headers is None: 255 extra_headers = {} 256 if content_type: 257 extra_headers['Content-Type'] = content_type 258 return self.request('POST', uri, data=data, headers=extra_headers, 259 url_params=url_params) 260 261 def Put(self, data, uri, extra_headers=None, url_params=None, 262 escape_params=True, content_type='application/atom+xml'): 263 """Updates an entry at the given URI. 264 265 Args: 266 data: string, ElementTree._Element, or xml_wrapper.ElementWrapper The 267 XML containing the updated data. 268 uri: string A URI indicating entry to which the update will be applied. 269 Example: '/base/feeds/items/ITEM-ID' 270 extra_headers: dict (optional) HTTP headers which are to be included. 271 The client automatically sets the Content-Type, 272 Authorization, and Content-Length headers. 273 url_params: dict (optional) Additional URL parameters to be included 274 in the URI. These are translated into query arguments 275 in the form '&dict_key=value&...'. 276 Example: {'max-results': '250'} becomes &max-results=250 277 escape_params: boolean (optional) If false, the calling code has already 278 ensured that the query will form a valid URL (all 279 reserved characters have been escaped). If true, this 280 method will escape the query and any URL parameters 281 provided. 282 283 Returns: 284 httplib.HTTPResponse Server's response to the PUT request. 285 """ 286 if extra_headers is None: 287 extra_headers = {} 288 if content_type: 289 extra_headers['Content-Type'] = content_type 290 return self.request('PUT', uri, data=data, headers=extra_headers, 291 url_params=url_params) 292 293 def Delete(self, uri, extra_headers=None, url_params=None, 294 escape_params=True): 295 """Deletes the entry at the given URI. 296 297 Args: 298 uri: string The URI of the entry to be deleted. Example: 299 '/base/feeds/items/ITEM-ID' 300 extra_headers: dict (optional) HTTP headers which are to be included. 301 The client automatically sets the Content-Type and 302 Authorization headers. 303 url_params: dict (optional) Additional URL parameters to be included 304 in the URI. These are translated into query arguments 305 in the form '&dict_key=value&...'. 306 Example: {'max-results': '250'} becomes &max-results=250 307 escape_params: boolean (optional) If false, the calling code has already 308 ensured that the query will form a valid URL (all 309 reserved characters have been escaped). If true, this 310 method will escape the query and any URL parameters 311 provided. 312 313 Returns: 314 httplib.HTTPResponse Server's response to the DELETE request. 315 """ 316 return self.request('DELETE', uri, data=None, headers=extra_headers, 317 url_params=url_params) 318 319 320class BasicAuthToken(atom.http_interface.GenericToken): 321 def __init__(self, auth_header, scopes=None): 322 """Creates a token used to add Basic Auth headers to HTTP requests. 323 324 Args: 325 auth_header: str The value for the Authorization header. 326 scopes: list of str or atom.url.Url specifying the beginnings of URLs 327 for which this token can be used. For example, if scopes contains 328 'http://example.com/foo', then this token can be used for a request to 329 'http://example.com/foo/bar' but it cannot be used for a request to 330 'http://example.com/baz' 331 """ 332 self.auth_header = auth_header 333 self.scopes = scopes or [] 334 335 def perform_request(self, http_client, operation, url, data=None, 336 headers=None): 337 """Sets the Authorization header to the basic auth string.""" 338 if headers is None: 339 headers = {'Authorization':self.auth_header} 340 else: 341 headers['Authorization'] = self.auth_header 342 return http_client.request(operation, url, data=data, headers=headers) 343 344 def __str__(self): 345 return self.auth_header 346 347 def valid_for_scope(self, url): 348 """Tells the caller if the token authorizes access to the desired URL. 349 """ 350 if isinstance(url, (str, unicode)): 351 url = atom.url.parse_url(url) 352 for scope in self.scopes: 353 if scope == atom.token_store.SCOPE_ALL: 354 return True 355 if isinstance(scope, (str, unicode)): 356 scope = atom.url.parse_url(scope) 357 if scope == url: 358 return True 359 # Check the host and the path, but ignore the port and protocol. 360 elif scope.host == url.host and not scope.path: 361 return True 362 elif scope.host == url.host and scope.path and not url.path: 363 continue 364 elif scope.host == url.host and url.path.startswith(scope.path): 365 return True 366 return False 367 368 369def PrepareConnection(service, full_uri): 370 """Opens a connection to the server based on the full URI. 371 372 This method is deprecated, instead use atom.http.HttpClient.request. 373 374 Examines the target URI and the proxy settings, which are set as 375 environment variables, to open a connection with the server. This 376 connection is used to make an HTTP request. 377 378 Args: 379 service: atom.AtomService or a subclass. It must have a server string which 380 represents the server host to which the request should be made. It may also 381 have a dictionary of additional_headers to send in the HTTP request. 382 full_uri: str Which is the target relative (lacks protocol and host) or 383 absolute URL to be opened. Example: 384 'https://www.google.com/accounts/ClientLogin' or 385 'base/feeds/snippets' where the server is set to www.google.com. 386 387 Returns: 388 A tuple containing the httplib.HTTPConnection and the full_uri for the 389 request. 390 """ 391 deprecation('calling deprecated function PrepareConnection') 392 (server, port, ssl, partial_uri) = ProcessUrl(service, full_uri) 393 if ssl: 394 # destination is https 395 proxy = os.environ.get('https_proxy') 396 if proxy: 397 (p_server, p_port, p_ssl, p_uri) = ProcessUrl(service, proxy, True) 398 proxy_username = os.environ.get('proxy-username') 399 if not proxy_username: 400 proxy_username = os.environ.get('proxy_username') 401 proxy_password = os.environ.get('proxy-password') 402 if not proxy_password: 403 proxy_password = os.environ.get('proxy_password') 404 if proxy_username: 405 user_auth = base64.encodestring('%s:%s' % (proxy_username, 406 proxy_password)) 407 proxy_authorization = ('Proxy-authorization: Basic %s\r\n' % ( 408 user_auth.strip())) 409 else: 410 proxy_authorization = '' 411 proxy_connect = 'CONNECT %s:%s HTTP/1.0\r\n' % (server, port) 412 user_agent = 'User-Agent: %s\r\n' % ( 413 service.additional_headers['User-Agent']) 414 proxy_pieces = (proxy_connect + proxy_authorization + user_agent 415 + '\r\n') 416 417 #now connect, very simple recv and error checking 418 p_sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM) 419 p_sock.connect((p_server,p_port)) 420 p_sock.sendall(proxy_pieces) 421 response = '' 422 423 # Wait for the full response. 424 while response.find("\r\n\r\n") == -1: 425 response += p_sock.recv(8192) 426 427 p_status=response.split()[1] 428 if p_status!=str(200): 429 raise 'Error status=',str(p_status) 430 431 # Trivial setup for ssl socket. 432 ssl = socket.ssl(p_sock, None, None) 433 fake_sock = httplib.FakeSocket(p_sock, ssl) 434 435 # Initalize httplib and replace with the proxy socket. 436 connection = httplib.HTTPConnection(server) 437 connection.sock=fake_sock 438 full_uri = partial_uri 439 440 else: 441 connection = httplib.HTTPSConnection(server, port) 442 full_uri = partial_uri 443 444 else: 445 # destination is http 446 proxy = os.environ.get('http_proxy') 447 if proxy: 448 (p_server, p_port, p_ssl, p_uri) = ProcessUrl(service.server, proxy, True) 449 proxy_username = os.environ.get('proxy-username') 450 if not proxy_username: 451 proxy_username = os.environ.get('proxy_username') 452 proxy_password = os.environ.get('proxy-password') 453 if not proxy_password: 454 proxy_password = os.environ.get('proxy_password') 455 if proxy_username: 456 UseBasicAuth(service, proxy_username, proxy_password, True) 457 connection = httplib.HTTPConnection(p_server, p_port) 458 if not full_uri.startswith("http://"): 459 if full_uri.startswith("/"): 460 full_uri = "http://%s%s" % (service.server, full_uri) 461 else: 462 full_uri = "http://%s/%s" % (service.server, full_uri) 463 else: 464 connection = httplib.HTTPConnection(server, port) 465 full_uri = partial_uri 466 467 return (connection, full_uri) 468 469 470def UseBasicAuth(service, username, password, for_proxy=False): 471 """Sets an Authenticaiton: Basic HTTP header containing plaintext. 472 473 Deprecated, use AtomService.use_basic_auth insread. 474 475 The username and password are base64 encoded and added to an HTTP header 476 which will be included in each request. Note that your username and 477 password are sent in plaintext. The auth header is added to the 478 additional_headers dictionary in the service object. 479 480 Args: 481 service: atom.AtomService or a subclass which has an 482 additional_headers dict as a member. 483 username: str 484 password: str 485 """ 486 deprecation('calling deprecated function UseBasicAuth') 487 base_64_string = base64.encodestring('%s:%s' % (username, password)) 488 base_64_string = base_64_string.strip() 489 if for_proxy: 490 header_name = 'Proxy-Authorization' 491 else: 492 header_name = 'Authorization' 493 service.additional_headers[header_name] = 'Basic %s' % (base_64_string,) 494 495 496def ProcessUrl(service, url, for_proxy=False): 497 """Processes a passed URL. If the URL does not begin with https?, then 498 the default value for server is used 499 500 This method is deprecated, use atom.url.parse_url instead. 501 """ 502 if not isinstance(url, atom.url.Url): 503 url = atom.url.parse_url(url) 504 505 server = url.host 506 ssl = False 507 port = 80 508 509 if not server: 510 if hasattr(service, 'server'): 511 server = service.server 512 else: 513 server = service 514 if not url.protocol and hasattr(service, 'ssl'): 515 ssl = service.ssl 516 if hasattr(service, 'port'): 517 port = service.port 518 else: 519 if url.protocol == 'https': 520 ssl = True 521 elif url.protocol == 'http': 522 ssl = False 523 if url.port: 524 port = int(url.port) 525 elif port == 80 and ssl: 526 port = 443 527 528 return (server, port, ssl, url.get_request_uri()) 529 530def DictionaryToParamList(url_parameters, escape_params=True): 531 """Convert a dictionary of URL arguments into a URL parameter string. 532 533 This function is deprcated, use atom.url.Url instead. 534 535 Args: 536 url_parameters: The dictionaty of key-value pairs which will be converted 537 into URL parameters. For example, 538 {'dry-run': 'true', 'foo': 'bar'} 539 will become ['dry-run=true', 'foo=bar']. 540 541 Returns: 542 A list which contains a string for each key-value pair. The strings are 543 ready to be incorporated into a URL by using '&'.join([] + parameter_list) 544 """ 545 # Choose which function to use when modifying the query and parameters. 546 # Use quote_plus when escape_params is true. 547 transform_op = [str, urllib.quote_plus][bool(escape_params)] 548 # Create a list of tuples containing the escaped version of the 549 # parameter-value pairs. 550 parameter_tuples = [(transform_op(param), transform_op(value)) 551 for param, value in (url_parameters or {}).items()] 552 # Turn parameter-value tuples into a list of strings in the form 553 # 'PARAMETER=VALUE'. 554 return ['='.join(x) for x in parameter_tuples] 555 556 557def BuildUri(uri, url_params=None, escape_params=True): 558 """Converts a uri string and a collection of parameters into a URI. 559 560 This function is deprcated, use atom.url.Url instead. 561 562 Args: 563 uri: string 564 url_params: dict (optional) 565 escape_params: boolean (optional) 566 uri: string The start of the desired URI. This string can alrady contain 567 URL parameters. Examples: '/base/feeds/snippets', 568 '/base/feeds/snippets?bq=digital+camera' 569 url_parameters: dict (optional) Additional URL parameters to be included 570 in the query. These are translated into query arguments 571 in the form '&dict_key=value&...'. 572 Example: {'max-results': '250'} becomes &max-results=250 573 escape_params: boolean (optional) If false, the calling code has already 574 ensured that the query will form a valid URL (all 575 reserved characters have been escaped). If true, this 576 method will escape the query and any URL parameters 577 provided. 578 579 Returns: 580 string The URI consisting of the escaped URL parameters appended to the 581 initial uri string. 582 """ 583 # Prepare URL parameters for inclusion into the GET request. 584 parameter_list = DictionaryToParamList(url_params, escape_params) 585 586 # Append the URL parameters to the URL. 587 if parameter_list: 588 if uri.find('?') != -1: 589 # If there are already URL parameters in the uri string, add the 590 # parameters after a new & character. 591 full_uri = '&'.join([uri] + parameter_list) 592 else: 593 # The uri string did not have any URL parameters (no ? character) 594 # so put a ? between the uri and URL parameters. 595 full_uri = '%s%s' % (uri, '?%s' % ('&'.join([] + parameter_list))) 596 else: 597 full_uri = uri 598 599 return full_uri 600 601 602def HttpRequest(service, operation, data, uri, extra_headers=None, 603 url_params=None, escape_params=True, content_type='application/atom+xml'): 604 """Performs an HTTP call to the server, supports GET, POST, PUT, and DELETE. 605 606 This method is deprecated, use atom.http.HttpClient.request instead. 607 608 Usage example, perform and HTTP GET on http://www.google.com/: 609 import atom.service 610 client = atom.service.AtomService() 611 http_response = client.Get('http://www.google.com/') 612 or you could set the client.server to 'www.google.com' and use the 613 following: 614 client.server = 'www.google.com' 615 http_response = client.Get('/') 616 617 Args: 618 service: atom.AtomService object which contains some of the parameters 619 needed to make the request. The following members are used to 620 construct the HTTP call: server (str), additional_headers (dict), 621 port (int), and ssl (bool). 622 operation: str The HTTP operation to be performed. This is usually one of 623 'GET', 'POST', 'PUT', or 'DELETE' 624 data: ElementTree, filestream, list of parts, or other object which can be 625 converted to a string. 626 Should be set to None when performing a GET or PUT. 627 If data is a file-like object which can be read, this method will read 628 a chunk of 100K bytes at a time and send them. 629 If the data is a list of parts to be sent, each part will be evaluated 630 and sent. 631 uri: The beginning of the URL to which the request should be sent. 632 Examples: '/', '/base/feeds/snippets', 633 '/m8/feeds/contacts/default/base' 634 extra_headers: dict of strings. HTTP headers which should be sent 635 in the request. These headers are in addition to those stored in 636 service.additional_headers. 637 url_params: dict of strings. Key value pairs to be added to the URL as 638 URL parameters. For example {'foo':'bar', 'test':'param'} will 639 become ?foo=bar&test=param. 640 escape_params: bool default True. If true, the keys and values in 641 url_params will be URL escaped when the form is constructed 642 (Special characters converted to %XX form.) 643 content_type: str The MIME type for the data being sent. Defaults to 644 'application/atom+xml', this is only used if data is set. 645 """ 646 deprecation('call to deprecated function HttpRequest') 647 full_uri = BuildUri(uri, url_params, escape_params) 648 (connection, full_uri) = PrepareConnection(service, full_uri) 649 650 if extra_headers is None: 651 extra_headers = {} 652 653 # Turn on debug mode if the debug member is set. 654 if service.debug: 655 connection.debuglevel = 1 656 657 connection.putrequest(operation, full_uri) 658 659 # If the list of headers does not include a Content-Length, attempt to 660 # calculate it based on the data object. 661 if (data and not service.additional_headers.has_key('Content-Length') and 662 not extra_headers.has_key('Content-Length')): 663 content_length = CalculateDataLength(data) 664 if content_length: 665 extra_headers['Content-Length'] = str(content_length) 666 667 if content_type: 668 extra_headers['Content-Type'] = content_type 669 670 # Send the HTTP headers. 671 if isinstance(service.additional_headers, dict): 672 for header in service.additional_headers: 673 connection.putheader(header, service.additional_headers[header]) 674 if isinstance(extra_headers, dict): 675 for header in extra_headers: 676 connection.putheader(header, extra_headers[header]) 677 connection.endheaders() 678 679 # If there is data, send it in the request. 680 if data: 681 if isinstance(data, list): 682 for data_part in data: 683 __SendDataPart(data_part, connection) 684 else: 685 __SendDataPart(data, connection) 686 687 # Return the HTTP Response from the server. 688 return connection.getresponse() 689 690 691def __SendDataPart(data, connection): 692 """This method is deprecated, use atom.http._send_data_part""" 693 deprecated('call to deprecated function __SendDataPart') 694 if isinstance(data, str): 695 #TODO add handling for unicode. 696 connection.send(data) 697 return 698 elif ElementTree.iselement(data): 699 connection.send(ElementTree.tostring(data)) 700 return 701 # Check to see if data is a file-like object that has a read method. 702 elif hasattr(data, 'read'): 703 # Read the file and send it a chunk at a time. 704 while 1: 705 binarydata = data.read(100000) 706 if binarydata == '': break 707 connection.send(binarydata) 708 return 709 else: 710 # The data object was not a file. 711 # Try to convert to a string and send the data. 712 connection.send(str(data)) 713 return 714 715 716def CalculateDataLength(data): 717 """Attempts to determine the length of the data to send. 718 719 This method will respond with a length only if the data is a string or 720 and ElementTree element. 721 722 Args: 723 data: object If this is not a string or ElementTree element this funtion 724 will return None. 725 """ 726 if isinstance(data, str): 727 return len(data) 728 elif isinstance(data, list): 729 return None 730 elif ElementTree.iselement(data): 731 return len(ElementTree.tostring(data)) 732 elif hasattr(data, 'read'): 733 # If this is a file-like object, don't try to guess the length. 734 return None 735 else: 736 return len(str(data)) 737 738 739def deprecation(message): 740 warnings.warn(message, DeprecationWarning, stacklevel=2)