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 "rr", "object", "count", "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.startswith("object"): # Accept "object", "objects", "objectformat", etc... 521 rowformat = "jsonobjects" # these are synonymous 522 if rowformat not in self.ROW_FORMATS: 523 raise ValueError("'%s' is not one of the valid row formats (%s)" % (rowformat, repr(list(self.ROW_FORMATS)))) 524 525 if rowformat in self.PARSED_FORMATS: 526 params.update({"format" : "jsonrows"}) 527 else: 528 params.update({"format" : rowformat}) 529 530 url = root + path 531 data = urllib.urlencode(params) 532 con = opener.open(url, data) 533 self.reader = { 534 "tsv" : lambda: FlatFileIterator(con, EchoParser()), 535 "csv" : lambda: FlatFileIterator(con, EchoParser()), 536 "count" : lambda: FlatFileIterator(con, EchoParser()), 537 "list" : lambda: JSONIterator(con, ListValueParser()), 538 "rr" : lambda: JSONIterator(con, ResultRowParser(view)), 539 "dict" : lambda: JSONIterator(con, DictValueParser(view)), 540 "jsonobjects" : lambda: JSONIterator(con, ResultObjParser(cld)), 541 "jsonrows" : lambda: JSONIterator(con, EchoParser()) 542 }.get(rowformat)()
543
544 - def __iter__(self):
545 return self.reader
546
547 - def next(self):
548 """ 549 Returns the next row, in the appropriate format 550 551 @rtype: whatever the rowformat was determined to be 552 """ 553 return self.reader.next()
554
555 -class FlatFileIterator(object):
556 """ 557 An iterator for handling results returned as a flat file (TSV/CSV). 558 =================================================================== 559 560 This iterator can be used as the sub iterator in a ResultIterator 561 """ 562
563 - def __init__(self, connection, parser):
564 """ 565 Constructor 566 =========== 567 568 @param connection: The source of data 569 @type connection: socket.socket 570 @param parser: a handler for each row of data 571 @type parser: Parser 572 """ 573 self.connection = connection 574 self.parser = parser
575
576 - def __iter__(self):
577 return self
578
579 - def next(self):
580 """Return a parsed line of data""" 581 line = self.connection.next().strip() 582 if line.startswith("[ERROR]"): 583 raise WebserviceError(line) 584 return self.parser.parse(line)
585
586 -class JSONIterator(object):
587 """ 588 An iterator for handling results returned in the JSONRows format 589 ================================================================ 590 591 This iterator can be used as the sub iterator in a ResultIterator 592 """ 593
594 - def __init__(self, connection, parser):
595 """ 596 Constructor 597 =========== 598 599 @param connection: The source of data 600 @type connection: socket.socket 601 @param parser: a handler for each row of data 602 @type parser: Parser 603 """ 604 self.connection = connection 605 self.parser = parser 606 self.header = "" 607 self.footer = "" 608 self.parse_header()
609
610 - def __iter__(self):
611 return self
612
613 - def next(self):
614 """Returns a parsed row of data""" 615 return self.get_next_row_from_connection()
616
617 - def parse_header(self):
618 """Reads out the header information from the connection""" 619 try: 620 line = self.connection.next().strip() 621 self.header += line 622 if not line.endswith('"results":['): 623 self.parse_header() 624 except StopIteration: 625 raise WebserviceError("The connection returned a bad header" + self.header)
626
627 - def check_return_status(self):
628 """ 629 Perform status checks 630 ===================== 631 632 The footer containts information as to whether the result 633 set was successfully transferred in its entirety. This 634 method makes sure we don't silently accept an 635 incomplete result set. 636 637 @raise WebserviceError: if the footer indicates there was an error 638 """ 639 container = self.header + self.footer 640 info = None 641 try: 642 info = json.loads(container) 643 except: 644 raise WebserviceError("Error parsing JSON container: " + container) 645 646 if not info["wasSuccessful"]: 647 raise WebserviceError(info["statusCode"], info["error"])
648
650 """ 651 Reads the connection to get the next row, and sends it to the parser 652 653 @raise WebserviceError: if the connection is interrupted 654 """ 655 next_row = None 656 try: 657 line = self.connection.next() 658 if line.startswith("]"): 659 self.footer += line; 660 for otherline in self.connection: 661 self.footer += line 662 self.check_return_status() 663 else: 664 line = line.strip().strip(',') 665 if len(line)> 0: 666 try: 667 row = json.loads(line) 668 except json.decoder.JSONDecodeError, e: 669 raise WebserviceError("Error parsing line from results: '" + line + "' - " + str(e)) 670 next_row = self.parser.parse(row) 671 except StopIteration: 672 raise WebserviceError("Connection interrupted") 673 674 if next_row is None: 675 raise StopIteration 676 else: 677 return next_row
678
679 -class Parser(object):
680 """ 681 Base class for result line parsers 682 ================================== 683 684 Sub-class this class to gain a default constructor 685 686 """ 687
688 - def __init__(self, view=[]):
689 """ 690 Constructor 691 =========== 692 693 @param view: the list of output columns (default: []) 694 @type view: list 695 """ 696 self.view = view
697
698 - def parse(self, data):
699 """ 700 Abstract method - implementations must provide behaviour 701 702 @param data: a line of data 703 """ 704 raise UnimplementedError
705
706 -class EchoParser(Parser):
707 """ 708 A result parser that echoes its input 709 ===================================== 710 711 Use for parsing situations when you don't 712 actually want to change the data 713 """ 714
715 - def parse(self, data):
716 """ 717 Most basic parser - just returns the fed in data structure 718 719 @param data: the data from the result set 720 """ 721 return data
722
723 -class ListValueParser(Parser):
724 """ 725 A result parser that produces lists 726 =================================== 727 728 Parses jsonrow formatted rows into lists 729 of values. 730 """ 731 732
733 - def parse(self, row):
734 """ 735 Parse a row of JSON results into a list 736 737 @param row: a row of data from a result set 738 @type row: a JSON string 739 740 @rtype: list 741 """ 742 return [cell.get("value") for cell in row]
743
744 -class DictValueParser(Parser):
745 """ 746 A result parser that produces dictionaries 747 ========================================== 748 749 Parses jsonrow formatted rows into dictionaries 750 where the key is the view string for the cell, 751 and the value is the contents of the returned cell. 752 """ 753
754 - def parse(self, row):
755 """ 756 Parse a row of JSON results into a dictionary 757 758 @param row: a row of data from a result set 759 @type row: a JSON string 760 761 @rtype: dict 762 """ 763 pairs = zip(self.view, row) 764 return_dict = {} 765 for view, cell in pairs: 766 return_dict[view] = cell.get("value") 767 return return_dict
768
769 -class ResultRowParser(Parser):
770 """ 771 A result parser that produces ResultRow objects, which support both index and key access 772 ======================================================================================== 773 774 Parses jsonrow formatted rows into ResultRows, 775 which supports key access by list indices (based on the 776 selected view) as well as lookup by view name (based 777 on the selected view value). 778 """ 779
780 - def parse(self, row):
781 """ 782 Parse a row of JSON results into a ResultRow 783 784 @param row: a row of data from a result set 785 @type row: a JSON string 786 787 @rtype: ResultRow 788 """ 789 rr = ResultRow(row, self.view) 790 return rr
791
792 -class ResultObjParser(Parser):
793 """ 794 A result parser that produces ResultRow objects, which support both index and key access 795 ======================================================================================== 796 797 Parses jsonrow formatted rows into ResultRows, 798 which supports key access by list indices (based on the 799 selected view) as well as lookup by view name (based 800 on the selected view value). 801 """ 802
803 - def __init__(self, cld):
804 """ 805 Constructor 806 =========== 807 808 @param cld: the class of object this result object represents 809 @type cld: intermine.model.Class 810 """ 811 self.cld = cld
812
813 - def parse(self, row):
814 """ 815 Parse a row of JSON results into a ResultRow 816 817 @param row: a row of data from a result set 818 @type row: a JSON string 819 820 @rtype: ResultObject 821 """ 822 ro = ResultObject(row, self.cld) 823 return ro
824
825 -class InterMineURLOpener(urllib.FancyURLopener):
826 """ 827 Specific implementation of urllib.FancyURLOpener for this client 828 ================================================================ 829 830 Provides user agent and authentication headers, and handling of errors 831 """ 832 version = "InterMine-Python-Client-0.96.00" 833
834 - def __init__(self, credentials=None, token=None):
835 """ 836 Constructor 837 =========== 838 839 InterMineURLOpener((username, password)) S{->} InterMineURLOpener 840 841 Return a new url-opener with the appropriate credentials 842 """ 843 urllib.FancyURLopener.__init__(self) 844 self.token = token 845 self.plain_post_header = { 846 "Content-Type": "text/plain; charset=utf-8", 847 "UserAgent": Service.USER_AGENT 848 } 849 if credentials and len(credentials) == 2: 850 base64string = base64.encodestring('%s:%s' % credentials)[:-1] 851 self.addheader("Authorization", base64string) 852 self.plain_post_header["Authorization"] = base64string 853 self.using_authentication = True 854 else: 855 self.using_authentication = False
856
857 - def post_plain_text(self, url, body):
858 url = self.prepare_url(url) 859 o = urlparse(url) 860 con = httplib.HTTPConnection(o.hostname, o.port) 861 con.request('POST', url, body, self.plain_post_header) 862 resp = con.getresponse() 863 content = resp.read() 864 con.close() 865 if resp.status != 200: 866 raise WebserviceError(resp.status, resp.reason, content) 867 return content
868
869 - def open(self, url, data=None):
870 url = self.prepare_url(url) 871 return urllib.FancyURLopener.open(self, url, data)
872
873 - def prepare_url(self, url):
874 if self.token: 875 token_param = "token=" + self.token 876 o = urlparse(url) 877 if o.query: 878 url += "&" + token_param 879 else: 880 url += "?" + token_param 881 882 return url
883
884 - def delete(self, url):
885 url = self.prepare_url(url) 886 o = urlparse(url) 887 con = httplib.HTTPConnection(o.hostname, o.port) 888 con.request('DELETE', url, None, self.plain_post_header) 889 resp = con.getresponse() 890 content = resp.read() 891 con.close() 892 if resp.status != 200: 893 raise WebserviceError(resp.status, resp.reason, content) 894 return content
895
896 - def http_error_default(self, url, fp, errcode, errmsg, headers):
897 """Re-implementation of http_error_default, with content now supplied by default""" 898 content = fp.read() 899 fp.close() 900 raise WebserviceError(errcode, errmsg, content)
901
902 - def http_error_400(self, url, fp, errcode, errmsg, headers, data=None):
903 """ 904 Handle 400 HTTP errors, attempting to return informative error messages 905 ======================================================================= 906 907 400 errors indicate that something about our request was incorrect 908 909 @raise WebserviceError: in all circumstances 910 911 """ 912 content = fp.read() 913 fp.close() 914 raise WebserviceError("There was a problem with our request", errcode, errmsg, content)
915
916 - def http_error_401(self, url, fp, errcode, errmsg, headers, data=None):
917 """ 918 Handle 401 HTTP errors, attempting to return informative error messages 919 ======================================================================= 920 921 401 errors indicate we don't have sufficient permission for the resource 922 we requested - usually a list or a tempate 923 924 @raise WebserviceError: in all circumstances 925 926 """ 927 content = fp.read() 928 fp.close() 929 if self.using_authentication: 930 raise WebserviceError("Insufficient permissions", errcode, errmsg, content) 931 else: 932 raise WebserviceError("No permissions - not logged in", errcode, errmsg, content)
933
934 - def http_error_404(self, url, fp, errcode, errmsg, headers, data=None):
935 """ 936 Handle 404 HTTP errors, attempting to return informative error messages 937 ======================================================================= 938 939 404 errors indicate that the requested resource does not exist - usually 940 a template that is not longer available. 941 942 @raise WebserviceError: in all circumstances 943 944 """ 945 content = fp.read() 946 fp.close() 947 raise WebserviceError("Missing resource", errcode, errmsg, content)
948 - def http_error_500(self, url, fp, errcode, errmsg, headers, data=None):
949 """ 950 Handle 500 HTTP errors, attempting to return informative error messages 951 ======================================================================= 952 953 500 errors indicate that the server borked during the request - ie: it wasn't 954 our fault. 955 956 @raise WebserviceError: in all circumstances 957 958 """ 959 content = fp.read() 960 fp.close() 961 raise WebserviceError("Internal server error", errcode, errmsg, content)
962
963 -class UnimplementedError(Exception):
964 pass
965
966 -class ServiceError(ReadableException):
967 """Errors in the creation and use of the Service object""" 968 pass
969 -class WebserviceError(IOError):
970 """Errors from interaction with the webservice""" 971 pass
972