Package sword2 :: Module connection
[hide private]
[frames] | no frames]

Source Code for Module sword2.connection

   1  #!/usr/bin/env python 
   2  # -*- coding: utf-8 -*- 
   3   
   4  """ 
   5  This module provides the 'Connection' class, a SWORD2 client. 
   6   
   7  #BETASWORD2URL 
   8  See http://sword-app.svn.sourceforge.net/viewvc/sword-app/spec/trunk/SWORDProfile.html?revision=HEAD for information 
   9  about the SWORD2 AtomPub profile. 
  10    
  11  """ 
  12  from sword2_logging import logging 
  13  conn_l = logging.getLogger(__name__) 
  14   
  15  from utils import Timer, NS, get_md5, create_multipart_related 
  16   
  17  from transaction_history import Transaction_History 
  18  from service_document import ServiceDocument 
  19  from deposit_receipt import Deposit_Receipt 
  20  from error_document import Error_Document 
  21  from collection import Sword_Statement 
  22  from exceptions import * 
  23   
  24  from compatible_libs import etree 
  25   
  26  import httplib2 
  27   
28 -class Connection(object):
29 """ 30 `Connection` - SWORD2 client 31 32 This connection is predicated on having a Service Document (SD), preferably by an instance being constructed with 33 the Service Document IRI (SD-IRI) which can dereference to the XML document itself. 34 35 36 Contructor parameters: 37 38 There are a number of flags that can be set when getting an instance of this class that affect the behaviour 39 of the client. See the help for `self.__init__` for more details. 40 41 Example usage: 42 43 >>> from sword2 import Connection 44 >>> conn = Connection("http://example.org/service-doc") # An SD-IRI is required. 45 46 47 48 49 # Get, validate and parse the document at the SD_IRI: 50 >>> conn.get_service_document() 51 52 # Load a Service Document from a string: 53 >>> conn.load_service_document(xml_service_doc) 54 2011-05-30 01:06:13,251 - sword2.service_document - INFO - Initial SWORD2 validation checks on service document - Valid document? True 55 56 # View transaction history (if enabled) 57 >>> print conn.history.to_pretty_json() 58 [ 59 { 60 "sd_iri": "http://example.org/service-doc", 61 "timestamp": "2011-05-30T01:05:54.071042", 62 "on_behalf_of": null, 63 "type": "init", 64 "user_name": null 65 }, 66 { 67 "IRI": "http://example.org/service-doc", 68 "valid": true, 69 "sword_version": "2.0", 70 "duration": 0.0029349327087402344, 71 "timestamp": "2011-05-30T01:06:13.253907", 72 "workspaces_found": [ 73 "Main Site", 74 "Sub-site" 75 ], 76 "type": "SD Parse", 77 "maxUploadSize": 16777216 78 } 79 ] 80 81 # Start a connection and do not maintain a transaction history 82 # Useful for bulk-testing where the history might grow exponentially 83 >>> conn = Connection(...... , keep_history=False, ....) 84 85 # Initialise a connection and get the document at the SD IRI: 86 # (Uses the Simple Sword Server as an endpoint - sss.py 87 88 >>> from sword2 import Connection 89 >>> c = Connection("http://localhost:8080/sd-uri", download_service_document=True) 90 2011-05-30 02:04:24,179 - sword2.connection - INFO - keep_history=True--> This instance will keep a JSON-compatible transaction log of all (SWORD/APP) activities in 'self.history' 91 2011-05-30 02:04:24,215 - sword2.connection - INFO - Received a document for http://localhost:8080/sd-uri 92 2011-05-30 02:04:24,216 - sword2.service_document - INFO - Initial SWORD2 validation checks on service document - Valid document? True 93 >>> print c.history 94 -------------------- 95 Type: 'init' [2011-05-30T02:04:24.180182] 96 Data: 97 user_name: None 98 on_behalf_of: None 99 sd_iri: http://localhost:8080/sd-uri 100 -------------------- 101 Type: 'SD_IRI GET' [2011-05-30T02:04:24.215661] 102 Data: 103 sd_iri: http://localhost:8080/sd-uri 104 response: {'status': '200', 'content-location': 'http://localhost:8080/sd-uri', 'transfer-encoding': 'chunked', 'server': 'CherryPy/3.1.2 WSGI Server', 'date': 'Mon, 30 May 2011 01:04:24 GMT', 'content-type': 'text/xml'} 105 process_duration: 0.0354170799255 106 -------------------- 107 Type: 'SD Parse' [2011-05-30T02:04:24.220798] 108 Data: 109 maxUploadSize: 16777216 110 sd_iri: http://localhost:8080/sd-uri 111 valid: True 112 sword_version: 2.0 113 workspaces_found: ['Main Site'] 114 process_duration: 0.00482511520386 115 116 Please see the testsuite for this class for more examples of the sorts of transactions that can be done. (tests/test_connection*.py) 117 """ 118
119 - def __init__(self, service_document_iri, 120 user_name=None, 121 user_pass=None, 122 on_behalf_of=None, 123 download_service_document = False, # Don't automagically GET the SD_IRI by default 124 keep_history=True, 125 cache_deposit_receipts=True, 126 honour_receipts=True, 127 error_response_raises_exceptions=True):
128 """ 129 Creates a new Connection object. 130 131 Parameters: 132 133 Connection(service_document_iri, <--- REQUIRED - use a dummy string here if the SD is local only. 134 135 # OPTIONAL parameters (default values are shown below) 136 137 # Authentication parameters: (can use any method that `httplib2` provides) 138 139 user_name=None, 140 user_pass=None, 141 142 # Set the SWORD2 On Behalf Of value here, for it to be included as part of every transaction 143 # Can be passed to every transaction method (update resource, delete deposit, etc) otherwise 144 145 on_behalf_of=None, 146 147 ## Behaviour Flags 148 # Try to GET the service document from the provided SD-IRI in `service_document_iri` if True 149 150 download_service_document = False, # Don't automagically GET the SD_IRI by default 151 152 # Keep a history of all transactions made with the SWORD2 Server 153 # Records details like the response headers, sent headers, times taken and so forth 154 # Kept in a `sword2.transaction_history:Transaction_History` object but can be treated like an ordinary `list` 155 keep_history=True, 156 157 # Keep a cache of all deposit receipt responses from the server and provide an 'index' to these `sword2.Deposit_Receipt` objects 158 # by Edit-IRI, Content-IRI and Sword-Edit-IRI. (ie given an Edit-IRI, find the deposit receipt for the last received response containing 159 # that IRI. 160 # If the following flag, `honour_receipts` is set to True, packaging checks and other limits set in these receipts will be 161 # honoured. 162 # For example, a request for an item with an invalid packaging type will never reach the server, but throw an exception. 163 164 cache_deposit_receipts=True, 165 166 # Make sure to behave as required by the SWORD2 server - not sending too large a file, not asking for invalid packaging types and so on. 167 168 honour_receipts=True, 169 170 # Two means of handling server error responses: 171 # If set to True - An exception will be thrown from `sword2.exceptions` (caused by any server error response w/ 172 # HTTP code greater than or equal to 400) 173 # OR 174 # If set to False - A `sword2.error_document:Error_Document` object will be returned. 175 176 error_response_raises_exceptions=True 177 ) 178 179 If a `Connection` is created with the parameter `download_service_document` set to `False`, then no attempt 180 to dereference the `service_document_iri` (SD-IRI) will be made at this stage. 181 182 To cause it to get or refresh the service document from this IRI, call `self.get_service_document()` 183 184 Loading in a locally held Service Document: 185 186 >>> conn = Connection(....) 187 188 >>> with open("service_doc.xml", "r") as f: 189 ... conn.load_service_document(f.read()) 190 191 192 """ 193 self.sd_iri = service_document_iri 194 self.sd = None 195 196 # Client behaviour flags: 197 # Honour deposit receipts - eg raise exceptions if interactions are attempted that the service document 198 # does not allow without bothering the server - invalid packaging types, max upload sizes, etc 199 self.honour_receipts = honour_receipts 200 201 # When error_response_raises_exceptions == True: 202 # Error responses (HTTP codes >399) will raise exceptions (from sword2.exceptions) in response 203 # when False: 204 # Error Responses, if Content-Type is text/xml or application/xml, a sword2.error_document.Error_Document will be the 205 # return - No Exception will be raised! 206 # Check Error_Document.code to get the response code, regardless to whether a valid Sword2 error document was received. 207 self.raise_except = error_response_raises_exceptions 208 209 self.keep_cache = cache_deposit_receipts 210 self.h = httplib2.Http(".cache", timeout=30.0) 211 self.user_name = user_name 212 self.on_behalf_of = on_behalf_of 213 214 # Cached Deposit Receipt 'indexes' *cough, cough* 215 self.edit_iris = {} # Key = IRI, Value = ref to latest Deposit Receipt for the resource 216 self.cont_iris = {} # Key = IRI, Value = ref to latest Deposit Receipt 217 self.se_iris = {} # Key = IRI, Value = ref to latest Deposit Receipt 218 self.cached_at = {} # Key = Edit-IRI, Value = Timestamp for when receipt was cached 219 220 # Transaction history hooks 221 self.history = None 222 self._t = Timer() 223 self.keep_history = keep_history 224 if keep_history: 225 conn_l.info("keep_history=True--> This instance will keep a JSON-compatible transaction log of all (SWORD/APP) activities in 'self.history'") 226 self.reset_transaction_history() 227 self.history.log('init', 228 sd_iri = self.sd_iri, 229 user_name = self.user_name, 230 on_behalf_of = self.on_behalf_of ) 231 # Add credentials to http client 232 if user_name: 233 conn_l.info("Adding username/password credentials for the client to use.") 234 self.h.add_credentials(user_name, user_pass) 235 236 if self.sd_iri and download_service_document: 237 self._t.start("get_service_document") 238 self.get_service_document() 239 conn_l.debug("Getting service document and dealing with the response: %s s" % self._t.time_since_start("get_service_document")[1])
240
241 - def _return_error_or_exception(self, cls, resp, content):
242 """Internal method for reporting errors, behaving as the `self.raise_except` flag requires. 243 244 `self.raise_except` can be altered at any time to affect this methods behaviour.""" 245 if self.raise_except: 246 raise cls(resp) 247 else: 248 if resp['content-type'] in ['text/xml', 'application/xml']: 249 conn_l.info("Returning an error document, due to HTTP response code %s" % resp.status) 250 e = Error_Document(content, code=resp.status, resp = resp) 251 return e 252 else: 253 conn_l.info("Returning due to HTTP response code %s" % resp.status) 254 e = Error_Document(code=resp.status, resp = resp) 255 return e
256
257 - def _handle_error_response(self, resp, content):
258 """Catch a number of general HTTP error responses from the server, based on HTTP code 259 260 401 - Unauthorised. 261 Will throw a `sword2.exceptions.NotAuthorised` exception, if exceptions are set to be on. 262 Otherwise will return a `sword2.Error_Document` (likewise for the rest of these) 263 264 403 - Forbidden. 265 Will throw a `sword2.exceptions.Forbidden` exception 266 267 404 - Not Found. 268 Will throw a `sword2.exceptions.NotFound` exception 269 270 408 - Request Timeout 271 Will throw a `sword2.exceptions.RequestTimeOut` exception 272 273 500-599 errors: 274 Will throw a general `sword2.exceptions.ServerError` exception 275 276 4XX not listed: 277 Will throw a general `sword2.exceptions.HTTPResponseError` exception 278 """ 279 if resp['status'] == "401": 280 conn_l.error("You are unauthorised (401) to access this document on the server. Check your username/password credentials and your 'On Behalf Of'") 281 self._return_error_or_exception(NotAuthorised, resp, content) 282 elif resp['status'] == "403": 283 conn_l.error("You are Forbidden (401) to POST to '%s'. Check your username/password credentials and your 'On Behalf Of'") 284 self._return_error_or_exception(Forbidden, resp, content) 285 elif resp['status'] == "408": 286 conn_l.error("Request Timeout (408) - error uploading.") 287 self._return_error_or_exception(RequestTimeOut, resp, content) 288 elif int(resp['status']) > 499: 289 conn_l.error("Server error occured. Response headers from the server:\n%s" % resp) 290 self._return_error_or_exception(ServerError, resp, content) 291 else: 292 conn_l.error("Unknown error occured. Response headers from the server:\n%s" % resp) 293 self._return_error_or_exception(HTTPResponseError, resp, content)
294
295 - def _cache_deposit_receipt(self, d):
296 """Method for storing the deposit receipts, and also for providing lookup dictionaries that 297 reference these objects. 298 299 (only provides cache if `self.keep_cache` is `True` [via the `cache_deposit_receipts` init parameter flag]) 300 301 Provides and maintains: 302 self.edit_iris -- a `dict`, keys: Edit-IRI hrefs, values: `sword2.Deposit_Receipt` objects they appear in 303 304 self.cont_iris -- a `dict`, keys: Content-IRI hrefs, values: `sword2.Deposit_Receipt` objects they appear in 305 306 self.se_iris -- a `dict`, keys: Sword-Edit-IRI hrefs, values: `sword2.Deposit_Receipt` objects they appear in 307 308 self.cached_at -- a `dict`, keys: Edit-IRIs, values: timestamp when receipt was last cached. 309 """ 310 if self.keep_cache: 311 timestamp = self._t.get_timestamp() 312 conn_l.debug("Caching document (Edit-IRI:%s) - at %s" % (d.edit, timestamp)) 313 self.edit_iris[d.edit] = d 314 if d.cont_iri: # SHOULD exist within receipt 315 self.cont_iris[d.cont_iri] = d 316 if d.se_iri: 317 # MUST exist according to the spec, but as it can be the same as the Edit-IRI 318 # it seems likely that a server implementation might ignore the 'MUST' part. 319 self.se_iris[d.se_iri] = d 320 self.cached_at[d.edit] = self._t.get_timestamp() 321 else: 322 conn_l.debug("Caching request denied - deposit receipt caching is set to 'False'")
323
324 - def load_service_document(self, xml_document):
325 """Load the Service Document XML from bytestring, `xml_document` 326 327 Useful if SD-IRI is non-existant or invalid. 328 329 Will set the following convenience attributes: 330 331 `self.sd` -- the `sword2.ServiceDocument` instance 332 333 `self.workspaces` -- a `list` of workspace tuples, of the form: 334 ('Workspace atom:title', [<`sword2.Collection` object>, ....]), 335 336 `self.maxUploadSize` -- the maximum filesize for a deposit, if given in the service document 337 """ 338 self._t.start("SD Parse") 339 self.sd = ServiceDocument(xml_document) 340 _, took_time = self._t.time_since_start("SD Parse") 341 # Set up some convenience references 342 self.workspaces = self.sd.workspaces 343 self.maxUploadSize = self.sd.maxUploadSize 344 345 if self.history: 346 if self.sd.valid: 347 self.history.log('SD Parse', 348 sd_iri = self.sd_iri, 349 valid = self.sd.valid, 350 workspaces_found = [k for k,v in self.sd.workspaces], 351 sword_version = self.sd.version, 352 maxUploadSize = self.sd.maxUploadSize, 353 process_duration = took_time) 354 else: 355 self.history.log('SD Parse', 356 sd_iri = self.sd_iri, 357 valid = self.sd.valid, 358 process_duration = took_time)
359
360 - def get_service_document(self):
361 """Perform an HTTP GET on the Service Document IRI (SD-IRI) and attempt to parse the result as 362 a SWORD2 Service Document (using `self.load_service_document`) 363 """ 364 headers = {} 365 if self.on_behalf_of: 366 headers['on-behalf-of'] = self.on_behalf_of 367 self._t.start("SD_URI request") 368 resp, content = self.h.request(self.sd_iri, "GET", headers=headers) 369 _, took_time = self._t.time_since_start("SD_URI request") 370 if self.history: 371 self.history.log('SD_IRI GET', 372 sd_iri = self.sd_iri, 373 response = resp, 374 process_duration = took_time) 375 if resp['status'] == "200": 376 conn_l.info("Received a document for %s" % self.sd_iri) 377 self.load_service_document(content) 378 elif resp['status'] == "401": 379 conn_l.error("You are unauthorised (401) to access this document on the server. Check your username/password credentials")
380
381 - def reset_transaction_history(self):
382 """ Clear the transaction history - `self.history`""" 383 del self.history 384 self.history = Transaction_History()
385
386 - def _make_request(self, 387 target_iri, 388 payload=None, # These need to be set to upload a file 389 mimetype=None, 390 filename=None, 391 packaging=None, 392 393 metadata_entry=None, # a sword2.Entry needs to be here, if 394 # a metadata entry is to be uploaded 395 396 # Set both a file and a metadata entry for the method to perform a multipart 397 # related upload. 398 suggested_identifier=None, # 'slug' 399 in_progress=True, 400 on_behalf_of=None, 401 metadata_relevant=False, 402 403 # flags: 404 empty = None, # If this is True, then the POST/PUT is sent with an empty body 405 # and the 'Content-Length' header explicitly set to 0 406 method = "POST", 407 request_type="" # text label for transaction history reports 408 ):
409 """Performs an HTTP request, as defined by the parameters. This is an internally used method and it is best that it 410 is not called directly. 411 412 target_iri -- IRI that will be the target of the HTTP call 413 414 # File upload parameters: 415 payload - the payload to send. Can be either a bytestring or a File-like object that supports `payload.read()` 416 mimetype - MIMEType of the payload 417 filename - filename. Most SWORD2 uploads have this as being mandatory. 418 packaging - the SWORD2 packaging type of the payload. 419 eg packaging = 'http://purl.org/net/sword/package/Binary' 420 421 # NB to work around a possible bug in httplib2 0.6.0, the file-like object is read into memory rather than streamed 422 # from disc, so is not as efficient as it should be. That said, it is recommended that file handles are passed to 423 # the _make_request method, as this is hoped to be a temporary situation. 424 425 metadata_entry - a `sword2.Entry` to be uploaded with metadata fields set as desired. 426 427 # If there is both a payload and a metadata_entry, then the request will be made as a Multipart-related request 428 # Otherwise, it will be a normal request for whicever type of upload. 429 430 empty - a flag to specify that an empty request should be made. A blank body and a 'Content-Length:0' header will be explicitly added 431 and any payload or metadata_entry passed in will be ignored. 432 433 434 # Header flags: 435 suggested_identifier -- set the 'Slug' header 436 in_progress -- 'In-Progress' 437 on_behalf_of -- 'On-Behalf-Of' 438 metadata_relevant -- 'Metadata-Relevant' 439 440 # HTTP settings: 441 method -- "GET", "POST", etc 442 request_type -- A label to be used in the transaction history for this particular operation. 443 444 Response: 445 446 A `sword2.Deposit_Receipt` object containing the deposit receipt data. If the response was blank or 447 not a Deposit Response, then only a few attributes will be populated: 448 449 `code` -- HTTP code of the response 450 `response_headers` -- `dict` of the reponse headers 451 `content` -- (Optional) in case the response body is not empty but the response is not a Deposit Receipt 452 453 If exception-throwing is turned off (`error_response_raises_exceptions = False` or `self.raise_except = False`) 454 then the response will be a `sword2.Error_Document`, but will still have the aforementioned attributes set, (code, 455 response_headers, etc) 456 """ 457 if payload: 458 md5sum, f_size = get_md5(payload) 459 460 # request-level headers 461 headers = {} 462 headers['In-Progress'] = str(in_progress).lower() 463 if on_behalf_of: 464 headers['On-Behalf-Of'] = self.on_behalf_of 465 elif self.on_behalf_of: 466 headers['On-Behalf-Of'] = self.on_behalf_of 467 468 if suggested_identifier: 469 headers['Slug'] = str(suggested_identifier) 470 471 if suggested_identifier: 472 headers['Slug'] = str(suggested_identifier) 473 474 if metadata_relevant: 475 headers['Metadata-Relevant'] = str(metadata_relevant).lower() 476 477 if hasattr(payload, 'read'): 478 # Need to work out why a 401 challenge will stop httplib2 from sending the file... 479 # likely need to make it re-seek to 0... 480 # In the meantime, read the file into memory... *sigh* 481 payload = payload.read() 482 483 self._t.start(request_type) 484 if empty: 485 # NULL body with explicit zero length. 486 headers['Content-Length'] = "0" 487 resp, content = self.h.request(target_iri, method, headers=headers) 488 _, took_time = self._t.time_since_start(request_type) 489 if self.history: 490 self.history.log(request_type + ": Empty request", 491 sd_iri = self.sd_iri, 492 target_iri = target_iri, 493 method = method, 494 response = resp, 495 headers = headers, 496 process_duration = took_time) 497 elif method == "DELETE": 498 resp, content = self.h.request(target_iri, method, headers=headers) 499 _, took_time = self._t.time_since_start(request_type) 500 if self.history: 501 self.history.log(request_type + ": DELETE request", 502 sd_iri = self.sd_iri, 503 target_iri = target_iri, 504 method = method, 505 response = resp, 506 headers = headers, 507 process_duration = took_time) 508 509 elif metadata_entry and not (filename and payload): 510 # Metadata-only resource creation 511 headers['Content-Type'] = "application/atom+xml;type=entry" 512 data = str(metadata_entry) 513 headers['Content-Length'] = str(len(data)) 514 515 resp, content = self.h.request(target_iri, method, headers=headers, body = data) 516 _, took_time = self._t.time_since_start(request_type) 517 if self.history: 518 self.history.log(request_type + ": Metadata-only resource request", 519 sd_iri = self.sd_iri, 520 target_iri = target_iri, 521 method = method, 522 response = resp, 523 headers = headers, 524 process_duration = took_time) 525 526 elif metadata_entry and filename and payload: 527 # Multipart resource creation 528 multicontent_type, payload_data = create_multipart_related([{'key':'atom', 529 'type':'application/atom+xml; charset="utf-8"', 530 'data':str(metadata_entry), # etree default is utf-8 531 }, 532 {'key':'payload', 533 'type':str(mimetype), 534 'filename':filename, 535 'data':payload, 536 'headers':{'Content-MD5':str(md5sum), 537 'Packaging':str(packaging), 538 } 539 } 540 ]) 541 542 headers['Content-Type'] = multicontent_type + '; type="application/atom+xml"' 543 headers['Content-Length'] = str(len(payload_data)) # must be str, not int type 544 resp, content = self.h.request(target_iri, method, headers=headers, body = payload_data) 545 _, took_time = self._t.time_since_start(request_type) 546 if self.history: 547 self.history.log(request_type + ": Multipart resource request", 548 sd_iri = self.sd_iri, 549 target_iri = target_iri, 550 response = resp, 551 headers = headers, 552 method = method, 553 multipart = [{'key':'atom', 554 'type':'application/atom+xml; charset="utf-8"' 555 }, 556 {'key':'payload', 557 'type':str(mimetype), 558 'filename':filename, 559 'headers':{'Content-MD5':str(md5sum), 560 'Packaging':str(packaging), 561 } 562 }], # record just the headers used in multipart construction 563 process_duration = took_time) 564 elif filename and payload: 565 headers['Content-Type'] = str(mimetype) 566 headers['Content-MD5'] = str(md5sum) 567 headers['Content-Length'] = str(f_size) 568 headers['Content-Disposition'] = "attachment; filename=%s" % filename # TODO: ensure filename is ASCII 569 headers['Packaging'] = str(packaging) 570 571 resp, content = self.h.request(target_iri, method, headers=headers, body = payload) 572 _, took_time = self._t.time_since_start(request_type) 573 if self.history: 574 self.history.log(request_type + ": simple resource request", 575 sd_iri = self.sd_iri, 576 target_iri = target_iri, 577 method = method, 578 response = resp, 579 headers = headers, 580 process_duration = took_time) 581 else: 582 conn_l.error("Parameters were not complete: requires a metadata_entry, or a payload/filename/packaging or both") 583 raise Exception("Parameters were not complete: requires a metadata_entry, or a payload/filename/packaging or both") 584 585 if resp['status'] == "201": 586 # Deposit receipt in content 587 conn_l.info("Received a Resource Created (201) response.") 588 # Check response headers for updated Location IRI 589 location = resp.get('location', None) 590 if len(content) > 0: 591 # Fighting chance that this is a deposit receipt 592 d = Deposit_Receipt(xml_deposit_receipt = content) 593 if d.parsed: 594 conn_l.info("Server response included a Deposit Receipt. Caching a copy in .resources['%s']" % d.edit) 595 d.response_headers = dict(resp) 596 d.location = location 597 d.code = 201 598 self._cache_deposit_receipt(d) 599 return d 600 else: 601 # No body... 602 d = Deposit_Receipt() 603 conn_l.info("Server response dir not include a Deposit Receipt.") 604 d.response_headers = dict(resp) 605 d.code = 201 606 d.location = location 607 return d 608 elif resp['status'] == "204": 609 # Deposit receipt in content 610 conn_l.info("Received a valid 'No Content' (204) response.") 611 location = resp.get('location', None) 612 # Check response headers for updated Locatio 613 return Deposit_Receipt(response_headers = dict(resp), location=location, code=204) 614 elif resp['status'] == "200": 615 # Deposit receipt in content 616 conn_l.info("Received a valid (200) OK response.") 617 content_type = resp.get('content-type') 618 location = resp.get('location', None) 619 if content_type == "application/atom+xml;type=entry" and len(content) > 0: 620 d = Deposit_Receipt(content) 621 if d.parsed: 622 conn_l.info("Server response included a Deposit Receipt. Caching a copy in .resources['%s']" % d.edit) 623 d.response_headers = dict(resp) 624 d.location = location 625 d.code = 200 626 self._cache_deposit_receipt(d) 627 return d 628 else: 629 # No atom entry... 630 d = Deposit_Receipt() 631 conn_l.info("Server response dir not include a Deposit Receipt Entry.") 632 d.response_headers = dict(resp) 633 d.location = location 634 d.content = content 635 return d 636 else: 637 return self._handle_error_response(resp, content)
638 639 640
641 - def create(self, 642 workspace=None, # Either provide workspace/collection or 643 collection=None, # the exact Col-IRI itself 644 col_iri=None, 645 646 payload=None, # These need to be set to upload a file 647 mimetype=None, 648 filename=None, 649 packaging=None, 650 651 metadata_entry=None, # a sword2.Entry needs to be here, if 652 # a metadata entry is to be uploaded 653 654 # Set both a file and a metadata entry for the method to perform a multipart 655 # related upload. 656 657 suggested_identifier=None, 658 in_progress=True, 659 on_behalf_of=None, 660 ):
661 """ 662 Creating a Resource 663 =================== 664 665 #BETASWORD2URL 666 See 6.3 Creating a Resource http://sword-app.svn.sourceforge.net/viewvc/sword-app/spec/trunk/SWORDProfile.html?revision=HEAD#protocoloperations_creatingresource 667 668 Basic parameters: 669 670 This method can create a new resource in a Collection on a SWORD2 server, given suitable authentication to do so. 671 672 Select a collection to send a request to by either: 673 674 setting the param `col_iri` to its Collection-IRI or Col-IRI 675 676 or 677 678 setting 'workspace' and 'collection' to the labels for the desired workspace and collection. 679 680 SWORD2 request parameters: 681 682 `suggested_identifier` -- the suggested identifier of this resource (HTTP header of 'Slug'), 683 684 `in_progress` (`True` or `False`) -- whether or not the deposit should be considered by the 685 server to be in progress ('In-Progress') 686 `on_behalf_of` -- if this is a mediated deposit ('On-Behalf-Of') 687 (the client-wide setting `self.on_behalf_of will be used otherwise) 688 689 690 1. "Binary File Deposit in a given Collection" 691 ---------------------------------------------- 692 693 Set the following parameters in addition to the basic parameters: 694 695 `payload` - the payload to send. Can be either a bytestring or a File-like object that supports `payload.read()` 696 `mimetype` - MIMEType of the payload 697 `filename` - filename. Most SWORD2 uploads have this as being mandatory. 698 `packaging` - the SWORD2 packaging type of the payload. 699 eg packaging = 'http://purl.org/net/sword/package/Binary' 700 701 Response: 702 703 A `sword2.Deposit_Receipt` object containing the deposit receipt data. If the response was blank or 704 not a Deposit Response, then only a few attributes will be populated: 705 706 `code` -- HTTP code of the response 707 `response_headers` -- `dict` of the reponse headers 708 `content` -- (Optional) in case the response body is not empty but the response is not a Deposit Receipt 709 710 If exception-throwing is turned off (`error_response_raises_exceptions = False` or `self.raise_except = False`) 711 then the response will be a `sword2.Error_Document`, but will still have the aforementioned attributes set, (code, 712 response_headers, etc) 713 714 2. "Creating a Resource with an Atom Entry" 715 ------------------------------------------- 716 717 create a container within a SWORD server and optionally provide it with metadata without adding any binary content to it. 718 719 Set the following parameters in addition to the basic parameters: 720 721 `metadata_entry` - An instance of `sword2.Entry`, set with the metadata required. 722 723 for example: 724 # conn = `sword2.Connection`, collection_iri = Collection-IRI 725 >>> from sword2 import Entry 726 >>> entry = Entry(title = "My new deposit", 727 ... id = "foo:id", 728 ... dcterms_abstract = "My Thesis", 729 ... dcterms_author = "Me", 730 ... dcterms_issued = "2009") 731 732 >>> conn.create(col_iri = collection_iri, 733 ... metadata_entry = entry, 734 ... in_progress = True) 735 # likely to want to add the thesis files later for example but get the identifier for the deposit now 736 737 Response: 738 739 A `sword2.Deposit_Receipt` object containing the deposit receipt data. If the response was blank or 740 not a Deposit Response, then only a few attributes will be populated: 741 742 `code` -- HTTP code of the response 743 `response_headers` -- `dict` of the reponse headers 744 `content` -- (Optional) in case the response body is not empty but the response is not a Deposit Receipt 745 746 If exception-throwing is turned off (`error_response_raises_exceptions = False` or `self.raise_except = False`) 747 then the response will be a `sword2.Error_Document`, but will still have the aforementioned attributes set, (code, 748 response_headers, etc) 749 750 3. "Creating a Resource with a Multipart Deposit" 751 ------------------------------------------------- 752 753 Create a resource in a given collection by uploading a file AND the metadata about this resource. 754 755 To make this sort of request, just set the parameters as shown for both the binary upload and the metadata upload. 756 757 eg: 758 759 >>> conn.create(col_iri = collection_iri, 760 ... metadata_entry = entry, 761 ... payload = open("foo.zip", "r"), 762 ... mimetype = 763 .... and so on 764 765 Response: 766 767 A `sword2.Deposit_Receipt` object containing the deposit receipt data. If the response was blank or 768 not a Deposit Response, then only a few attributes will be populated: 769 770 `code` -- HTTP code of the response 771 `response_headers` -- `dict` of the reponse headers 772 `content` -- (Optional) in case the response body is not empty but the response is not a Deposit Receipt 773 774 If exception-throwing is turned off (`error_response_raises_exceptions = False` or `self.raise_except = False`) 775 then the response will be a `sword2.Error_Document`, but will still have the aforementioned attributes set, (code, 776 response_headers, etc) 777 778 (under the hood, this request uses Atom Multipart-related) 779 780 From the spec: 781 782 "In order to ensure that all SWORD clients and servers can exchange a full range of file content and metadata, the use of Atom Multipart [AtomMultipart] is permitted to combine a package (possibly a simple ZIP) with a set of Dublin Core metadata terms [DublinCore] embedded in an Atom Entry. 783 784 The SWORD server is not required to support packaging formats, but this profile RECOMMENDS that the server be able to accept a ZIP file as the Media Part of an Atom Multipart request (See Section 5: IRIs and Section 7: Packaging for more details)." 785 """ 786 conn_l.debug("Create Resource") 787 if not col_iri: 788 for w, collections in self.workspaces: 789 if w == workspace: 790 for c in collections: 791 if c.title == collection: 792 conn_l.debug("Matched: Workspace='%s', Collection='%s' ==> Col-IRI='%s'" % (workspace, 793 collection, 794 c.href)) 795 col_iri = c.href 796 break 797 798 if not col_iri: # no col_iri provided and no valid workspace/collection given 799 conn_l.error("No suitable Col-IRI was found, with the given parameters.") 800 return 801 802 return self._make_request(target_iri = col_iri, 803 payload=payload, 804 mimetype=mimetype, 805 filename=filename, 806 packaging=packaging, 807 metadata_entry=metadata_entry, 808 suggested_identifier=suggested_identifier, 809 in_progress=in_progress, 810 on_behalf_of=on_behalf_of, 811 method="POST", 812 request_type='Col_IRI POST')
813
814 - def update(self, metadata_entry = None, # required for a metadata update 815 payload = None, # required for a file update 816 filename = None, # required for a file update 817 mimetype=None, # required for a file update 818 packaging=None, # required for a file update 819 820 dr = None, # Important! Without this, you will have to set the edit_iri AND the edit_media_iri parameters. 821 822 edit_iri = None, 823 edit_media_iri = None, 824 825 metadata_relevant=False, 826 in_progress=False, 827 on_behalf_of=None, 828 ):
829 """ 830 Replacing the Metadata and/or Files of a Resource 831 832 #BETASWORD2URL 833 See http://sword-app.svn.sourceforge.net/viewvc/sword-app/spec/trunk/SWORDProfile.html?revision=HEAD#protocoloperations_editingcontent_multipart 834 835 Replace the metadata and/or files of a resource. 836 837 This wraps a number of methods and relies on being passed the Deposit Receipt, as the target IRI changes depending 838 on whether the metadata, the files or both are to be updated by the request. 839 840 This method has the same functionality as the following methods: 841 update_files_for_resource 842 update_metadata_for_resource 843 update_metadata_and_files_for_resource 844 845 Usage: 846 ------ 847 848 Set the target for this request: 849 -------------------------------- 850 851 You MUST pass back the `sword2.Deposit_Receipt` object you got from a previous transaction as the `dr` parameter, 852 and the correct IRI will automatically be chosen based on what combination of files you want to upload. 853 854 Then, add in the metadata and/or file information as desired: 855 ------------------------------------------------------------- 856 857 File information requires: 858 859 `payload` - the payload to send. Can be either a bytestring or a File-like object that supports `payload.read()` 860 `mimetype` - MIMEType of the payload 861 `filename` - filename. Most SWORD2 uploads have this as being mandatory. 862 `packaging` - the SWORD2 packaging type of the payload. 863 eg packaging = 'http://purl.org/net/sword/package/Binary' 864 865 `metadata_relevant` - This should be set to `True` if the server should consider the file a potential source of metadata extraction, 866 or `False` if the server should not attempt to extract any metadata from the deposi 867 868 Metadata information requires: 869 870 `metadata_entry` - An instance of `sword2.Entry`, set with the metadata required. 871 872 for example, to create a metadata entry 873 >>> from sword2 import Entry 874 >>> entry = Entry(title = "My new deposit", 875 ... id = "new:id", # atom:id 876 ... dcterms_abstract = "My Thesis", 877 ... dcterms_author = "Ben", 878 ... dcterms_issued = "2010") 879 880 Response: 881 882 A `sword2.Deposit_Receipt` object containing the deposit receipt data. If the response was blank or 883 not a Deposit Response, then only a few attributes will be populated: 884 885 `code` -- HTTP code of the response 886 `response_headers` -- `dict` of the reponse headers 887 `content` -- (Optional) in case the response body is not empty but the response is not a Deposit Receipt 888 889 If exception-throwing is turned off (`error_response_raises_exceptions = False` or `self.raise_except = False`) 890 then the response will be a `sword2.Error_Document`, but will still have the aforementioned attributes set, (code, 891 response_headers, etc) 892 """ 893 target_iri = None 894 request_type = "Update PUT" 895 if metadata_entry != None: 896 # Metadata or Metadata + file --> Edit-IRI 897 conn_l.info("Using the Edit-IRI - Metadata or Metadata + file multipart-related uses a PUT request to the Edit-IRI") 898 if payload != None and filename != None: 899 request_type = "Update Multipart PUT" 900 else: 901 request_type = "Update Metadata PUT" 902 if dr != None and dr.edit != None: 903 conn_l.info("Using the deposit receipt to get the Edit-IRI: %s" % dr.edit) 904 target_iri = dr.edit 905 elif edit_iri != None: 906 conn_l.info("Using the %s receipt as the Edit-IRI" % edit_iri) 907 target_iri = edit_iri 908 else: 909 conn_l.error("Metadata or Metadata + file multipart-related update: Cannot find the Edit-IRI from the parameters supplied.") 910 elif payload != None and filename != None: 911 # File-only --> Edit-Media-IRI 912 conn_l.info("Using the Edit-Media-IRI - File update uses a PUT request to the Edit-Media-IRI") 913 request_type = "Update File PUT" 914 if dr != None and dr.edit_media != None: 915 conn_l.info("Using the deposit receipt to get the Edit-Media-IRI: %s" % dr.edit_media) 916 target_iri = dr.edit_media 917 elif edit_media_iri != None: 918 conn_l.info("Using the %s receipt as the Edit-Media-IRI" % edit_media_iri) 919 target_iri = edit_media_iri 920 else: 921 conn_l.error("File update: Cannot find the Edit-Media-IRI from the parameters supplied.") 922 923 if target_iri == None: 924 raise Exception("No suitable IRI was found for the request needed.") 925 926 return self._make_request(target_iri = target_iri, 927 metadata_entry=metadata_entry, 928 payload=payload, 929 mimetype=mimetype, 930 filename=filename, 931 packaging=packaging, 932 on_behalf_of=on_behalf_of, 933 in_progress=in_progress, 934 metadata_relevant=str(metadata_relevant), 935 method="PUT", 936 request_type=request_type)
937 938 939
940 - def add_file_to_resource(self, 941 edit_media_iri, 942 payload, # These need to be set to upload a file 943 filename, # According to spec, "The client MUST supply a Content-Disposition header with a filename parameter 944 # (note that this requires the filename be expressed in ASCII)." 945 mimetype=None, 946 947 948 on_behalf_of=None, 949 in_progress=False, 950 metadata_relevant=False 951 ):
952 """ 953 Adding Files to the Media Resource 954 955 From the spec, paraphrased: 956 957 "This feature is for use when clients wish to send individual files to the server and to receive back the IRI for the created resource. [Adding new items to the deposit container] will not give back the location of the deposited resources, so in cases where the server does not provide the (optional) Deposit Receipt, it is not possible for the client to ascertain the location of the file actually deposited - the Location header in that operation is the Edit-IRI. By POSTing to the EM-IRI, the Location header will return the IRI of the deposited file itself, rather than that of the container. 958 959 As the EM-IRI represents the Media Resource itself, rather than the Container, this operation will not formally support metadata handling, and therefore also offers no explicit support for packaging either since packages may be both content and metadata. Nonetheless, for files which may contain extractable metadata, there is a Metadata-Relevant header which can be defined to indicate whether the deposit can be used to augment the metadata of the container." 960 961 #BETASWORD2URL 962 See http://sword-app.svn.sourceforge.net/viewvc/sword-app/spec/trunk/SWORDProfile.html?revision=HEAD#protocoloperations_addingcontent_mediaresource 963 964 965 Set the following parameters in addition to the basic parameters: 966 967 `edit_media_iri` - The Edit-Media-IRI 968 969 `payload` - the payload to send. Can be either a bytestring or a File-like object that supports `payload.read()` 970 `mimetype` - MIMEType of the payload 971 `filename` - filename. Most SWORD2 uploads have this as being mandatory. 972 `packaging` - the SWORD2 packaging type of the payload. 973 eg packaging = 'http://purl.org/net/sword/package/Binary' 974 975 Response: 976 977 A `sword2.Deposit_Receipt` object containing the deposit receipt data. If the response was blank or 978 not a Deposit Response, then only a few attributes will be populated: 979 980 `code` -- HTTP code of the response 981 `response_headers` -- `dict` of the reponse headers 982 `content` -- (Optional) in case the response body is not empty but the response is not a Deposit Receipt 983 984 If exception-throwing is turned off (`error_response_raises_exceptions = False` or `self.raise_except = False`) 985 then the response will be a `sword2.Error_Document`, but will still have the aforementioned attributes set, (code, 986 response_headers, etc) 987 988 """ 989 conn_l.info("Appending file to a deposit via Edit-Media-IRI %s" % edit_media_iri) 990 return self._make_request(target_iri = edit_media_iri, 991 payload=payload, 992 mimetype=mimetype, 993 filename=filename, 994 on_behalf_of=on_behalf_of, 995 in_progress=in_progress, 996 method="POST", 997 metadata_relevant=metadata_relevant, 998 request_type='EM_IRI POST (APPEND)')
999
1000 - def append(self, 1001 se_iri = None, 1002 1003 payload = None, # These need to be set to upload a file 1004 filename = None, # According to spec, "The client MUST supply a Content-Disposition header with a filename parameter 1005 # (note that this requires the filename be expressed in ASCII)." 1006 mimetype = None, 1007 packaging = None, 1008 on_behalf_of = None, 1009 metadata_entry = None, 1010 metadata_relevant = False, 1011 in_progress = False, 1012 dr = None 1013 ):
1014 """ 1015 Adding Content to a Resource 1016 1017 #BETASWORD2URL 1018 See http://sword-app.svn.sourceforge.net/viewvc/sword-app/spec/trunk/SWORDProfile.html?revision=HEAD#protocoloperations_addingcontent 1019 1020 Usage: 1021 ------ 1022 1023 Set the target for this request: 1024 -------------------------------- 1025 1026 Set `se_iri` to be the SWORD2-Edit-IRI for a given deposit. (This can be found in `sword2.Deposit_Receipt.se_iri`) 1027 1028 1029 OR 1030 1031 you can pass back the `sword2.Deposit_Receipt` object you got from a previous transaction as the `dr` parameter, 1032 and the correct IRI will automatically be chosen. 1033 1034 Then: 1035 ----- 1036 1037 1. "Adding New Packages or Files to a Container" 1038 ------------------------------------------------ 1039 1040 Set the following parameters in addition to the basic parameters: 1041 1042 `payload` - the payload to send. Can be either a bytestring or a File-like object that supports `payload.read()` 1043 `mimetype` - MIMEType of the payload 1044 `filename` - filename. Most SWORD2 uploads have this as being mandatory. 1045 `packaging` - the SWORD2 packaging type of the payload. 1046 eg packaging = 'http://purl.org/net/sword/package/Binary' 1047 1048 Response: 1049 1050 A `sword2.Deposit_Receipt` object containing the deposit receipt data. If the response was blank or 1051 not a Deposit Response, then only a few attributes will be populated: 1052 1053 `code` -- HTTP code of the response 1054 `response_headers` -- `dict` of the reponse headers 1055 `content` -- (Optional) in case the response body is not empty but the response is not a Deposit Receipt 1056 1057 If exception-throwing is turned off (`error_response_raises_exceptions = False` or `self.raise_except = False`) 1058 then the response will be a `sword2.Error_Document`, but will still have the aforementioned attributes set, (code, 1059 response_headers, etc) 1060 1061 1062 2. "Adding New Metadata to a Container" 1063 --------------------------------------- 1064 1065 NB SWORD2 does not instruct the server on the best way to handle metadata, only that metadata SHOULD be 1066 added and not overwritten; in certain circumstances this may not produce the desired behaviour. 1067 1068 Set the following parameters in addition to the basic parameters: 1069 1070 `metadata_entry` - An instance of `sword2.Entry`, set with the metadata required. 1071 1072 for example: 1073 # conn = `sword2.Connection`, se_iri = SWORD2-Edit-IRI 1074 >>> from sword2 import Entry 1075 >>> entry = Entry(dcterms:identifier = "doi://......") 1076 >>> conn.add_new_item_to_container(se_iri = se_iri, 1077 ... metadata_entry = entry) 1078 1079 Response: 1080 1081 A `sword2.Deposit_Receipt` object containing the deposit receipt data. If the response was blank or 1082 not a Deposit Response, then only a few attributes will be populated: 1083 1084 `code` -- HTTP code of the response 1085 `response_headers` -- `dict` of the reponse headers 1086 `content` -- (Optional) in case the response body is not empty but the response is not a Deposit Receipt 1087 1088 If exception-throwing is turned off (`error_response_raises_exceptions = False` or `self.raise_except = False`) 1089 then the response will be a `sword2.Error_Document`, but will still have the aforementioned attributes set, (code, 1090 response_headers, etc) 1091 1092 3. "Adding New Metadata and Packages or Files to a Container with Multipart" 1093 ---------------------------------------------------------------------------- 1094 1095 Create a resource in a given collection by uploading a file AND the metadata about this resource. 1096 1097 To make this sort of request, just set the parameters as shown for both the binary upload and the metadata upload. 1098 1099 eg: 1100 1101 >>> conn.add_new_item_to_container(se_iri = se_iri, 1102 ... metadata_entry = entry, 1103 ... payload = open("foo.zip", "r"), 1104 ... mimetype = 1105 .... and so on 1106 1107 Response: 1108 1109 A `sword2.Deposit_Receipt` object containing the deposit receipt data. If the response was blank or 1110 not a Deposit Response, then only a few attributes will be populated: 1111 1112 `code` -- HTTP code of the response 1113 `response_headers` -- `dict` of the reponse headers 1114 `content` -- (Optional) in case the response body is not empty but the response is not a Deposit Receipt 1115 1116 If exception-throwing is turned off (`error_response_raises_exceptions = False` or `self.raise_except = False`) 1117 then the response will be a `sword2.Error_Document`, but will still have the aforementioned attributes set, (code, 1118 response_headers, etc) 1119 1120 """ 1121 1122 if not se_iri: 1123 if dr != None: 1124 conn_l.info("Using the deposit receipt to get the SWORD2-Edit-IRI") 1125 se_iri = dr.se_iri 1126 if se_iri: 1127 conn_l.info("Update Resource via SWORD2-Edit-IRI %s" % se_iri) 1128 else: 1129 raise Exception("No SWORD2-Edit-IRI was given and no suitable IRI was found in the deposit receipt.") 1130 else: 1131 raise Exception("No SWORD2-Edit-IRI was given") 1132 else: 1133 conn_l.info("Update Resource via SWORD2-Edit-IRI %s" % se_iri) 1134 1135 conn_l.info("Adding new file, metadata or both to a SWORD deposit via SWORD-Edit-IRI %s" % se_iri) 1136 return self._make_request(target_iri = se_iri, 1137 payload=payload, 1138 mimetype=mimetype, 1139 packaging=packaging, 1140 filename=filename, 1141 metadata_entry=metadata_entry, 1142 on_behalf_of=on_behalf_of, 1143 in_progress=in_progress, 1144 method="POST", 1145 metadata_relevant=metadata_relevant, 1146 request_type='SE_IRI POST (APPEND PKG)')
1147 1148
1149 - def delete(self, 1150 resource_iri, 1151 on_behalf_of=None):
1152 """ 1153 Delete resource 1154 1155 Generic method to send an HTTP DELETE request to a given IRI. 1156 1157 Can be given the optional parameter of `on_behalf_of`. 1158 """ 1159 conn_l.info("Deleting resource %s" % resource_iri) 1160 return self._make_request(target_iri = resource_iri, 1161 on_behalf_of=on_behalf_of, 1162 method="DELETE", 1163 request_type='IRI DELETE')
1164
1165 - def delete_content_of_resource(self, edit_media_iri = None, 1166 on_behalf_of = None, 1167 dr = None):
1168 1169 """ 1170 Deleting the Content of a Resource 1171 1172 Remove all the content of a resource without removing the resource itself 1173 1174 #BETASWORD2URL 1175 See http://sword-app.svn.sourceforge.net/viewvc/sword-app/spec/trunk/SWORDProfile.html?revision=HEAD#protocoloperations_deletingcontent 1176 1177 Usage: 1178 ------ 1179 1180 Set the target for this request: 1181 -------------------------------- 1182 1183 Set `edit_media_iri` to be the Edit-Media-IRI for a given resource. 1184 1185 1186 OR 1187 1188 you can pass back the `sword2.Deposit_Receipt` object you got from a previous transaction as the `dr` parameter, 1189 and the correct IRI will automatically be chosen. 1190 """ 1191 if not edit_media_iri: 1192 if dr != None: 1193 conn_l.info("Using the deposit receipt to get the Edit-Media-IRI") 1194 edit_media_iri = dr.edit_media 1195 if edit_media_iri: 1196 conn_l.info("Deleting Resource via Edit-Media-IRI %s" % edit_media_iri) 1197 else: 1198 raise Exception("No Edit-Media-IRI was given and no suitable IRI was found in the deposit receipt.") 1199 else: 1200 raise Exception("No Edit-Media-IRI was given") 1201 else: 1202 conn_l.info("Deleting Resource via Edit-Media-IRI %s" % edit_media_iri) 1203 1204 return self.delete_resource(edit_media_iri, 1205 on_behalf_of = on_behalf_of)
1206 1207 1208 1209 1210
1211 - def delete_container(self, edit_iri = None, 1212 on_behalf_of = None, 1213 dr = None):
1214 1215 """ 1216 Deleting the Container 1217 1218 Delete the entire object on the server, effectively removing the deposit entirely. 1219 1220 #BETASWORD2URL 1221 See http://sword-app.svn.sourceforge.net/viewvc/sword-app/spec/trunk/SWORDProfile.html?revision=HEAD#protocoloperations_deleteconteiner 1222 1223 Usage: 1224 ------ 1225 1226 Set the target for this request: 1227 -------------------------------- 1228 1229 Set `edit_iri` to be the Edit-IRI for a given resource. 1230 1231 1232 OR 1233 1234 you can pass back the `sword2.Deposit_Receipt` object you got from a previous transaction as the `dr` parameter, 1235 and the correct IRI will automatically be chosen. 1236 1237 """ 1238 if not edit_iri: 1239 if dr != None: 1240 conn_l.info("Using the deposit receipt to get the Edit-IRI") 1241 edit_iri = dr.edit 1242 if edit_iri: 1243 conn_l.info("Deleting Container via Edit-IRI %s" % edit_iri) 1244 else: 1245 raise Exception("No Edit-IRI was given and no suitable IRI was found in the deposit receipt.") 1246 else: 1247 raise Exception("No Edit-IRI was given") 1248 else: 1249 conn_l.info("Deleting Container via Edit-IRI %s" % edit_iri) 1250 1251 return self.delete_resource(edit_iri, 1252 on_behalf_of = on_behalf_of)
1253
1254 - def complete_deposit(self, 1255 se_iri = None, 1256 on_behalf_of=None, 1257 dr = None):
1258 """ 1259 Completing a Previously Incomplete Deposit 1260 1261 Use this method to indicate to a server that a deposit which was 'in progress' is now complete. In other words, complete a deposit 1262 which had the 'In-Progress' flag set to True. 1263 1264 #BETASWORD2URL 1265 http://sword-app.svn.sourceforge.net/viewvc/sword-app/spec/trunk/SWORDProfile.html?revision=HEAD#continueddeposit_complete 1266 1267 Usage: 1268 ------ 1269 1270 Set the target for this request: 1271 -------------------------------- 1272 1273 Set `se_iri` to be the SWORD2-Edit-IRI for a given resource. 1274 1275 1276 OR 1277 1278 you can pass back the `sword2.Deposit_Receipt` object you got from a previous transaction as the `dr` parameter, 1279 and the correct IRI will automatically be chosen. 1280 """ 1281 1282 if not se_iri: 1283 if dr != None: 1284 conn_l.info("Using the deposit receipt to get the SWORD2-Edit-IRI") 1285 se_iri = dr.se_iri 1286 if se_iri: 1287 conn_l.info("Complete deposit using the SWORD2-Edit-IRI %s" % se_iri) 1288 else: 1289 raise Exception("No SWORD2-Edit-IRI was given and no suitable IRI was found in the deposit receipt.") 1290 else: 1291 raise Exception("No SWORD2-Edit-IRI was given") 1292 else: 1293 conn_l.info("Complete deposit using the SWORD2-Edit-IRI %s" % se_iri) 1294 1295 return self._make_request(target_iri = se_iri, 1296 on_behalf_of=on_behalf_of, 1297 in_progress='false', 1298 method="POST", 1299 empty=True, 1300 request_type='SE_IRI Complete Deposit')
1301
1302 - def update_files_for_resource(self, 1303 payload, # These need to be set to upload a file 1304 filename, # According to spec, "The client MUST supply a Content-Disposition header with a filename parameter 1305 # (note that this requires the filename be expressed in ASCII)." 1306 mimetype=None, 1307 packaging=None, 1308 1309 edit_media_iri = None, 1310 1311 on_behalf_of=None, 1312 in_progress=False, 1313 metadata_relevant=False, 1314 # Pass back the deposit receipt to automatically get the right IRI to use 1315 dr = None 1316 ):
1317 """ 1318 Replacing the File Content of a Resource 1319 1320 #BETASWORD2URL 1321 See http://sword-app.svn.sourceforge.net/viewvc/sword-app/spec/trunk/SWORDProfile.html?revision=HEAD#protocoloperations_editingcontent_binary 1322 1323 The `Connection` can replace the file content of a resource, given the Edit-Media-IRI for this resource. This can be found 1324 from the `sword2.Deposit_Receipt.edit_media` attribute of a previous deposit, or directly from the deposit receipt XML response. 1325 1326 Usage: 1327 ------ 1328 1329 Set the target for this request: 1330 -------------------------------- 1331 1332 Set the `edit_media_iri` parameter to the Edit-Media-IRI. 1333 1334 OR 1335 1336 you can pass back the `sword2.Deposit_Receipt` object you got from a previous transaction as the `dr` parameter, 1337 and the correct IRI will automatically be chosen. 1338 1339 Then, add in the payload: 1340 ------------------------- 1341 1342 Set the following parameters in addition to the basic parameters (see `self.create_resource`): 1343 1344 `payload` - the payload to send. Can be either a bytestring or a File-like object that supports `payload.read()` 1345 `mimetype` - MIMEType of the payload 1346 `filename` - filename. Most SWORD2 uploads have this as being mandatory. 1347 `packaging` - the SWORD2 packaging type of the payload. 1348 eg packaging = 'http://purl.org/net/sword/package/Binary' 1349 1350 `metadata_relevant` - This should be set to `True` if the server should consider the file a potential source of metadata extraction, 1351 or `False` if the server should not attempt to extract any metadata from the deposi 1352 1353 Response: 1354 1355 A `sword2.Deposit_Receipt` object containing the deposit receipt data. If the response was blank or 1356 not a Deposit Response, then only a few attributes will be populated: 1357 1358 `code` -- HTTP code of the response 1359 `response_headers` -- `dict` of the reponse headers 1360 `content` -- (Optional) in case the response body is not empty but the response is not a Deposit Receipt 1361 1362 If exception-throwing is turned off (`error_response_raises_exceptions = False` or `self.raise_except = False`) 1363 then the response will be a `sword2.Error_Document`, but will still have the aforementioned attributes set, (code, 1364 response_headers, etc) 1365 """ 1366 if not edit_media_iri: 1367 if dr != None: 1368 conn_l.info("Using the deposit receipt to get the Edit-Media-IRI") 1369 edit_media_iri = dr.edit_media 1370 if edit_media_iri: 1371 conn_l.info("Update Resource via Edit-Media-IRI %s" % edit_media_iri) 1372 else: 1373 raise Exception("No Edit-Media-IRI was given and no suitable IRI was found in the deposit receipt.") 1374 else: 1375 raise Exception("No Edit-Media-IRI was given") 1376 else: 1377 conn_l.info("Update Resource via Edit-Media-IRI %s" % edit_media_iri) 1378 1379 return self._make_request(target_iri = edit_media_iri, 1380 payload=payload, 1381 mimetype=mimetype, 1382 filename=filename, 1383 in_progress=in_progress, 1384 packaging=packaging, 1385 on_behalf_of=on_behalf_of, 1386 method="PUT", 1387 metadata_relevant=str(metadata_relevant), 1388 request_type='EM_IRI PUT')
1389
1390 - def update_metadata_for_resource(self, metadata_entry, # required 1391 edit_iri = None, 1392 in_progress=False, 1393 on_behalf_of=None, 1394 dr = None 1395 ):
1396 """ 1397 Replacing the Metadata of a Resource 1398 1399 #BETASWORD2URL 1400 See http://sword-app.svn.sourceforge.net/viewvc/sword-app/spec/trunk/SWORDProfile.html?revision=HEAD#protocoloperations_editingcontent_metadata 1401 1402 Replace the metadata of a resource as identified by its Edit-IRI. 1403 1404 Note, from the specification: "The client can only be sure that the server will support this process when using the default format supported by SWORD: Qualified Dublin Core XML embedded directly in the atom:entry. Other metadata formats MAY be supported by a particular server, but this is not covered by the SWORD profile" 1405 1406 Usage: 1407 ------ 1408 1409 Set the target for this request: 1410 -------------------------------- 1411 1412 Set the `edit_iri` parameter to the Edit-IRI. 1413 1414 OR 1415 1416 you can pass back the `sword2.Deposit_Receipt` object you got from a previous transaction as the `dr` parameter, 1417 and the correct IRI will automatically be chosen. 1418 1419 Then, add in the metadata: 1420 -------------------------- 1421 1422 Set the following in addition to the basic parameters: 1423 1424 `metadata_entry` - An instance of `sword2.Entry`, set with the metadata required. 1425 1426 for example, to replace the metadata for a given: 1427 # conn = `sword2.Connection`, edit_iri = Edit-IRI 1428 1429 >>> from sword2 import Entry 1430 >>> entry = Entry(title = "My new deposit", 1431 ... id = "new:id", # atom:id 1432 ... dcterms_abstract = "My Thesis", 1433 ... dcterms_author = "Ben", 1434 ... dcterms_issued = "2010") 1435 1436 >>> conn.update_metadata_for_resource(edit_iri = edit_iri, 1437 ... metadata_entry = entry) 1438 1439 1440 Response: 1441 1442 A `sword2.Deposit_Receipt` object containing the deposit receipt data. If the response was blank or 1443 not a Deposit Response, then only a few attributes will be populated: 1444 1445 `code` -- HTTP code of the response 1446 `response_headers` -- `dict` of the reponse headers 1447 `content` -- (Optional) in case the response body is not empty but the response is not a Deposit Receipt 1448 1449 If exception-throwing is turned off (`error_response_raises_exceptions = False` or `self.raise_except = False`) 1450 then the response will be a `sword2.Error_Document`, but will still have the aforementioned attributes set, (code, 1451 response_headers, etc) 1452 """ 1453 if not edit_iri: 1454 if dr != None: 1455 conn_l.info("Using the deposit receipt to get the Edit-IRI") 1456 edit_iri = dr.edit 1457 if edit_iri: 1458 conn_l.info("Update Resource via Edit-IRI %s" % edit_iri) 1459 else: 1460 raise Exception("No Edit-IRI was given and no suitable IRI was found in the deposit receipt.") 1461 else: 1462 raise Exception("No Edit-IRI was given") 1463 else: 1464 conn_l.info("Update Resource via Edit-IRI %s" % edit_iri) 1465 1466 return self._make_request(target_iri = edit_iri, 1467 metadata_entry=metadata_entry, 1468 on_behalf_of=on_behalf_of, 1469 in_progress=in_progress, 1470 method="PUT", 1471 request_type='Edit_IRI PUT')
1472
1473 - def update_metadata_and_files_for_resource(self, metadata_entry, # required 1474 payload, # These need to be set to upload a file 1475 filename, # According to spec, "The client MUST supply a Content-Disposition header with a filename parameter 1476 # (note that this requires the filename be expressed in ASCII)." 1477 mimetype=None, 1478 packaging=None, 1479 1480 edit_iri = None, 1481 1482 metadata_relevant=False, 1483 in_progress=False, 1484 on_behalf_of=None, 1485 dr = None 1486 ):
1487 """ 1488 Replacing the Metadata and Files of a Resource 1489 1490 #BETASWORD2URL 1491 See http://sword-app.svn.sourceforge.net/viewvc/sword-app/spec/trunk/SWORDProfile.html?revision=HEAD#protocoloperations_editingcontent_multipart 1492 1493 Replace the metadata and files of a resource as identified by its Edit-IRI. 1494 1495 Usage: 1496 ------ 1497 1498 Set the target for this request: 1499 -------------------------------- 1500 1501 Set the `edit_iri` parameter to the Edit-IRI. 1502 1503 OR 1504 1505 you can pass back the `sword2.Deposit_Receipt` object you got from a previous transaction as the `dr` parameter, 1506 and the correct IRI will automatically be chosen. 1507 1508 Then, add in the file and metadata information: 1509 ----------------------------------------------- 1510 Set the following in addition to the basic parameters: 1511 1512 File information: 1513 1514 `payload` - the payload to send. Can be either a bytestring or a File-like object that supports `payload.read()` 1515 `mimetype` - MIMEType of the payload 1516 `filename` - filename. Most SWORD2 uploads have this as being mandatory. 1517 `packaging` - the SWORD2 packaging type of the payload. 1518 eg packaging = 'http://purl.org/net/sword/package/Binary' 1519 1520 `metadata_relevant` - This should be set to `True` if the server should consider the file a potential source of metadata extraction, 1521 or `False` if the server should not attempt to extract any metadata from the deposi 1522 1523 Metadata information: 1524 1525 `metadata_entry` - An instance of `sword2.Entry`, set with the metadata required. 1526 1527 for example, to create a metadata entry 1528 >>> from sword2 import Entry 1529 >>> entry = Entry(title = "My new deposit", 1530 ... id = "new:id", # atom:id 1531 ... dcterms_abstract = "My Thesis", 1532 ... dcterms_author = "Ben", 1533 ... dcterms_issued = "2010") 1534 1535 Response: 1536 1537 A `sword2.Deposit_Receipt` object containing the deposit receipt data. If the response was blank or 1538 not a Deposit Response, then only a few attributes will be populated: 1539 1540 `code` -- HTTP code of the response 1541 `response_headers` -- `dict` of the reponse headers 1542 `content` -- (Optional) in case the response body is not empty but the response is not a Deposit Receipt 1543 1544 If exception-throwing is turned off (`error_response_raises_exceptions = False` or `self.raise_except = False`) 1545 then the response will be a `sword2.Error_Document`, but will still have the aforementioned attributes set, (code, 1546 response_headers, etc) 1547 """ 1548 if not edit_iri: 1549 if dr != None: 1550 conn_l.info("Using the deposit receipt to get the Edit-IRI") 1551 edit_iri = dr.edit 1552 if edit_iri: 1553 conn_l.info("Update Resource via Edit-IRI %s" % edit_iri) 1554 else: 1555 raise Exception("No Edit-IRI was given and no suitable IRI was found in the deposit receipt.") 1556 else: 1557 raise Exception("No Edit-IRI was given") 1558 else: 1559 conn_l.info("Update Resource via Edit-IRI %s" % edit_iri) 1560 1561 return self._make_request(target_iri = edit_iri, 1562 metadata_entry=metadata_entry, 1563 payload=payload, 1564 mimetype=mimetype, 1565 filename=filename, 1566 packaging=packaging, 1567 on_behalf_of=on_behalf_of, 1568 in_progress=in_progress, 1569 metadata_relevant=str(metadata_relevant), 1570 method="PUT", 1571 request_type='Edit_IRI PUT')
1572 1573
1574 - def get_atom_sword_statement(self, sword_statement_iri):
1575 """ 1576 Getting the Sword Statement. 1577 1578 IN PROGRESS - USE AT OWN RISK.... see `sword2.Sword_Statement`. 1579 """ 1580 # get the statement first 1581 conn_l.debug("Trying to GET the ATOM Sword Statement at %s." % sword_statement_iri) 1582 response = self.get_resource(sword_statement_iri, headers = {'Accept':'application/atom+xml;type=feed'}) 1583 if response.code == 200: 1584 #try: 1585 if True: 1586 conn_l.debug("Attempting to parse the response as a ATOM Sword Statement") 1587 s = Sword_Statement(response.content) 1588 conn_l.debug("Parsed SWORD2 Statement, returning") 1589 return s
1590 #except Exception, e: 1591 # # Any error here is to do with the parsing 1592 # return response.content 1593
1594 - def get_resource(self, content_iri = None, 1595 packaging=None, 1596 on_behalf_of=None, 1597 headers = {}, 1598 dr = None):
1599 """ 1600 Retrieving the content 1601 1602 Get the file or package from the SWORD2 server. 1603 1604 From the specification: 1605 "The Deposit Receipt contains two IRIs which can be used to retrieve content from the server: Cont-IRI and EM-IRI. These are provided in the atom:content@src element and the atom:link@rel="edit-media" elements respectively. Their only functional difference is that the client MUST NOT carry out any HTTP operations other than GET on the Cont-IRI, while all operations are permitted on the EM-IRI. It is acceptable, but not required, that both IRIs to be the same, and in this section we refer only to the EM-IRI but in all cases it can be substituted for the Cont-IRI." 1606 1607 #BETASWORD2URL 1608 See http://sword-app.svn.sourceforge.net/viewvc/sword-app/spec/trunk/SWORDProfile.html?revision=HEAD#protocoloperations_retrievingcontent 1609 1610 Usage: 1611 ------ 1612 1613 Set the target for this request: 1614 -------------------------------- 1615 1616 Set `content_iri` to be the Content-IRI for a given resource (or to the IRI of any resource you wish to HTTP GET) 1617 1618 1619 OR 1620 1621 you can pass back the `sword2.Deposit_Receipt` object you got from a previous transaction as the `dr` parameter, 1622 and the correct IRI will automatically be chosen. 1623 1624 Response: 1625 1626 A `ContentWrapper` - 1627 `ContentWrapper.response_headers` -- response headers 1628 `ContentWrapper.content` -- body of response from server (the file or package) 1629 `ContentWrapper.code` -- status code ('200' on success.) 1630 1631 """ 1632 1633 if not content_iri: 1634 if dr != None: 1635 conn_l.info("Using the deposit receipt to get the SWORD2-Edit-IRI") 1636 content_iri = dr.cont_iri 1637 if content_iri: 1638 conn_l.info("Getting the resource at Content-IRI %s" % content_iri) 1639 else: 1640 raise Exception("No Content-IRI was given and no suitable IRI was found in the deposit receipt.") 1641 else: 1642 raise Exception("No Content-IRI was given") 1643 else: 1644 conn_l.info("Getting the resource at Content-IRI %s" % content_iri) 1645 1646 # 406 - PackagingFormatNotAvailable 1647 if self.honour_receipts and packaging: 1648 # Make sure that the packaging format is available from the deposit receipt, if loaded 1649 conn_l.debug("Checking that the packaging format '%s' is available." % content_iri) 1650 conn_l.debug("Cached Cont-IRI Receipts: %s" % self.cont_iris.keys()) 1651 if content_iri in self.cont_iris.keys(): 1652 if not (packaging in self.cont_iris[content_iri].packaging): 1653 conn_l.error("Desired packaging format '%' not available from the server, according to the deposit receipt. Change the client parameter 'honour_receipts' to False to avoid this check.") 1654 return self._return_error_or_exception(PackagingFormatNotAvailable, {}, "") 1655 if on_behalf_of: 1656 headers['On-Behalf-Of'] = self.on_behalf_of 1657 elif self.on_behalf_of: 1658 headers['On-Behalf-Of'] = self.on_behalf_of 1659 if packaging: 1660 headers['Accept-Packaging'] = packaging 1661 1662 self._t.start("IRI GET resource") 1663 if packaging: 1664 conn_l.info("IRI GET resource '%s' with Accept-Packaging:%s" % (content_iri, packaging)) 1665 else: 1666 conn_l.info("IRI GET resource '%s'" % content_iri) 1667 resp, content = self.h.request(content_iri, "GET", headers=headers) 1668 _, took_time = self._t.time_since_start("IRI GET resource") 1669 if self.history: 1670 self.history.log('Cont_IRI GET resource', 1671 sd_iri = self.sd_iri, 1672 content_iri = content_iri, 1673 packaging = packaging, 1674 on_behalf_of = self.on_behalf_of, 1675 response = resp, 1676 headers = headers, 1677 process_duration = took_time) 1678 conn_l.info("Server response: %s" % resp['status']) 1679 conn_l.debug(resp) 1680 if resp['status'] == '200': 1681 conn_l.debug("Cont_IRI GET resource successful - got %s bytes from %s" % (len(content), content_iri)) 1682 class ContentWrapper(object): 1683 def __init__(self, resp, content): 1684 self.response_headers = dict(resp) 1685 self.content = content 1686 self.code = resp.status
1687 return ContentWrapper(resp, content) 1688 elif resp['status'] == '408': # Unavailable packaging format 1689 conn_l.error("Desired packaging format '%' not available from the server.") 1690 return self._return_error_or_exception(PackagingFormatNotAvailable, resp, content) 1691 else: 1692 return self._handle_error_response(resp, content) 1693