annotate sword2-libraries-pyinstaller-compatible/sword2/connection.py @ 22:d1752c7031e4 timeouts tip

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