Package intermine :: Module webservice
[hide private]
[frames] | no frames]

Source Code for Module intermine.webservice

  1  from urlparse import urlunsplit, urljoin 
  2  from xml.dom import minidom 
  3  import urllib 
  4  from urlparse import urlparse 
  5  import csv 
  6  import base64 
  7  import httplib 
  8  import re 
  9   
 10  # Use core json for 2.6+, simplejson for <=2.5 
 11  try: 
 12      import json 
 13  except ImportError: # pragma: no cover 
 14      import simplejson as json 
 15   
 16  # Local intermine imports 
 17  from intermine.query import Query, Template 
 18  from intermine.model import Model, Attribute, Reference, Collection 
 19  from intermine.util import ReadableException 
 20  from intermine.lists.listmanager import ListManager 
 21   
 22  """ 
 23  Webservice Interaction Routines for InterMine Webservices 
 24  ========================================================= 
 25   
 26  Classes for dealing with communication with an InterMine 
 27  RESTful webservice. 
 28   
 29  """ 
 30   
 31  __author__ = "Alex Kalderimis" 
 32  __organization__ = "InterMine" 
 33  __license__ = "LGPL" 
 34  __contact__ = "dev@intermine.org" 
35 36 -class Service(object):
37 """ 38 A class representing connections to different InterMine WebServices 39 =================================================================== 40 41 The intermine.webservice.Service class is the main interface for the user. 42 It will provide access to queries and templates, as well as doing the 43 background task of fetching the data model, and actually requesting 44 the query results. 45 46 SYNOPSIS 47 -------- 48 49 example:: 50 51 from intermine.webservice import Service 52 service = Service("http://www.flymine.org/query/service") 53 54 template = service.get_template("Gene_Pathways") 55 for row in template.results(A={"value":"zen"}): 56 do_something_with(row) 57 ... 58 59 query = service.new_query() 60 query.add_view("Gene.symbol", "Gene.pathway.name") 61 query.add_constraint("Gene", "LOOKUP", "zen") 62 for row in query.results(): 63 do_something_with(row) 64 ... 65 66 new_list = service.create_list("some/file/with.ids", "Gene") 67 list_on_server = service.get_list("On server") 68 in_both = new_list & list_on_server 69 in_both.name = "Intersection of these lists" 70 for row in in_both.to_attribute_query().results(): 71 do_something_with(row) 72 ... 73 74 OVERVIEW 75 -------- 76 The two methods the user will be most concerned with are: 77 - L{Service.new_query}: constructs a new query to query a service with 78 - L{Service.get_template}: gets a template from the service 79 - L{ListManager.create_list}: creates a new list on the service 80 81 For list management information, see L{ListManager}. 82 83 TERMINOLOGY 84 ----------- 85 X{Query} is the term for an arbitrarily complex structured request for 86 data from the webservice. The user is responsible for specifying the 87 structure that determines what records are returned, and what information 88 about each record is provided. 89 90 X{Template} is the term for a predefined "Query", ie: one that has been 91 written and saved on the webservice you will access. The definition 92 of the query is already done, but the user may want to specify the 93 values of the constraints that exist on the template. Templates are accessed 94 by name, and while you can easily introspect templates, it is assumed 95 you know what they do when you use them 96 97 X{List} is a saved result set containing a set of objects previously identified 98 in the database. Lists can be created and managed using this client library. 99 100 @see: L{intermine.query} 101 """ 102 USER_AGENT = 'WebserviceInterMinePerlAPIClient' 103 QUERY_PATH = '/query/results' 104 QUERY_LIST_UPLOAD_PATH = '/query/tolist/json' 105 QUERY_LIST_APPEND_PATH = '/query/append/tolist/json' 106 MODEL_PATH = '/model' 107 TEMPLATES_PATH = '/templates/xml' 108 TEMPLATEQUERY_PATH = '/template/results' 109 LIST_PATH = '/lists/json' 110 LIST_CREATION_PATH = '/lists/json' 111 LIST_RENAME_PATH = '/lists/rename/json' 112 LIST_APPENDING_PATH = '/lists/append/json' 113 SAVEDQUERY_PATH = '/savedqueries/xml' 114 VERSION_PATH = '/version/ws' 115 RELEASE_PATH = '/version/release' 116 SCHEME = 'http://' 117
118 - def __init__(self, root, username=None, password=None, token=None):
119 """ 120 Constructor 121 =========== 122 123 Construct a connection to a webservice:: 124 125 url = "http://www.flymine.org/query/service" 126 127 # An unauthenticated connection - access to all public data 128 service = Service(url) 129 130 # An authenticated connection - access to private and public data 131 service = Service(url, token="ABC123456") 132 133 134 @param root: the root url of the webservice (required) 135 @param username: your login name (optional) 136 @param password: your password (required if a username is given) 137 @param token: your API access token(optional - used in preference to username and password) 138 139 @raise ServiceError: if the version cannot be fetched and parsed 140 @raise ValueError: if a username is supplied, but no password 141 142 There are two alternative authentication systems supported by InterMine 143 webservices. The first is username and password authentication, which 144 is supported by all webservices. Newer webservices (version 6+) 145 also support API access token authentication, which is the recommended 146 system to use. Token access is more secure as you will never have 147 to transmit your username or password, and the token can be easily changed 148 or disabled without changing your webapp login details. 149 150 """ 151 o = urlparse(root) 152 if not o.scheme: root = "http://" + root 153 if not root.endswith("/service"): root = root + "/service" 154 155 self.root = root 156 self._templates = None 157 self._model = None 158 self._version = None 159 self._release = None 160 self._list_manager = ListManager(self) 161 self.__missing_method_name = None 162 if token: 163 self.opener = InterMineURLOpener(token=token) 164 elif username: 165 if token: 166 raise ValueError("Both username and token credentials supplied") 167 168 if not password: 169 raise ValueError("Username given, but no password supplied") 170 171 self.opener = InterMineURLOpener((username, password)) 172 else: 173 self.opener = InterMineURLOpener() 174 175 try: 176 self.version 177 except WebserviceError, e: 178 raise ServiceError("Could not validate service - is the root url (%s) correct? %s" % (root, e)) 179 180 if token and self.version < 6: 181 raise ServiceError("This service does not support API access token authentication") 182 183 # Set up sugary aliases 184 self.query = self.new_query
185 186 187 # Delegated list methods 188 189 LIST_MANAGER_METHODS = frozenset(["get_list", "get_all_lists", "get_all_list_names", 190 "create_list", "get_list_count", "delete_lists", "l"]) 191
192 - def __getattribute__(self, name):
193 return object.__getattribute__(self, name)
194
195 - def __getattr__(self, name):
196 if name in self.LIST_MANAGER_METHODS: 197 method = getattr(self._list_manager, name) 198 return method 199 raise AttributeError("Could not find " + name)
200
201 - def __del__(self):
202 try: 203 self._list_manager.delete_temporary_lists() 204 except ReferenceError: 205 pass
206 207 @property
208 - def version(self):
209 """ 210 Returns the webservice version 211 ============================== 212 213 The version specifies what capabilities a 214 specific webservice provides. The most current 215 version is 3 216 217 may raise ServiceError: if the version cannot be fetched 218 219 @rtype: int 220 """ 221 if self._version is None: 222 try: 223 url = self.root + self.VERSION_PATH 224 self._version = int(self.opener.open(url).read()) 225 except ValueError, e: 226 raise ServiceError("Could not parse a valid webservice version: " + str(e)) 227 return self._version
228 @property
229 - def release(self):
230 """ 231 Returns the datawarehouse release 232 ================================= 233 234 Service.release S{->} string 235 236 The release is an arbitrary string used to distinguish 237 releases of the datawarehouse. This usually coincides 238 with updates to the data contained within. While a string, 239 releases usually sort in ascending order of recentness 240 (eg: "release-26", "release-27", "release-28"). They can also 241 have less machine readable meanings (eg: "beta") 242 243 @rtype: string 244 """ 245 if self._release is None: 246 self._release = urllib.urlopen(self.root + self.RELEASE_PATH).read() 247 return self._release
248
249 - def new_query(self, root=None):
250 """ 251 Construct a new Query object for the given webservice 252 ===================================================== 253 254 This is the standard method for instantiating new Query 255 objects. Queries require access to the data model, as well 256 as the service itself, so it is easiest to access them through 257 this factory method. 258 259 @return: L{intermine.query.Query} 260 """ 261 return Query(self.model, self, root=root)
262
263 - def get_template(self, name):
264 """ 265 Returns a template of the given name 266 ==================================== 267 268 Tries to retrieve a template of the given name 269 from the webservice. If you are trying to fetch 270 a private template (ie. one you made yourself 271 and is not available to others) then you may need to authenticate 272 273 @see: L{intermine.webservice.Service.__init__} 274 275 @param name: the template's name 276 @type name: string 277 278 @raise ServiceError: if the template does not exist 279 @raise QueryParseError: if the template cannot be parsed 280 281 @return: L{intermine.query.Template} 282 """ 283 try: 284 t = self.templates[name] 285 except KeyError: 286 raise ServiceError("There is no template called '" 287 + name + "' at this service") 288 if not isinstance(t, Template): 289 t = Template.from_xml(t, self.model, self) 290 self.templates[name] = t 291 return t
292 293 @property
294 - def templates(self):
295 """ 296 The dictionary of templates from the webservice 297 =============================================== 298 299 Service.templates S{->} dict(intermine.query.Template|string) 300 301 For efficiency's sake, Templates are not parsed until 302 they are required, and until then they are stored as XML 303 strings. It is recommended that in most cases you would want 304 to use L{Service.get_template}. 305 306 You can use this property however to test for template existence though:: 307 308 if name in service.templates: 309 template = service.get_template(name) 310 311 @rtype: dict 312 313 """ 314 if self._templates is None: 315 sock = self.opener.open(self.root + self.TEMPLATES_PATH) 316 dom = minidom.parse(sock) 317 sock.close() 318 templates = {} 319 for e in dom.getElementsByTagName('template'): 320 name = e.getAttribute('name') 321 if name in templates: 322 raise ServiceError("Two templates with same name: " + name) 323 else: 324 templates[name] = e.toxml() 325 self._templates = templates 326 return self._templates
327 328 @property
329 - def model(self):
330 """ 331 The data model for the webservice you are querying 332 ================================================== 333 334 Service.model S{->} L{intermine.model.Model} 335 336 This is used when constructing queries to provide them 337 with information on the structure of the data model 338 they are accessing. You are very unlikely to want to 339 access this object directly. 340 341 raises ModelParseError: if the model cannot be read 342 343 @rtype: L{intermine.model.Model} 344 345 """ 346 if self._model is None: 347 model_url = self.root + self.MODEL_PATH 348 self._model = Model(model_url, self) 349 return self._model
350
351 - def get_results(self, path, params, rowformat, view, cld=None):
352 """ 353 Return an Iterator over the rows of the results 354 =============================================== 355 356 This method is called internally by the query objects 357 when they are called to get results. You will not 358 normally need to call it directly 359 360 @param path: The resource path (eg: "/query/results") 361 @type path: string 362 @param params: The query parameters for this request as a dictionary 363 @type params: dict 364 @param rowformat: One of "rr", "dict", "list", "tsv", "csv", "jsonrows", "jsonobjects" 365 @type rowformat: string 366 @param view: The output columns 367 @type view: list 368 369 @raise WebserviceError: for failed requests 370 371 @return: L{intermine.webservice.ResultIterator} 372 """ 373 return ResultIterator(self.root, path, params, rowformat, view, self.opener, cld)
374
375 -class ResultObject(object):
376 """ 377 An object used to represent result records as returned in jsonobjects format 378 ============================================================================ 379 380 These objects are backed by a row of data and the class descriptor that 381 describes the object. They allow access in standard object style: 382 383 for gene in query.results(): 384 print gene.symbol 385 print map(lambda x: x.name, gene.pathways) 386 387 """ 388
389 - def __init__(self, data, cld):
390 self._data = data 391 self._cld = cld 392 self._attr_cache = {}
393
394 - def __getattr__(self, name):
395 if name in self._attr_cache: 396 return self._attr_cache[name] 397 398 fld = self._cld.get_field(name) 399 attr = None 400 if isinstance(fld, Attribute): 401 if name in self._data: 402 attr = self._data[name] 403 elif isinstance(fld, Collection): 404 if name in self._data: 405 attr = map(lambda x: ResultObject(x, fld.type_class), self._data[name]) 406 else: 407 attr = [] 408 elif isinstance(fld, Reference): 409 if name in self._data: 410 attr = ResultObject(self._data[name], fld.type_class) 411 else: 412 raise WebserviceError("Inconsistent model - This should never happen") 413 self._attr_cache[name] = attr 414 return attr
415
416 417 -class ResultRow(object):
418 """ 419 An object for representing a row of data received back from the server. 420 ======================================================================= 421 422 ResultRows provide access to the fields of the row through index lookup. However, 423 for convenience both list indexes and dictionary keys can be used. So the 424 following all work: 425 426 # view is "Gene.symbol", "Gene.organism.name" 427 row["symbol"] 428 row["Gene.symbol"] 429 row[0] 430 row[:1] 431 432 """ 433
434 - def __init__(self, data, views):
435 self.data = data 436 self.views = views 437 self.index_map = None
438
439 - def __len__(self):
440 """Return the number of cells in this row""" 441 return len(self.data)
442
443 - def __iter__(self):
444 """Return the list view of the row, so each cell can be processed""" 445 return self.to_l()
446
447 - def __getitem__(self, key):
448 if isinstance(key, int): 449 return self.data[key]["value"] 450 elif isinstance(key, slice): 451 vals = map(lambda x: x["value"], self.data[key]) 452 return vals 453 else: 454 index = self._get_index_for(key) 455 return self.data[index]["value"]
456
457 - def _get_index_for(self, key):
458 if self.index_map is None: 459 self.index_map = {} 460 for i in range(len(self.views)): 461 view = self.views[i] 462 headless_view = re.sub("^[^.]+.", "", view) 463 self.index_map[view] = i 464 self.index_map[headless_view] = i 465 466 return self.index_map[key]
467
468 - def __str__(self):
469 root = re.sub("\..*$", "", self.views[0]) 470 parts = [root + ":"] 471 for view in self.views: 472 short_form = re.sub("^[^.]+.", "", view) 473 value = self[view] 474 parts.append(short_form + "=" + str(value)) 475 return " ".join(parts)
476
477 - def to_l(self):
478 """Return a list view of this row""" 479 return map(lambda x: x["value"], self.data)
480
481 - def to_d(self):
482 """Return a dictionary view of this row""" 483 d = {} 484 for view in self.views: 485 d[view] = self[view] 486 487 return d
488
489 -class ResultIterator(object):
490 491 PARSED_FORMATS = frozenset(["rr", "list", "dict"]) 492 STRING_FORMATS = frozenset(["tsv", "csv", "count"]) 493 JSON_FORMATS = frozenset(["jsonrows", "jsonobjects"]) 494 ROW_FORMATS = PARSED_FORMATS | STRING_FORMATS | JSON_FORMATS 495
496 - def __init__(self, root, path, params, rowformat, view, opener, cld=None):
497 """ 498 Constructor 499 =========== 500 501 Services are responsible for getting result iterators. You will 502 not need to create one manually. 503 504 @param root: The root path (eg: "http://www.flymine.org/query/service") 505 @type root: string 506 @param path: The resource path (eg: "/query/results") 507 @type path: string 508 @param params: The query parameters for this request 509 @type params: dict 510 @param rowformat: One of "dict", "list", "tsv", "csv", "jsonrows", "jsonobjects" 511 @type rowformat: string 512 @param view: The output columns 513 @type view: list 514 @param opener: A url opener (user-agent) 515 @type opener: urllib.URLopener 516 517 @raise ValueError: if the row format is incorrect 518 @raise WebserviceError: if the request is unsuccessful 519 """ 520 if rowformat not in self.ROW_FORMATS: 521 raise ValueError("'" + rowformat + "' is not a valid row format:" + self.ROW_FORMATS) 522 523 if rowformat in self.PARSED_FORMATS: 524 params.update({"format" : "jsonrows"}) 525 else: 526 params.update({"format" : rowformat}) 527 528 url = root + path 529 data = urllib.urlencode(params) 530 con = opener.open(url, data) 531 self.reader = { 532 "tsv" : lambda: FlatFileIterator(con, EchoParser()), 533 "csv" : lambda: FlatFileIterator(con, EchoParser()), 534 "count" : lambda: FlatFileIterator(con, EchoParser()), 535 "list" : lambda: JSONIterator(con, ListValueParser()), 536 "rr" : lambda: JSONIterator(con, ResultRowParser(view)), 537 "dict" : lambda: JSONIterator(con, DictValueParser(view)), 538 "jsonobjects" : lambda: JSONIterator(con, ResultObjParser(cld)), 539 "jsonrows" : lambda: JSONIterator(con, EchoParser()) 540 }.get(rowformat)()
541
542 - def __iter__(self):
543 return self.reader
544
545 - def next(self):
546 """ 547 Returns the next row, in the appropriate format 548 549 @rtype: whatever the rowformat was determined to be 550 """ 551 return self.reader.next()
552
553 -class FlatFileIterator(object):
554 """ 555 An iterator for handling results returned as a flat file (TSV/CSV). 556 =================================================================== 557 558 This iterator can be used as the sub iterator in a ResultIterator 559 """ 560
561 - def __init__(self, connection, parser):
562 """ 563 Constructor 564 =========== 565 566 @param connection: The source of data 567 @type connection: socket.socket 568 @param parser: a handler for each row of data 569 @type parser: Parser 570 """ 571 self.connection = connection 572 self.parser = parser
573
574 - def __iter__(self):
575 return self
576
577 - def next(self):
578 """Return a parsed line of data""" 579 line = self.connection.next().strip() 580 if line.startswith("[ERROR]"): 581 raise WebserviceError(line) 582 return self.parser.parse(line)
583
584 -class JSONIterator(object):
585 """ 586 An iterator for handling results returned in the JSONRows format 587 ================================================================ 588 589 This iterator can be used as the sub iterator in a ResultIterator 590 """ 591
592 - def __init__(self, connection, parser):
593 """ 594 Constructor 595 =========== 596 597 @param connection: The source of data 598 @type connection: socket.socket 599 @param parser: a handler for each row of data 600 @type parser: Parser 601 """ 602 self.connection = connection 603 self.parser = parser 604 self.header = "" 605 self.footer = "" 606 self.parse_header()
607
608 - def __iter__(self):
609 return self
610
611 - def next(self):
612 """Returns a parsed row of data""" 613 return self.get_next_row_from_connection()
614
615 - def parse_header(self):
616 """Reads out the header information from the connection""" 617 try: 618 line = self.connection.next().strip() 619 self.header += line 620 if not line.endswith('"results":['): 621 self.parse_header() 622 except StopIteration: 623 raise WebserviceError("The connection returned a bad header" + self.header)
624
625 - def check_return_status(self):
626 """ 627 Perform status checks 628 ===================== 629 630 The footer containts information as to whether the result 631 set was successfully transferred in its entirety. This 632 method makes sure we don't silently accept an 633 incomplete result set. 634 635 @raise WebserviceError: if the footer indicates there was an error 636 """ 637 container = self.header + self.footer 638 info = None 639 try: 640 info = json.loads(container) 641 except: 642 raise WebserviceError("Error parsing JSON container: " + container) 643 644 if not info["wasSuccessful"]: 645 raise WebserviceError(info["statusCode"], info["error"])
646
648 """ 649 Reads the connection to get the next row, and sends it to the parser 650 651 @raise WebserviceError: if the connection is interrupted 652 """ 653 next_row = None 654 try: 655 line = self.connection.next() 656 if line.startswith("]"): 657 self.footer += line; 658 for otherline in self.connection: 659 self.footer += line 660 self.check_return_status() 661 else: 662 line = line.strip().strip(',') 663 if len(line)> 0: 664 try: 665 row = json.loads(line) 666 except json.decoder.JSONDecodeError, e: 667 raise WebserviceError("Error parsing line from results: '" + line + "' - " + str(e)) 668 next_row = self.parser.parse(row) 669 except StopIteration: 670 raise WebserviceError("Connection interrupted") 671 672 if next_row is None: 673 raise StopIteration 674 else: 675 return next_row
676
677 -class Parser(object):
678 """ 679 Base class for result line parsers 680 ================================== 681 682 Sub-class this class to gain a default constructor 683 684 """ 685
686 - def __init__(self, view=[]):
687 """ 688 Constructor 689 =========== 690 691 @param view: the list of output columns (default: []) 692 @type view: list 693 """ 694 self.view = view
695
696 - def parse(self, data):
697 """ 698 Abstract method - implementations must provide behaviour 699 700 @param data: a line of data 701 """ 702 raise UnimplementedError
703
704 -class EchoParser(Parser):
705 """ 706 A result parser that echoes its input 707 ===================================== 708 709 Use for parsing situations when you don't 710 actually want to change the data 711 """ 712
713 - def parse(self, data):
714 """ 715 Most basic parser - just returns the fed in data structure 716 717 @param data: the data from the result set 718 """ 719 return data
720
721 -class ListValueParser(Parser):
722 """ 723 A result parser that produces lists 724 =================================== 725 726 Parses jsonrow formatted rows into lists 727 of values. 728 """ 729 730
731 - def parse(self, row):
732 """ 733 Parse a row of JSON results into a list 734 735 @param row: a row of data from a result set 736 @type row: a JSON string 737 738 @rtype: list 739 """ 740 return [cell.get("value") for cell in row]
741
742 -class DictValueParser(Parser):
743 """ 744 A result parser that produces dictionaries 745 ========================================== 746 747 Parses jsonrow formatted rows into dictionaries 748 where the key is the view string for the cell, 749 and the value is the contents of the returned cell. 750 """ 751
752 - def parse(self, row):
753 """ 754 Parse a row of JSON results into a dictionary 755 756 @param row: a row of data from a result set 757 @type row: a JSON string 758 759 @rtype: dict 760 """ 761 pairs = zip(self.view, row) 762 return_dict = {} 763 for view, cell in pairs: 764 return_dict[view] = cell.get("value") 765 return return_dict
766
767 -class ResultRowParser(Parser):
768 """ 769 A result parser that produces ResultRow objects, which support both index and key access 770 ======================================================================================== 771 772 Parses jsonrow formatted rows into ResultRows, 773 which supports key access by list indices (based on the 774 selected view) as well as lookup by view name (based 775 on the selected view value). 776 """ 777
778 - def parse(self, row):
779 """ 780 Parse a row of JSON results into a ResultRow 781 782 @param row: a row of data from a result set 783 @type row: a JSON string 784 785 @rtype: ResultRow 786 """ 787 rr = ResultRow(row, self.view) 788 return rr
789
790 -class ResultObjParser(Parser):
791 """ 792 A result parser that produces ResultRow objects, which support both index and key access 793 ======================================================================================== 794 795 Parses jsonrow formatted rows into ResultRows, 796 which supports key access by list indices (based on the 797 selected view) as well as lookup by view name (based 798 on the selected view value). 799 """ 800
801 - def __init__(self, cld):
802 """ 803 Constructor 804 =========== 805 806 @param cld: the class of object this result object represents 807 @type cld: intermine.model.Class 808 """ 809 self.cld = cld
810
811 - def parse(self, row):
812 """ 813 Parse a row of JSON results into a ResultRow 814 815 @param row: a row of data from a result set 816 @type row: a JSON string 817 818 @rtype: ResultObject 819 """ 820 ro = ResultObject(row, self.cld) 821 return ro
822
823 -class InterMineURLOpener(urllib.FancyURLopener):
824 """ 825 Specific implementation of urllib.FancyURLOpener for this client 826 ================================================================ 827 828 Provides user agent and authentication headers, and handling of errors 829 """ 830 version = "InterMine-Python-Client-0.96.00" 831
832 - def __init__(self, credentials=None, token=None):
833 """ 834 Constructor 835 =========== 836 837 InterMineURLOpener((username, password)) S{->} InterMineURLOpener 838 839 Return a new url-opener with the appropriate credentials 840 """ 841 urllib.FancyURLopener.__init__(self) 842 self.token = token 843 self.plain_post_header = { 844 "Content-Type": "text/plain; charset=utf-8", 845 "UserAgent": Service.USER_AGENT 846 } 847 if credentials and len(credentials) == 2: 848 base64string = base64.encodestring('%s:%s' % credentials)[:-1] 849 self.addheader("Authorization", base64string) 850 self.plain_post_header["Authorization"] = base64string 851 self.using_authentication = True 852 else: 853 self.using_authentication = False
854
855 - def post_plain_text(self, url, body):
856 url = self.prepare_url(url) 857 o = urlparse(url) 858 con = httplib.HTTPConnection(o.hostname, o.port) 859 con.request('POST', url, body, self.plain_post_header) 860 resp = con.getresponse() 861 content = resp.read() 862 con.close() 863 if resp.status != 200: 864 raise WebserviceError(resp.status, resp.reason, content) 865 return content
866
867 - def open(self, url, data=None):
868 url = self.prepare_url(url) 869 return urllib.FancyURLopener.open(self, url, data)
870
871 - def prepare_url(self, url):
872 if self.token: 873 token_param = "token=" + self.token 874 o = urlparse(url) 875 if o.query: 876 url += "&" + token_param 877 else: 878 url += "?" + token_param 879 880 return url
881
882 - def delete(self, url):
883 url = self.prepare_url(url) 884 o = urlparse(url) 885 con = httplib.HTTPConnection(o.hostname, o.port) 886 con.request('DELETE', url, None, self.plain_post_header) 887 resp = con.getresponse() 888 content = resp.read() 889 con.close() 890 if resp.status != 200: 891 raise WebserviceError(resp.status, resp.reason, content) 892 return content
893
894 - def http_error_default(self, url, fp, errcode, errmsg, headers):
895 """Re-implementation of http_error_default, with content now supplied by default""" 896 content = fp.read() 897 fp.close() 898 raise WebserviceError(errcode, errmsg, content)
899
900 - def http_error_400(self, url, fp, errcode, errmsg, headers, data=None):
901 """ 902 Handle 400 HTTP errors, attempting to return informative error messages 903 ======================================================================= 904 905 400 errors indicate that something about our request was incorrect 906 907 @raise WebserviceError: in all circumstances 908 909 """ 910 content = fp.read() 911 fp.close() 912 raise WebserviceError("There was a problem with our request", errcode, errmsg, content)
913
914 - def http_error_401(self, url, fp, errcode, errmsg, headers, data=None):
915 """ 916 Handle 401 HTTP errors, attempting to return informative error messages 917 ======================================================================= 918 919 401 errors indicate we don't have sufficient permission for the resource 920 we requested - usually a list or a tempate 921 922 @raise WebserviceError: in all circumstances 923 924 """ 925 content = fp.read() 926 fp.close() 927 if self.using_authentication: 928 raise WebserviceError("Insufficient permissions", errcode, errmsg, content) 929 else: 930 raise WebserviceError("No permissions - not logged in", errcode, errmsg, content)
931
932 - def http_error_404(self, url, fp, errcode, errmsg, headers, data=None):
933 """ 934 Handle 404 HTTP errors, attempting to return informative error messages 935 ======================================================================= 936 937 404 errors indicate that the requested resource does not exist - usually 938 a template that is not longer available. 939 940 @raise WebserviceError: in all circumstances 941 942 """ 943 content = fp.read() 944 fp.close() 945 raise WebserviceError("Missing resource", errcode, errmsg, content)
946 - def http_error_500(self, url, fp, errcode, errmsg, headers, data=None):
947 """ 948 Handle 500 HTTP errors, attempting to return informative error messages 949 ======================================================================= 950 951 500 errors indicate that the server borked during the request - ie: it wasn't 952 our fault. 953 954 @raise WebserviceError: in all circumstances 955 956 """ 957 content = fp.read() 958 fp.close() 959 raise WebserviceError("Internal server error", errcode, errmsg, content)
960
961 -class UnimplementedError(Exception):
962 pass
963
964 -class ServiceError(ReadableException):
965 """Errors in the creation and use of the Service object""" 966 pass
967 -class WebserviceError(IOError):
968 """Errors from interaction with the webservice""" 969 pass
970