You can subscribe to this list here.
| 2008 |
Jan
|
Feb
|
Mar
|
Apr
|
May
|
Jun
|
Jul
|
Aug
|
Sep
|
Oct
|
Nov
(11) |
Dec
(6) |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 2009 |
Jan
(2) |
Feb
(64) |
Mar
(4) |
Apr
(2) |
May
(10) |
Jun
(26) |
Jul
|
Aug
|
Sep
(1) |
Oct
|
Nov
(1) |
Dec
|
| 2010 |
Jan
(1) |
Feb
|
Mar
|
Apr
|
May
|
Jun
|
Jul
|
Aug
|
Sep
|
Oct
|
Nov
|
Dec
|
| 2011 |
Jan
(14) |
Feb
(7) |
Mar
(7) |
Apr
(37) |
May
(59) |
Jun
(4) |
Jul
(16) |
Aug
(3) |
Sep
(9) |
Oct
(1) |
Nov
|
Dec
(25) |
| 2012 |
Jan
(68) |
Feb
(5) |
Mar
(28) |
Apr
(12) |
May
(2) |
Jun
|
Jul
|
Aug
|
Sep
|
Oct
|
Nov
|
Dec
|
|
From: SVN c. m. f. t. SWORD-A. p. <swo...@li...> - 2012-01-22 21:22:41
|
Revision: 460
http://sword-app.svn.sourceforge.net/sword-app/?rev=460&view=rev
Author: richard-jones
Date: 2012-01-22 21:22:35 +0000 (Sun, 22 Jan 2012)
Log Message:
-----------
improve logging and import Namespaces object in the __init__ script
Modified Paths:
--------------
sss/branches/sss-2/sss/__init__.py
sss/branches/sss-2/sss/core.py
Modified: sss/branches/sss-2/sss/__init__.py
===================================================================
--- sss/branches/sss-2/sss/__init__.py 2012-01-22 18:04:24 UTC (rev 459)
+++ sss/branches/sss-2/sss/__init__.py 2012-01-22 21:22:35 UTC (rev 460)
@@ -2,7 +2,7 @@
from core import Auth, AuthException, Authenticator, DeleteRequest, DeleteResponse, DepositRequest, DepositResponse, EntryDocument, SDCollection, SWORDRequest, ServiceDocument, Statement, SwordError, SwordServer, WebUI
from config import Configuration, SSS_CONFIG_FILE
-from spec import Errors, HttpHeaders
+from spec import Errors, HttpHeaders, Namespaces
import ingesters_disseminators
import negotiator
Modified: sss/branches/sss-2/sss/core.py
===================================================================
--- sss/branches/sss-2/sss/core.py 2012-01-22 18:04:24 UTC (rev 459)
+++ sss/branches/sss-2/sss/core.py 2012-01-22 21:22:35 UTC (rev 460)
@@ -923,15 +923,15 @@
# now check that all those uris tie up:
if describes_uri != aggregation_uri:
- ssslog.info("Validation of Ore Statement failed; ore:describes URI does not match Aggregation URI: " +
- str(describes_uri) + " != " + str(aggregation_uri))
+ ssslog.info("Validation of RDF as valid ReM failed; ore:describes URI does not match Aggregation URI: " +
+ str(describes_uri) + " != " + str(aggregation_uri) + " (this is non fatal, don't panic)")
valid = False
if rem_uri not in is_described_by_uris:
- ssslog.info("Validation of Ore Statement failed; Resource Map URI does not match one of ore:isDescribedBy URIs: " +
- str(rem_uri) + " not in " + str(is_described_by_uris))
+ ssslog.info("Validation of RDF as valid ReM failed; Resource Map URI does not match one of ore:isDescribedBy URIs: " +
+ str(rem_uri) + " not in " + str(is_described_by_uris) + " (this is non fatal, don't panic)")
valid = False
- ssslog.info("Statement validation; was it a success? " + str(valid))
+ ssslog.info("Was supplied RDF a ReM? " + str(valid))
return valid
def _get_aggregation_element(self, rdf):
@@ -953,6 +953,8 @@
Get an lxml Element object back representing this statement
"""
+ ssslog.debug("Merging with supplied RDF string: " + existing_rdf_as_string)
+
# first parse in the existing rdf if necessary
rdf = None
aggregation = None
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: SVN c. m. f. t. SWORD-A. p. <swo...@li...> - 2012-01-22 18:04:30
|
Revision: 459
http://sword-app.svn.sourceforge.net/sword-app/?rev=459&view=rev
Author: richard-jones
Date: 2012-01-22 18:04:24 +0000 (Sun, 22 Jan 2012)
Log Message:
-----------
allow entry document object to be retrieved from deposit request
Modified Paths:
--------------
sss/branches/sss-2/sss/core.py
Modified: sss/branches/sss-2/sss/core.py
===================================================================
--- sss/branches/sss-2/sss/core.py 2012-01-22 17:09:24 UTC (rev 458)
+++ sss/branches/sss-2/sss/core.py 2012-01-22 18:04:24 UTC (rev 459)
@@ -187,6 +187,8 @@
if self.parsed:
for element in self.dom.getchildren():
+ if isinstance(element, etree._Comment):
+ continue
field = self._canonical_tag(element.tag)
ssslog.debug("Attempting to intepret field: '%s'" % field)
if field == "atom_id" and element.text is not None:
@@ -649,8 +651,15 @@
self.content_type = "application/octet-stream"
self.content = None
self.atom = None
+ self.entry_document = None
self.filename = "unnamed.file"
self.too_large = False
+
+ def get_entry_document(self):
+ if self.entry_document is None:
+ if self.atom is not None:
+ self.entry_document = EntryDocument(xml_source=self.atom)
+ return self.entry_document
class DepositResponse(object):
"""
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: SVN c. m. f. t. SWORD-A. p. <swo...@li...> - 2012-01-22 17:09:31
|
Revision: 458
http://sword-app.svn.sourceforge.net/sword-app/?rev=458&view=rev
Author: richard-jones
Date: 2012-01-22 17:09:24 +0000 (Sun, 22 Jan 2012)
Log Message:
-----------
add EntryDocument load from xml string, to aid in server-side interpretation of incoming atom entries. Also add tests for this object
Modified Paths:
--------------
sss/branches/sss-2/sss/core.py
sss/branches/sss-2/sss/spec.py
Added Paths:
-----------
sss/branches/sss-2/tests/functional/test_entry.py
Modified: sss/branches/sss-2/sss/core.py
===================================================================
--- sss/branches/sss-2/sss/core.py 2012-01-20 10:54:06 UTC (rev 457)
+++ sss/branches/sss-2/sss/core.py 2012-01-22 17:09:24 UTC (rev 458)
@@ -1,4 +1,4 @@
-import web, os, base64
+import web, os, base64, uuid
from lxml import etree
from datetime import datetime
from spec import Namespaces, HttpHeaders, Errors
@@ -145,12 +145,14 @@
def __init__(self, atom_id=None, alternate_uri=None, content_uri=None, edit_uri=None, se_uri=None, em_uris=[],
packaging=[], state_uris=[], updated=None, dc_metadata={},
generator=("http://www.swordapp.org/sss", __version__),
- verbose_description=None, treatment=None, original_deposit_uri=None, derived_resource_uris=[], nsmap=None):
+ verbose_description=None, treatment=None, original_deposit_uri=None, derived_resource_uris=[], nsmap=None,
+ xml_source=None):
self.ns = Namespaces()
self.drmap = {None: self.ns.ATOM_NS, "sword" : self.ns.SWORD_NS, "dcterms" : self.ns.DC_NS}
if nsmap is not None:
self.drmap = nsmap
+ self.other_metadata = {}
self.dc_metadata = dc_metadata
self.atom_id = atom_id if atom_id is not None else "urn:uuid:" + str(uuid.uuid4())
self.updated = updated if updated is not None else datetime.now()
@@ -166,7 +168,116 @@
self.state_uris = state_uris
self.original_deposit_uri = original_deposit_uri
self.derived_resource_uris = derived_resource_uris
+
+ # we may have been passed the xml_source argument, in which case we want
+ # to load from a string
+ self.links = {}
+ self.dom = None
+ self.parsed = False
+ if xml_source is not None:
+ self._load(xml_source)
+ def _load(self, xml_source):
+ try:
+ self.dom = etree.fromstring(xml_source)
+ self.parsed = True
+ except Exception as e:
+ ssslog.error("Was not able to parse the Entry Document as XML.")
+ raise e
+
+ if self.parsed:
+ for element in self.dom.getchildren():
+ field = self._canonical_tag(element.tag)
+ ssslog.debug("Attempting to intepret field: '%s'" % field)
+ if field == "atom_id" and element.text is not None:
+ self.atom_id = element.text.strip()
+ elif field == "atom_updated" and element.text is not None:
+ try:
+ self.updated = datetime.strptime(element.text.strip(), "%Y-%m-%dT%H:%M:%SZ")
+ except Exception as e:
+ ssslog.info("Unable to parse updated time: " + element.text.strip())
+ elif field == "atom_link":
+ self._handle_link(element)
+ elif field == "atom_content":
+ self._handle_content(element)
+ elif field == "atom_generator":
+ uri = element.attrib.get("uri")
+ version = element.attrib.get("version")
+ self.generator = (uri, version)
+ elif field == "sword_packaging" and element.text is not None:
+ self.packaging.append(element.text.strip())
+ elif field == "sword_verboseDescription" and element.text is not None:
+ self.verbose_description = element.text.strip()
+ elif field == "sword_treatment" and element.text is not None:
+ self.treatment = element.text.strip()
+ elif field.startswith("dcterms_") and element.text is not None:
+ field = field[8:] # get rid of the dcterms_ prefix
+ if self.dc_metadata.has_key(field):
+ self.dc_metadata[field].append(element.text.strip())
+ else:
+ self.dc_metadata[field] = [element.text.strip()]
+ else:
+ if element.text is not None: # handle empty elements
+ if self.other_metadata.has_key(field):
+ self.other_metadata[field].append(element.text.strip())
+ else:
+ self.other_metadata[field] = [element.text.strip()]
+
+
+ def _canonical_tag(self, tag):
+ ns, field = tag.rsplit("}", 1)
+ prefix = self.ns.prefix.get(ns[1:], ns[1:])
+ return prefix + "_" + field
+
+ def _handle_link(self, e):
+ """Method that handles the intepreting of <atom:link> element information and placing it into the anticipated attributes."""
+ # MUST have rel
+ rel = e.attrib.get('rel', None)
+ if rel:
+ if rel == "edit":
+ self.edit_uri = e.attrib.get('href', None)
+ elif rel == "edit-media":
+ # FIXME: need to better handle uris with types
+ self.em_uris.append((e.attrib.get('href', None), e.attrib.get("type", None)))
+ # only put the edit-media iri in the convenience attribute if
+ # there is no 'type'
+ #if not ('type' in e.attrib.keys()):
+ # self.edit_media = e.attrib.get('href', None)
+ #elif e.attrib['type'] == "application/atom+xml;type=feed":
+ # self.edit_media_feed = e.attrib.get('href', None)
+ elif rel == "http://purl.org/net/sword/terms/add":
+ self.se_uri = e.attrib.get('href', None)
+ elif rel == "alternate":
+ self.alternate_uri = e.attrib.get('href', None)
+ elif rel == "http://purl.org/net/sword/terms/statement":
+ self.state_uris.append((e.attrib.get('href', None), e.attrib.get("type", None)))
+ elif rel == "http://purl.org/net/sword/terms/originalDeposit":
+ self.original_deposit_uri = e.attrib.get("href", None)
+ elif rel == "http://purl.org/net/sword/terms/derivedResource":
+ # FIXME: doesn't handle types
+ self.derived_resource_uris.append(e.attrib.get("href", None))
+
+ # Put all links into .links attribute, with all element attribs
+ attribs = {}
+ for k,v in e.attrib.iteritems():
+ if k != "rel":
+ attribs[k] = v
+ if self.links.has_key(rel):
+ self.links[rel].append(attribs)
+ else:
+ self.links[rel] = [attribs]
+
+
+ def _handle_content(self, e):
+ """Method to intepret the <atom:content> elements."""
+ # eg <content type="application/zip" src="http://swordapp.org/cont-IRI/43/my_deposit"/>
+ if e.attrib.has_key("src"):
+ src = e.attrib['src']
+ info = dict(e.attrib).copy()
+ del info['src']
+ #self.content[src] = info # FIXME: this class isn't generic enough yet to do this
+ self.content_uri = src
+
def serialise(self):
# the main entry document room
entry = etree.Element(self.ns.ATOM + "entry", nsmap=self.drmap)
@@ -202,6 +313,12 @@
# now embed all the metadata as foreign markup
for field in self.dc_metadata.keys():
+ # ensure it's a list (common mistake)
+ if not isinstance(self.dc_metadata[field], list):
+ self.dc_metadata[field] = [self.dc_metadata[field]]
+ if field.startswith("dcterms_"):
+ # a potentially common mistake?
+ field = field[8:]
for v in self.dc_metadata[field]:
fdc = etree.SubElement(entry, self.ns.DC + field)
fdc.text = v
@@ -265,6 +382,7 @@
od.set("rel", "http://purl.org/net/sword/terms/originalDeposit")
od.set("href", self.original_deposit_uri)
+ # FIXME: doesn't handle types
# Derived Resources
if self.derived_resource_uris is not None:
for uri in self.derived_resource_uris:
Modified: sss/branches/sss-2/sss/spec.py
===================================================================
--- sss/branches/sss-2/sss/spec.py 2012-01-20 10:54:06 UTC (rev 457)
+++ sss/branches/sss-2/sss/spec.py 2012-01-22 17:09:24 UTC (rev 458)
@@ -4,6 +4,7 @@
from sss_logging import logging
ssslog = logging.getLogger(__name__)
+# FIXME: this is a poorly constructed object
class Namespaces(object):
"""
This class encapsulates all the namespace declarations that we will need
@@ -12,31 +13,49 @@
# AtomPub namespace and lxml format
self.APP_NS = "http://www.w3.org/2007/app"
self.APP = "{%s}" % self.APP_NS
+ self.APP_PREFIX = "app"
# Atom namespace and lxml format
self.ATOM_NS = "http://www.w3.org/2005/Atom"
self.ATOM = "{%s}" % self.ATOM_NS
+ self.ATOM_PREFIX = "atom"
# SWORD namespace and lxml format
self.SWORD_NS = "http://purl.org/net/sword/terms/"
self.SWORD = "{%s}" % self.SWORD_NS
+ self.SWORD_PREFIX = "sword"
# Dublin Core namespace and lxml format
self.DC_NS = "http://purl.org/dc/terms/"
self.DC = "{%s}" % self.DC_NS
+ self.DC_PREFIX = "dcterms"
# RDF namespace and lxml format
self.RDF_NS = "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
self.RDF = "{%s}" % self.RDF_NS
+ self.RDF_PREFIX = "rdf"
# ORE namespace and lxml format
self.ORE_NS = "http://www.openarchives.org/ore/terms/"
self.ORE = "{%s}" % self.ORE_NS
+ self.ORE_PREFIX = "ore"
# ORE ATOM
self.ORE_ATOM_NS = "http://www.openarchives.org/ore/atom/"
self.ORE_ATOM = "{%s}" % self.ORE_ATOM_NS
+ self.ORE_ATOM_PREFIX = "oreatom"
+ # lookup dictionary
+ self.prefix = {
+ self.APP_NS : self.APP_PREFIX,
+ self.ATOM_NS : self.ATOM_PREFIX,
+ self.SWORD_NS : self.SWORD_PREFIX,
+ self.DC_NS : self.DC_PREFIX,
+ self.RDF_NS : self.RDF_PREFIX,
+ self.ORE_NS : self.ORE_PREFIX,
+ self.ORE_ATOM_NS : self.ORE_ATOM_PREFIX
+ }
+
class Errors(object):
content = "http://purl.org/net/sword/error/ErrorContent"
checksum_mismatch = "http://purl.org/net/sword/error/ErrorChecksumMismatch"
Added: sss/branches/sss-2/tests/functional/test_entry.py
===================================================================
--- sss/branches/sss-2/tests/functional/test_entry.py (rev 0)
+++ sss/branches/sss-2/tests/functional/test_entry.py 2012-01-22 17:09:24 UTC (rev 458)
@@ -0,0 +1,301 @@
+from . import TestController
+
+from datetime import datetime
+from lxml import etree
+
+from sss import EntryDocument
+
+ATOM = "{http://www.w3.org/2005/Atom}"
+SWORD = "{http://purl.org/net/sword/terms/}"
+DC = "{http://purl.org/dc/terms/}"
+
+class TestConnection(TestController):
+ def test_01_blank_init(self):
+ e = EntryDocument()
+
+ # check the meaningful default values
+ assert e.atom_id is not None
+ assert e.updated is not None
+
+ g, v = e.generator
+ assert g == "http://www.swordapp.org/sss"
+ assert v is not None
+
+ # check a couple of other things for emptyness
+ assert e.other_metadata is not None
+ assert len(e.other_metadata) == 0
+ assert e.dc_metadata is not None
+ assert len(e.dc_metadata) == 0
+
+ def test_02_args_init(self):
+
+ e = EntryDocument(
+ atom_id = "1234",
+ alternate_uri = "http://alternate/",
+ content_uri = "http://content/",
+ edit_uri = "http://edit/",
+ se_uri = "http://sword-edit/",
+ em_uris = [
+ ("http://edit-media/1", "application/atom+xml"),
+ ("http://edit-media/2", "application/zip")
+ ],
+ packaging = ["http://packaging/"],
+ state_uris = [
+ ("http://state/1", "application/atom+xml"),
+ ("http://state/2", "application/rdf+xml")
+ ],
+ updated = datetime.now(),
+ dc_metadata = {
+ "identifier" : "http://identifier/",
+ "rights" : "you can do this!",
+ "replaces" : "something else"
+ },
+ verbose_description = "Verbose Description",
+ treatment = "Treatment",
+ original_deposit_uri = "http://original/",
+ derived_resource_uris = ["http://derived/1", "http://derived/2"]
+ )
+
+ assert e.atom_id == "1234"
+ assert e.alternate_uri == "http://alternate/"
+ assert e.content_uri == "http://content/"
+ assert e.edit_uri == "http://edit/"
+ assert e.se_uri == "http://sword-edit/"
+ assert len(e.em_uris) == 2
+ assert "http://edit-media/1" in e.em_uris[0]
+ assert "application/zip" in e.em_uris[1]
+ assert len(e.packaging) == 1
+ assert "http://packaging/" in e.packaging
+ assert len(e.state_uris) == 2
+ assert "application/atom+xml" in e.state_uris[0]
+ assert "http://state/2" in e.state_uris[1]
+ assert e.updated is not None
+ assert len(e.dc_metadata) == 3
+ assert "identifier" in e.dc_metadata.keys()
+ assert e.verbose_description == "Verbose Description"
+ assert e.treatment == "Treatment"
+ assert e.original_deposit_uri == "http://original/"
+ assert len(e.derived_resource_uris) == 2
+
+ def test_03_serialise(self):
+ e = EntryDocument(
+ atom_id = "1234",
+ alternate_uri = "http://alternate/",
+ content_uri = "http://content/",
+ edit_uri = "http://edit/",
+ se_uri = "http://sword-edit/",
+ em_uris = [
+ ("http://edit-media/1", "application/atom+xml"),
+ ("http://edit-media/2", "application/zip")
+ ],
+ packaging = ["http://packaging/"],
+ state_uris = [
+ ("http://state/1", "application/atom+xml"),
+ ("http://state/2", "application/rdf+xml")
+ ],
+ updated = datetime.now(),
+ dc_metadata = {
+ "identifier" : "http://identifier/",
+ "rights" : "you can do this!",
+ "replaces" : "something else"
+ },
+ verbose_description = "Verbose Description",
+ treatment = "Treatment",
+ original_deposit_uri = "http://original/",
+ derived_resource_uris = ["http://derived/1", "http://derived/2"]
+ )
+
+ s = e.serialise()
+
+ # does it parse as xml
+ xml = etree.fromstring(s)
+
+ # now check the xml document and see if it ties in with the above
+ # attributes
+ has_id = False
+ has_alt = False
+ has_cont = False
+ has_edit = False
+ has_se = False
+ has_em_atom = False
+ has_em_zip = False
+ has_packaging = False
+ has_state_atom = False
+ has_state_rdf = False
+ has_updated = False
+ dc_count = 0
+ has_vd = False
+ has_treatment = False
+ has_od = False
+ dr_count = 0
+ for element in xml.getchildren():
+ if element.tag == ATOM + "id":
+ assert element.text.strip() == "1234"
+ has_id = True
+ elif element.tag == ATOM + "content":
+ src = element.attrib.get("src")
+ assert src == "http://content/"
+ has_cont = True
+ elif element.tag == SWORD + "packaging":
+ assert element.text.strip() == "http://packaging/"
+ has_packaging = True
+ elif element.tag == ATOM + "updated":
+ has_updated = True
+ elif element.tag == DC + "identifier":
+ assert element.text.strip() == "http://identifier/"
+ dc_count += 1
+ elif element.tag == DC + "rights":
+ assert element.text.strip() == "you can do this!"
+ dc_count += 1
+ elif element.tag == DC + "replaces":
+ assert element.text.strip() == "something else"
+ dc_count += 1
+ elif element.tag == SWORD + "verboseDescription":
+ assert element.text.strip() == "Verbose Description"
+ has_vd = True
+ elif element.tag == SWORD + "treatment":
+ assert element.text.strip() == "Treatment"
+ has_treatment = True
+ elif element.tag == ATOM + "link":
+ rel = element.attrib.get("rel")
+ if rel == "alternate":
+ assert element.attrib.get("href") == "http://alternate/"
+ has_alt = True
+ elif rel == "edit":
+ assert element.attrib.get("href") == "http://edit/"
+ has_edit = True
+ elif rel == "http://purl.org/net/sword/terms/add":
+ assert element.attrib.get("href") == "http://sword-edit/"
+ has_se= True
+ elif rel == "edit-media":
+ t = element.attrib.get("type")
+ if t == "application/atom+xml":
+ assert element.attrib.get("href") == "http://edit-media/1"
+ has_em_atom = True
+ elif t == "application/zip":
+ assert element.attrib.get("href") == "http://edit-media/2"
+ has_em_zip = True
+ else:
+ assert False
+ elif rel == "http://purl.org/net/sword/terms/statement":
+ t = element.attrib.get("type")
+ if t == "application/atom+xml":
+ assert element.attrib.get("href") == "http://state/1"
+ has_state_atom = True
+ elif t == "application/rdf+xml":
+ assert element.attrib.get("href") == "http://state/2"
+ has_state_rdf = True
+ else:
+ assert False
+ elif rel == "http://purl.org/net/sword/terms/originalDeposit":
+ assert element.attrib.get("href") == "http://original/"
+ has_od = True
+ elif rel == "http://purl.org/net/sword/terms/derivedResource":
+ assert element.attrib.get("href") in ["http://derived/1", "http://derived/2"]
+ dr_count += 1
+
+ # now check all our switches were appropriately thrown
+ assert has_id
+ assert has_alt
+ assert has_cont
+ assert has_edit
+ assert has_se
+ assert has_em_atom
+ assert has_em_zip
+ assert has_packaging
+ assert has_state_atom
+ assert has_state_rdf
+ assert has_updated
+ assert dc_count == 3
+ assert has_vd
+ assert has_treatment
+ assert has_od
+ assert dr_count == 2
+
+ def test_04_round_trip_load(self):
+ e1 = EntryDocument(
+ atom_id = "1234",
+ alternate_uri = "http://alternate/",
+ content_uri = "http://content/",
+ edit_uri = "http://edit/",
+ se_uri = "http://sword-edit/",
+ em_uris = [
+ ("http://edit-media/1", "application/atom+xml"),
+ ("http://edit-media/2", "application/zip")
+ ],
+ packaging = ["http://packaging/"],
+ state_uris = [
+ ("http://state/1", "application/atom+xml"),
+ ("http://state/2", "application/rdf+xml")
+ ],
+ updated = datetime.now(),
+ dc_metadata = {
+ "identifier" : "http://identifier/",
+ "rights" : "you can do this!",
+ "replaces" : "something else"
+ },
+ verbose_description = "Verbose Description",
+ treatment = "Treatment",
+ original_deposit_uri = "http://original/",
+ derived_resource_uris = ["http://derived/1", "http://derived/2"]
+ )
+
+ s = e1.serialise()
+
+ # now create a new entry from the output
+ e = EntryDocument(xml_source=s)
+
+ assert e.atom_id == "1234"
+ assert e.alternate_uri == "http://alternate/"
+ assert e.content_uri == "http://content/"
+ assert e.edit_uri == "http://edit/"
+ assert e.se_uri == "http://sword-edit/"
+ assert len(e.em_uris) == 2
+ assert "http://edit-media/1" in e.em_uris[0]
+ assert "application/zip" in e.em_uris[1]
+ assert len(e.packaging) == 1
+ assert "http://packaging/" in e.packaging
+ assert len(e.state_uris) == 2
+ assert "application/atom+xml" in e.state_uris[0]
+ assert "http://state/2" in e.state_uris[1]
+ assert e.updated is not None
+ assert len(e.dc_metadata) == 3
+ assert "identifier" in e.dc_metadata.keys()
+ assert e.verbose_description == "Verbose Description"
+ assert e.treatment == "Treatment"
+ assert e.original_deposit_uri == "http://original/"
+ assert len(e.derived_resource_uris) == 2
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: SVN c. m. f. t. SWORD-A. p. <swo...@li...> - 2012-01-20 10:54:15
|
Revision: 457
http://sword-app.svn.sourceforge.net/sword-app/?rev=457&view=rev
Author: richard-jones
Date: 2012-01-20 10:54:06 +0000 (Fri, 20 Jan 2012)
Log Message:
-----------
general enhancements to the Statement, in reading from files and for outputting elements only when necessary
Modified Paths:
--------------
sss/branches/sss-2/sss/core.py
sss/branches/sss-2/sss/repository.py
Modified: sss/branches/sss-2/sss/core.py
===================================================================
--- sss/branches/sss-2/sss/core.py 2012-01-19 17:23:47 UTC (rev 456)
+++ sss/branches/sss-2/sss/core.py 2012-01-20 10:54:06 UTC (rev 457)
@@ -601,7 +601,7 @@
"""
Class representing the Statement; a description of the object as it appears on the server
"""
- def __init__(self, aggregation_uri=None, rem_uri=None, original_deposits=[], aggregates=[], states=[]):
+ def __init__(self, rdf_file=None, aggregation_uri=None, rem_uri=None, original_deposits=[], aggregates=[], states=[]):
"""
The statement has 4 important properties:
- aggregation_uri - The URI of the aggregation in ORE terms
@@ -621,6 +621,10 @@
self.smap = {"rdf" : self.ns.RDF_NS, "ore" : self.ns.ORE_NS, "sword" : self.ns.SWORD_NS}
self.asmap = {"oreatom" : self.ns.ORE_ATOM_NS, "atom" : self.ns.ATOM_NS, "rdf" : self.ns.RDF_NS, "ore" : self.ns.ORE_NS, "sword" : self.ns.SWORD_NS}
self.fmap = {"atom" : self.ns.ATOM_NS, "sword" : self.ns.SWORD_NS}
+
+ self.rdf = None
+ if rdf_file is not None:
+ self.load_from_rdf(rdf_file)
def __str__(self):
return str(self.aggregation_uri) + ", " + str(self.rem_uri) + ", " + str(self.original_deposits)
@@ -646,11 +650,16 @@
if agg not in self.aggregates:
self.aggregates.append(agg)
- def load(self, filepath):
+ def load_from_rdf(self, filepath_or_filehandle):
"""
Populate this statement object from the XML serialised statement to be found at the specified filepath
"""
- f = open(filepath, "r")
+ f = None
+ if hasattr(filepath_or_filehandle, "read"):
+ f = filepath_or_filehandle
+ else:
+ f = open(filepath_or_filehandle, "r")
+
rdf = etree.fromstring(f.read())
aggs = []
@@ -700,6 +709,8 @@
for agg in aggs:
if agg not in ods:
self.aggregates.append(agg)
+
+ self.rdf = rdf
def serialise_rdf(self, existing_rdf_as_string=None):
"""
@@ -823,7 +834,7 @@
rdf = etree.fromstring(existing_rdf_as_string)
is_rem = self._is_rem(rdf)
if is_rem:
- aggregation = self._get_aggregation_element()
+ aggregation = self._get_aggregation_element(rdf)
else:
aggregation = self._get_description_element(rdf, self.aggregation_uri)
else:
@@ -897,19 +908,25 @@
# Build the Description elements for the original deposits, with their sword:depositedOn and sword:packaging
# relations
for (uri, datestamp, format_uri, by, obo) in self.original_deposits:
+ if uri is None:
+ continue
+
desc = etree.SubElement(rdf, self.ns.RDF + "Description", nsmap=self.smap)
desc.set(self.ns.RDF + "about", uri)
- format = etree.SubElement(desc, self.ns.SWORD + "packaging", nsmap=self.smap)
- format.set(self.ns.RDF + "resource", format_uri)
+ if format_uri is not None:
+ format = etree.SubElement(desc, self.ns.SWORD + "packaging", nsmap=self.smap)
+ format.set(self.ns.RDF + "resource", format_uri)
- deposited = etree.SubElement(desc, self.ns.SWORD + "depositedOn", nsmap=self.smap)
- deposited.set(self.ns.RDF + "datatype", "http://www.w3.org/2001/XMLSchema#dateTime")
- deposited.text = datestamp.strftime("%Y-%m-%dT%H:%M:%SZ")
+ if datestamp is not None:
+ deposited = etree.SubElement(desc, self.ns.SWORD + "depositedOn", nsmap=self.smap)
+ deposited.set(self.ns.RDF + "datatype", "http://www.w3.org/2001/XMLSchema#dateTime")
+ deposited.text = datestamp.strftime("%Y-%m-%dT%H:%M:%SZ")
- deposit_by = etree.SubElement(desc, self.ns.SWORD + "depositedBy", nsmap=self.smap)
- deposit_by.set(self.ns.RDF + "datatype", "http://www.w3.org/2001/XMLSchema#string")
- deposit_by.text = by
+ if by is not None:
+ deposit_by = etree.SubElement(desc, self.ns.SWORD + "depositedBy", nsmap=self.smap)
+ deposit_by.set(self.ns.RDF + "datatype", "http://www.w3.org/2001/XMLSchema#string")
+ deposit_by.text = by
if obo is not None:
deposit_obo = etree.SubElement(desc, self.ns.SWORD + "depositedOnBehalfOf", nsmap=self.smap)
Modified: sss/branches/sss-2/sss/repository.py
===================================================================
--- sss/branches/sss-2/sss/repository.py 2012-01-19 17:23:47 UTC (rev 456)
+++ sss/branches/sss-2/sss/repository.py 2012-01-20 10:54:06 UTC (rev 457)
@@ -1139,8 +1139,7 @@
Returns a Statement object fully populated to represent this object
"""
sfile = os.path.join(self.configuration.store_dir, collection, id, "sss_statement.xml")
- s = Statement()
- s.load(sfile)
+ s = Statement(rdf_file=sfile)
return s
def list_content(self, collection, id, exclude=[]):
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: SVN c. m. f. t. SWORD-A. p. <swo...@li...> - 2012-01-19 17:23:58
|
Revision: 456
http://sword-app.svn.sourceforge.net/sword-app/?rev=456&view=rev
Author: richard-jones
Date: 2012-01-19 17:23:47 +0000 (Thu, 19 Jan 2012)
Log Message:
-----------
fix namespace labelling issue
Modified Paths:
--------------
sss/branches/sss-2/sss/core.py
Modified: sss/branches/sss-2/sss/core.py
===================================================================
--- sss/branches/sss-2/sss/core.py 2012-01-19 17:09:25 UTC (rev 455)
+++ sss/branches/sss-2/sss/core.py 2012-01-19 17:23:47 UTC (rev 456)
@@ -834,23 +834,23 @@
# map
if not is_rem:
# in the RDF root create a Description for the REM which ore:describes the Aggregation
- description1 = etree.SubElement(rdf, self.ns.RDF + "Description")
+ description1 = etree.SubElement(rdf, self.ns.RDF + "Description", nsmap=self.smap)
description1.set(self.ns.RDF + "about", self.rem_uri)
- describes = etree.SubElement(description1, self.ns.ORE + "describes")
+ describes = etree.SubElement(description1, self.ns.ORE + "describes", nsmap=self.smap)
describes.set(self.ns.RDF + "resource", self.aggregation_uri)
if aggregation is not None and not is_rem:
# there is already an rdf:Description for the element, but it hasn't
# been properly linked to the ReM yet
- idb = etree.SubElement(aggregation, self.ns.ORE + "isDescribedBy")
+ idb = etree.SubElement(aggregation, self.ns.ORE + "isDescribedBy", nsmap=self.smap)
idb.set(self.ns.RDF + "resource", self.rem_uri)
if aggregation is None:
# CREATE THE AGGREGATION
# in the RDF root create a Description for the Aggregation which is ore:isDescribedBy the REM
- aggregation = etree.SubElement(rdf, self.ns.RDF + "Description")
+ aggregation = etree.SubElement(rdf, self.ns.RDF + "Description", nsmap=self.smap)
aggregation.set(self.ns.RDF + "about", self.aggregation_uri)
- idb = etree.SubElement(aggregation, self.ns.ORE + "isDescribedBy")
+ idb = etree.SubElement(aggregation, self.ns.ORE + "isDescribedBy", nsmap=self.smap)
idb.set(self.ns.RDF + "resource", self.rem_uri)
# we want to create an ORE resource map, and also add on the sword specific bits for the original deposits and the state
@@ -864,7 +864,7 @@
for uri in self.aggregates:
if uri in existing_a:
continue
- aggregates = etree.SubElement(aggregation, self.ns.ORE + "aggregates")
+ aggregates = etree.SubElement(aggregation, self.ns.ORE + "aggregates", nsmap=self.smap)
aggregates.set(self.ns.RDF + "resource", uri)
existing_a.append(uri) # remember that we've added this aggregation, in case there are duplicates in original_deposits
@@ -876,43 +876,43 @@
for (uri, datestamp, format, by, obo) in self.original_deposits:
# standard ORE aggregates statement
if uri not in existing_a:
- aggregates = etree.SubElement(aggregation, self.ns.ORE + "aggregates")
+ aggregates = etree.SubElement(aggregation, self.ns.ORE + "aggregates", nsmap=self.smap)
aggregates.set(self.ns.RDF + "resource", uri)
# assert that this is an original package
if uri not in existing_od:
- original = etree.SubElement(aggregation, self.ns.SWORD + "originalDeposit")
+ original = etree.SubElement(aggregation, self.ns.SWORD + "originalDeposit", nsmap=self.smap)
original.set(self.ns.RDF + "resource", uri)
# now do the state information
for state_uri, state_description in self.states:
- state = etree.SubElement(aggregation, self.ns.SWORD + "state")
+ state = etree.SubElement(aggregation, self.ns.SWORD + "state", nsmap=self.smap)
state.set(self.ns.RDF + "resource", state_uri)
- sdesc = etree.SubElement(rdf, self.ns.RDF + "Description")
+ sdesc = etree.SubElement(rdf, self.ns.RDF + "Description", nsmap=self.smap)
sdesc.set(self.ns.RDF + "about", state_uri)
- meaning = etree.SubElement(sdesc, self.ns.SWORD + "stateDescription")
+ meaning = etree.SubElement(sdesc, self.ns.SWORD + "stateDescription", nsmap=self.smap)
meaning.text = state_description
# Build the Description elements for the original deposits, with their sword:depositedOn and sword:packaging
# relations
for (uri, datestamp, format_uri, by, obo) in self.original_deposits:
- desc = etree.SubElement(rdf, self.ns.RDF + "Description")
+ desc = etree.SubElement(rdf, self.ns.RDF + "Description", nsmap=self.smap)
desc.set(self.ns.RDF + "about", uri)
- format = etree.SubElement(desc, self.ns.SWORD + "packaging")
+ format = etree.SubElement(desc, self.ns.SWORD + "packaging", nsmap=self.smap)
format.set(self.ns.RDF + "resource", format_uri)
- deposited = etree.SubElement(desc, self.ns.SWORD + "depositedOn")
+ deposited = etree.SubElement(desc, self.ns.SWORD + "depositedOn", nsmap=self.smap)
deposited.set(self.ns.RDF + "datatype", "http://www.w3.org/2001/XMLSchema#dateTime")
deposited.text = datestamp.strftime("%Y-%m-%dT%H:%M:%SZ")
- deposit_by = etree.SubElement(desc, self.ns.SWORD + "depositedBy")
+ deposit_by = etree.SubElement(desc, self.ns.SWORD + "depositedBy", nsmap=self.smap)
deposit_by.set(self.ns.RDF + "datatype", "http://www.w3.org/2001/XMLSchema#string")
deposit_by.text = by
if obo is not None:
- deposit_obo = etree.SubElement(desc, self.ns.SWORD + "depositedOnBehalfOf")
+ deposit_obo = etree.SubElement(desc, self.ns.SWORD + "depositedOnBehalfOf", nsmap=self.smap)
deposit_obo.set(self.ns.RDF + "datatype", "http://www.w3.org/2001/XMLSchema#string")
deposit_obo.text = obo
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: SVN c. m. f. t. SWORD-A. p. <swo...@li...> - 2012-01-19 17:09:36
|
Revision: 455
http://sword-app.svn.sourceforge.net/sword-app/?rev=455&view=rev
Author: richard-jones
Date: 2012-01-19 17:09:25 +0000 (Thu, 19 Jan 2012)
Log Message:
-----------
add tests for statement code
Added Paths:
-----------
sss/branches/sss-2/tests/functional/test_statement.py
Added: sss/branches/sss-2/tests/functional/test_statement.py
===================================================================
--- sss/branches/sss-2/tests/functional/test_statement.py (rev 0)
+++ sss/branches/sss-2/tests/functional/test_statement.py 2012-01-19 17:09:25 UTC (rev 455)
@@ -0,0 +1,317 @@
+import os
+from lxml import etree
+from datetime import datetime
+
+from . import TestController
+
+from sss import Statement
+
+RDF = "{http://www.w3.org/1999/02/22-rdf-syntax-ns#}"
+ORE = "{http://www.openarchives.org/ore/terms/}"
+SWORD = "{http://purl.org/net/sword/terms/}"
+OX = "{http://vocab.ox.ac.uk/dataset/schema#}"
+DC = "{http://purl.org/dc/terms/}"
+
+RDF_DOC = """<?xml version="1.0" encoding="UTF-8"?>
+<rdf:RDF
+ xmlns:dcterms="http://purl.org/dc/terms/"
+ xmlns:ore="http://www.openarchives.org/ore/terms/"
+ xmlns:oxds="http://vocab.ox.ac.uk/dataset/schema#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+>
+ <rdf:Description rdf:about="http://192.168.23.133/asdfasd/datasets/mydataset6">
+ <oxds:currentVersion>6</oxds:currentVersion>
+ <oxds:embargoedUntil>2081-12-30T13:20:10.864975</oxds:embargoedUntil>
+ <oxds:isEmbargoed>True</oxds:isEmbargoed>
+ <dcterms:identifier>mydataset6</dcterms:identifier>
+ <dcterms:modified>2012-01-17 18:33:10.228565</dcterms:modified>
+ <dcterms:rights rdf:resource="http://ora.ouls.ox.ac.uk/objects/uuid%3A1d00eebb-8fed-46ad-8e38-45dbdb4b224c"/>
+ <rdf:type rdf:resource="http://vocab.ox.ac.uk/dataset/schema#DataSet"/>
+ <dcterms:publisher>Bodleian Libraries, University of Oxford</dcterms:publisher>
+ <dcterms:created>2012-01-17 13:20:10.865789</dcterms:created>
+ <dcterms:mediator></dcterms:mediator>
+ <ore:aggregates rdf:resource="http://192.168.23.133/asdfasd/datasets/mydataset6/example.zip"/>
+ </rdf:Description>
+</rdf:RDF>
+"""
+
+class TestEntry(TestController):
+ def test_01_init_statement(self):
+ n = datetime.now()
+ ods = [
+ ("http://od1/", n, "http://package/", "sword", "obo"),
+ ("http://od2/", n, "http://package/", "bob", None)
+ ]
+ s = Statement(aggregation_uri="http://aggregation/", rem_uri="http://rem/",
+ original_deposits=ods,
+ aggregates=["http://od1/", "http://od2/", "http://agg1/", "http://agg2/"],
+ states=[("http://state/", "everything is groovy")])
+
+ # now check that the item is correctly initialised
+ assert s.aggregation_uri == "http://aggregation/"
+ assert s.rem_uri == "http://rem/"
+ assert len(s.original_deposits) == 2
+ assert "http://od1/" in s.original_deposits[0]
+ assert "http://od2/" in s.original_deposits[1]
+ assert "http://od1/" in s.aggregates
+ assert "http://od2/" in s.aggregates
+ assert "http://agg1/" in s.aggregates
+ assert "http://agg2/" in s.aggregates
+ assert len(s.aggregates) == 4
+ assert len(s.states) == 1
+
+ state_uri, state_description = s.states[0]
+ assert state_uri == "http://state/"
+ assert state_description == "everything is groovy"
+
+ def test_02_modify_statement(self):
+ n = datetime.now()
+ ods = [
+ ("http://od1/", n, "http://package/", "sword", "obo"),
+ ("http://od2/", n, "http://package/", "bob", None)
+ ]
+ s = Statement(aggregation_uri="http://aggregation/", rem_uri="http://rem/",
+ original_deposits=ods,
+ aggregates=["http://od1/", "http://od2/", "http://agg1/", "http://agg2/"],
+ states=[("http://state/", "everything is groovy")])
+
+ s.set_state("http://new/state/", "still good, though")
+
+ assert len(s.states) == 1
+ state_uri, state_description = s.states[0]
+ assert state_uri == "http://new/state/"
+ assert state_description == "still good, though"
+
+ s.add_state("http://another/state", "also, this")
+ assert len(s.states) == 2
+
+ def test_03_rdf_serialise(self):
+ n = datetime.now()
+ ods = [
+ ("http://od1/", n, "http://package/", "sword", "obo"),
+ ("http://od2/", n, "http://package/", "bob", None)
+ ]
+ od_uris = ["http://od1/", "http://od2/"]
+ s = Statement(aggregation_uri="http://aggregation/", rem_uri="http://rem/",
+ original_deposits=ods,
+ aggregates=["http://od1/", "http://od2/", "http://agg1/", "http://agg2/"],
+ states=[("http://state/", "everything is groovy")])
+
+ rdf_string = s.serialise_rdf()
+
+ # first try the round trip
+ rdf = etree.fromstring(rdf_string)
+
+ # here are some counters/switches which will help us test that everything
+ # is good within the statement
+ descriptions = 0
+ states = 0
+ state_descriptions = 0
+ original_deposits = 0
+ aggregated_resources = 0
+ packaging = 0
+ dep_on = 0
+ dep_by = 0
+ dep_obo = 0
+
+ has_rem_description = False
+ has_agg_description = False
+
+ # now go through the rdf and check that everything is as expected
+ for desc in rdf.findall(RDF + "Description"):
+ descriptions += 1
+ about = desc.get(RDF + "about")
+ for element in desc.getchildren():
+ if element.tag == ORE + "describes":
+ resource = element.get(RDF + "resource")
+ assert about == s.rem_uri
+ assert resource == s.aggregation_uri
+ has_rem_description = True
+ if element.tag == ORE + "isDescribedBy":
+ resource = element.get(RDF + "resource")
+ assert about == s.aggregation_uri
+ assert resource == s.rem_uri
+ has_agg_description = True
+ if element.tag == ORE + "aggregates":
+ resource = element.get(RDF + "resource")
+ assert resource in s.aggregates or resource in od_uris
+ aggregated_resources += 1
+ if element.tag == SWORD + "originalDeposit":
+ resource = element.get(RDF + "resource")
+ assert resource in od_uris
+ original_deposits += 1
+ if element.tag == SWORD + "state":
+ resource = element.get(RDF + "resource")
+ assert resource == "http://state/"
+ states += 1
+ if element.tag == SWORD + "stateDescription":
+ assert element.text.strip() == "everything is groovy"
+ assert about == "http://state/"
+ state_descriptions += 1
+ if element.tag == SWORD + "packaging":
+ resource = element.get(RDF + "resource")
+ assert resource == "http://package/"
+ assert about in od_uris
+ packaging += 1
+ if element.tag == SWORD + "depositedOn":
+ assert about in od_uris
+ dep_on += 1
+ if element.tag == SWORD + "depositedBy":
+ assert element.text in ["sword", "bob"]
+ assert about in od_uris
+ dep_by += 1
+ if element.tag == SWORD + "depositedOnBehalfOf":
+ assert element.text == "obo"
+ assert about in od_uris
+ dep_obo += 1
+
+ # now check that our counters/switches were flipped appropriately
+ assert descriptions == 5
+ assert states == 1
+ assert state_descriptions == 1
+ assert original_deposits == 2
+ assert aggregated_resources == 4
+ assert packaging == 2
+ assert dep_on == 2
+ assert dep_by == 2
+ assert dep_obo == 1
+ assert has_rem_description
+ assert has_agg_description
+
+ def test_04_rdf_aggregation_uri_exists(self):
+ n = datetime.now()
+ ods = [
+ ("http://od1/", n, "http://package/", "sword", "obo"),
+ ("http://192.168.23.133/asdfasd/datasets/mydataset6/example.zip", n, "http://package/", "bob", None)
+ ]
+ od_uris = ["http://od1/", "http://192.168.23.133/asdfasd/datasets/mydataset6/example.zip"]
+ s = Statement(aggregation_uri="http://192.168.23.133/asdfasd/datasets/mydataset6", rem_uri="http://rem/",
+ original_deposits=ods,
+ aggregates=["http://od1/", "http://192.168.23.133/asdfasd/datasets/mydataset6/example.zip", "http://agg1/", "http://agg2/"],
+ states=[("http://state/", "everything is groovy")])
+
+ rdf_string = s.serialise_rdf(RDF_DOC)
+
+ # first try the round trip
+ rdf = etree.fromstring(rdf_string)
+
+ # here are some counters/switches which will help us test that everything
+ # is good within the statement
+ descriptions = 0
+ states = 0
+ state_descriptions = 0
+ original_deposits = 0
+ aggregated_resources = 0
+ packaging = 0
+ dep_on = 0
+ dep_by = 0
+ dep_obo = 0
+
+ has_rem_description = False
+ has_agg_description = False
+ ox_tag = False
+ dc_tag = False
+ rdf_tag = False
+
+ # now go through the rdf and check that everything is as expected
+ for desc in rdf.findall(RDF + "Description"):
+ descriptions += 1
+ about = desc.get(RDF + "about")
+ for element in desc.getchildren():
+ # we expect all of the same things to be true as in the previous
+ # test
+ if element.tag == ORE + "describes":
+ resource = element.get(RDF + "resource")
+ assert about == s.rem_uri
+ assert resource == s.aggregation_uri
+ has_rem_description = True
+ if element.tag == ORE + "isDescribedBy":
+ resource = element.get(RDF + "resource")
+ assert about == s.aggregation_uri
+ assert resource == s.rem_uri
+ has_agg_description = True
+ if element.tag == ORE + "aggregates":
+ resource = element.get(RDF + "resource")
+ assert resource in s.aggregates or resource in od_uris
+ aggregated_resources += 1
+ if element.tag == SWORD + "originalDeposit":
+ resource = element.get(RDF + "resource")
+ assert resource in od_uris
+ original_deposits += 1
+ if element.tag == SWORD + "state":
+ resource = element.get(RDF + "resource")
+ assert resource == "http://state/"
+ states += 1
+ if element.tag == SWORD + "stateDescription":
+ assert element.text.strip() == "everything is groovy"
+ assert about == "http://state/"
+ state_descriptions += 1
+ if element.tag == SWORD + "packaging":
+ resource = element.get(RDF + "resource")
+ assert resource == "http://package/"
+ assert about in od_uris
+ packaging += 1
+ if element.tag == SWORD + "depositedOn":
+ assert about in od_uris
+ dep_on += 1
+ if element.tag == SWORD + "depositedBy":
+ assert element.text in ["sword", "bob"]
+ assert about in od_uris
+ dep_by += 1
+ if element.tag == SWORD + "depositedOnBehalfOf":
+ assert element.text == "obo"
+ assert about in od_uris
+ dep_obo += 1
+
+ # and we must verify that we didn't overwrite anything in the
+ # passed in RDF document (don't check everything, but let's pick
+ # one thing from each namespace)
+ if element.tag == OX + "currentVersion":
+ assert element.text == "6"
+ ox_tag = True
+ if element.tag == DC + "identifier":
+ assert element.text == "mydataset6"
+ dc_tag = True
+ if element.tag == RDF + "type":
+ resource = element.get(RDF + "resource")
+ assert resource == "http://vocab.ox.ac.uk/dataset/schema#DataSet"
+ rdf_tag = True
+
+ # now check that our counters/switches were flipped appropriately
+ assert descriptions == 5
+ assert states == 1
+ assert state_descriptions == 1
+ assert original_deposits == 2
+ assert aggregated_resources == 4
+ assert packaging == 2
+ assert dep_on == 2
+ assert dep_by == 2
+ assert dep_obo == 1
+ assert has_rem_description
+ assert has_agg_description
+
+ assert ox_tag
+ assert dc_tag
+ assert rdf_tag
+
+ def test_05_rdf_no_aggregation(self):
+ # FIXME: implement a test in which there is an existing RDF document
+ # but it does not contain an rdf:Description@about which contains
+ # the URI of an aggregation. It should, therefore, simply add the
+ # Statement to the existing rdf document
+ pass
+
+ def test_06_rdf_full_rem(self):
+ # FIXME: impelement a test in which the existing RDF document is a full
+ # ReM, and therefore the Statement is just additive.
+ pass
+
+
+
+
+
+
+
+
+
+
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: SVN c. m. f. t. SWORD-A. p. <swo...@li...> - 2012-01-19 15:50:40
|
Revision: 454
http://sword-app.svn.sourceforge.net/sword-app/?rev=454&view=rev
Author: richard-jones
Date: 2012-01-19 15:50:28 +0000 (Thu, 19 Jan 2012)
Log Message:
-----------
improve Statement handling and serialisation, and now support passing in an existing RDF document to add the statement information to
Modified Paths:
--------------
sss/branches/sss-2/sss/core.py
sss/branches/sss-2/sss/pylons_sword_controller.py
sss/branches/sss-2/sss/repository.py
sss/branches/sss-2/sss/webpy.py
Modified: sss/branches/sss-2/sss/core.py
===================================================================
--- sss/branches/sss-2/sss/core.py 2012-01-17 15:05:22 UTC (rev 453)
+++ sss/branches/sss-2/sss/core.py 2012-01-19 15:50:28 UTC (rev 454)
@@ -596,44 +596,12 @@
self.error_code = None
self.error = None
self.receipt = None
-
-# Operational SWORD Classes
-#############################################################################
-# Classes which carry out the grunt work of the SSS
-
-class SWORDSpec(object):
- """
- Class which attempts to represent the specification itself. Instead of being operational like the SWORDServer
- class, it attempts to just be able to interpret the supplied http headers and content bodies and turn them into
- the entities with which SWORD works. The jury is out, in my mind, whether this class is a useful separation, but
- for what it's worth, here it is ...
- """
- def __init__(self, config):
-
- self.config = config
-
- # FIXME: this is a webpy thing ...
- # The HTTP headers that are part of the specification (from a web.py perspective - don't be fooled, these
- # aren't the real HTTP header names - see the spec)
- self.sword_headers = [
- "HTTP_ON_BEHALF_OF", "HTTP_PACKAGING", "HTTP_IN_PROGRESS", "HTTP_METADATA_RELEVANT",
- "HTTP_CONTENT_MD5", "HTTP_SLUG", "HTTP_ACCEPT_PACKAGING"
- ]
-
- self.error_content_uri = "http://purl.org/net/sword/error/ErrorContent"
- self.error_checksum_mismatch_uri = "http://purl.org/net/sword/error/ErrorChecksumMismatch"
- self.error_bad_request_uri = "http://purl.org/net/sword/error/ErrorBadRequest"
- self.error_target_owner_unknown_uri = "http://purl.org/net/sword/error/TargetOwnerUnknown"
- self.error_mediation_not_allowed_uri = "http://purl.org/net/sword/error/MediationNotAllowed"
- self.error_method_not_allowed_uri = "http://purl.org/net/sword/error/MethodNotAllowed"
- self.error_max_upload_size_exceeded = "http://purl.org/net/sword/error/MaxUploadSizeExceeded"
-
-
+
class Statement(object):
"""
Class representing the Statement; a description of the object as it appears on the server
"""
- def __init__(self):
+ def __init__(self, aggregation_uri=None, rem_uri=None, original_deposits=[], aggregates=[], states=[]):
"""
The statement has 4 important properties:
- aggregation_uri - The URI of the aggregation in ORE terms
@@ -642,22 +610,12 @@
- in_progress - Is the submission in progress (boolean)
- aggregates - the non-original deposit files associated with the item
"""
- self.aggregation_uri = None
- self.rem_uri = None
- self.original_deposits = []
- self.aggregates = []
- self.in_progress = False
-
- # URIs to use for the two supported states in SSS
- self.in_progress_uri = "http://purl.org/net/sword/state/in-progress"
- self.archived_uri = "http://purl.org/net/sword/state/archived"
-
- # the descriptions to associated with the two supported states in SSS
- self.states = {
- self.in_progress_uri : "The work is currently in progress, and has not passed to a reviewer",
- self.archived_uri : "The work has passed through review and is now in the archive"
- }
-
+ self.aggregation_uri = aggregation_uri
+ self.rem_uri = rem_uri
+ self.original_deposits = original_deposits
+ self.aggregates = aggregates
+ self.states = states
+
# Namespace map for XML serialisation
self.ns = Namespaces()
self.smap = {"rdf" : self.ns.RDF_NS, "ore" : self.ns.ORE_NS, "sword" : self.ns.SWORD_NS}
@@ -666,7 +624,13 @@
def __str__(self):
return str(self.aggregation_uri) + ", " + str(self.rem_uri) + ", " + str(self.original_deposits)
+
+ def add_state(self, state, state_description):
+ self.states.append((state, state_description))
+ def set_state(self, state, state_description):
+ self.states = [(state, state_description)]
+
def original_deposit(self, uri, deposit_time, packaging_format, by, obo):
"""
Add an original deposit to the statement
@@ -691,6 +655,7 @@
aggs = []
ods = []
+ states = []
for desc in rdf.getchildren():
packaging = None
depositedOn = None
@@ -707,7 +672,7 @@
self.rem_uri = about
if element.tag == self.ns.SWORD + "state":
state = element.get(self.ns.RDF + "resource")
- self.in_progress = state == "http://purl.org/net/sword/state/in-progress"
+ states.append(state)
if element.tag == self.ns.SWORD + "packaging":
packaging = element.get(self.ns.RDF + "resource")
if element.tag == self.ns.SWORD + "depositedOn":
@@ -721,17 +686,26 @@
ods.append(about)
self.original_deposit(about, depositedOn, packaging, deposit_by, deposit_obo)
+ # now find the state descriptions
+ for desc in rdf.getchildren():
+ about = desc.get(self.ns.RDF + "about")
+ if about in states:
+ for element in desc.getchildren():
+ if element.tag == self.ns.SWORD + "stateDescription":
+ state_description = element.text
+ self.add_state(about, state_description)
+
# sort out the ordinary aggregations from the original deposits
self.aggregates = []
for agg in aggs:
if agg not in ods:
self.aggregates.append(agg)
- def serialise(self):
+ def serialise_rdf(self, existing_rdf_as_string=None):
"""
Serialise this statement into an RDF/XML string
"""
- rdf = self.get_rdf_xml()
+ rdf = self.get_rdf_xml(existing_rdf_as_string)
return etree.tostring(rdf, pretty_print=True)
def serialise_atom(self):
@@ -742,11 +716,11 @@
feed = etree.Element(self.ns.ATOM + "feed", nsmap=self.fmap)
# create the sword:state term in the root of the feed
- state_uri = self.in_progress_uri if self.in_progress else self.archived_uri
- state = etree.SubElement(feed, self.ns.SWORD + "state")
- state.set("href", state_uri)
- meaning = etree.SubElement(state, self.ns.SWORD + "stateDescription")
- meaning.text = self.states[state_uri]
+ for state_uri, state_description in self.states:
+ state = etree.SubElement(feed, self.ns.SWORD + "state")
+ state.set("href", state_uri)
+ meaning = etree.SubElement(state, self.ns.SWORD + "stateDescription")
+ meaning.text = state_description
# now do an entry for each original deposit
for (uri, datestamp, format_uri, by, obo) in self.original_deposits:
@@ -787,47 +761,138 @@
return etree.tostring(feed, pretty_print=True)
- def get_rdf_xml(self):
+ def _is_rem(self, rdf):
+ valid = True
+
+ # does it meet the basic requirements of being a resource map, which
+ # is to have an ore:describes and and ore:isDescribedBy
+ describes_uri = None
+ rem_uri = None
+ aggregation_uri = None
+ is_described_by_uris = []
+ for desc in rdf.findall(self.ns.RDF + "Description"):
+ # look for the describes tag
+ ore_desc = desc.find(self.ns.ORE + "describes")
+ if ore_desc is not None:
+ describes_uri = ore_desc.get(self.ns.RDF + "resource")
+ rem_uri = desc.get(self.ns.RDF + "about")
+ # look for the isDescribedBy tag
+ ore_idb = desc.findall(self.ns.ORE + "isDescribedBy")
+ if len(ore_idb) > 0:
+ aggregation_uri = desc.get(self.ns.RDF + "about")
+ for idb in ore_idb:
+ is_described_by_uris.append(idb.get(self.ns.RDF + "resource"))
+
+ # now check that all those uris tie up:
+ if describes_uri != aggregation_uri:
+ ssslog.info("Validation of Ore Statement failed; ore:describes URI does not match Aggregation URI: " +
+ str(describes_uri) + " != " + str(aggregation_uri))
+ valid = False
+ if rem_uri not in is_described_by_uris:
+ ssslog.info("Validation of Ore Statement failed; Resource Map URI does not match one of ore:isDescribedBy URIs: " +
+ str(rem_uri) + " not in " + str(is_described_by_uris))
+ valid = False
+
+ ssslog.info("Statement validation; was it a success? " + str(valid))
+ return valid
+
+ def _get_aggregation_element(self, rdf):
+ for desc in rdf.findall(self.ns.RDF + "Description"):
+ ore_idb = desc.findall(self.ns.ORE + "isDescribedBy")
+ if len(ore_idb) > 0:
+ return desc
+ return None
+
+ def _get_description_element(self, rdf, uri):
+ for desc in rdf.findall(self.ns.RDF + "Description"):
+ about = desc.get(self.ns.RDF + "about")
+ if about == uri:
+ return desc
+ return None
+
+ def get_rdf_xml(self, existing_rdf_as_string=None):
"""
Get an lxml Element object back representing this statement
"""
- # we want to create an ORE resource map, and also add on the sword specific bits for the original deposits and the state
+ # first parse in the existing rdf if necessary
+ rdf = None
+ aggregation = None
+ is_rem = False
+ if existing_rdf_as_string is not None:
+ rdf = etree.fromstring(existing_rdf_as_string)
+ is_rem = self._is_rem(rdf)
+ if is_rem:
+ aggregation = self._get_aggregation_element()
+ else:
+ aggregation = self._get_description_element(rdf, self.aggregation_uri)
+ else:
+ # create the RDF root
+ rdf = etree.Element(self.ns.RDF + "RDF", nsmap=self.smap)
- # create the RDF root
- rdf = etree.Element(self.ns.RDF + "RDF", nsmap=self.smap)
+ # these operations ensure that an existing rdf document becomes a resource
+ # map
+ if not is_rem:
+ # in the RDF root create a Description for the REM which ore:describes the Aggregation
+ description1 = etree.SubElement(rdf, self.ns.RDF + "Description")
+ description1.set(self.ns.RDF + "about", self.rem_uri)
+ describes = etree.SubElement(description1, self.ns.ORE + "describes")
+ describes.set(self.ns.RDF + "resource", self.aggregation_uri)
- # in the RDF root create a Description for the REM which ore:describes the Aggregation
- description1 = etree.SubElement(rdf, self.ns.RDF + "Description")
- description1.set(self.ns.RDF + "about", self.rem_uri)
- describes = etree.SubElement(description1, self.ns.ORE + "describes")
- describes.set(self.ns.RDF + "resource", self.aggregation_uri)
+ if aggregation is not None and not is_rem:
+ # there is already an rdf:Description for the element, but it hasn't
+ # been properly linked to the ReM yet
+ idb = etree.SubElement(aggregation, self.ns.ORE + "isDescribedBy")
+ idb.set(self.ns.RDF + "resource", self.rem_uri)
- # in the RDF root create a Description for the Aggregation which is ore:isDescribedBy the REM
- description = etree.SubElement(rdf, self.ns.RDF + "Description")
- description.set(self.ns.RDF + "about", self.aggregation_uri)
- idb = etree.SubElement(description, self.ns.ORE + "isDescribedBy")
- idb.set(self.ns.RDF + "resource", self.rem_uri)
+ if aggregation is None:
+ # CREATE THE AGGREGATION
+ # in the RDF root create a Description for the Aggregation which is ore:isDescribedBy the REM
+ aggregation = etree.SubElement(rdf, self.ns.RDF + "Description")
+ aggregation.set(self.ns.RDF + "about", self.aggregation_uri)
+ idb = etree.SubElement(aggregation, self.ns.ORE + "isDescribedBy")
+ idb.set(self.ns.RDF + "resource", self.rem_uri)
+ # we want to create an ORE resource map, and also add on the sword specific bits for the original deposits and the state
+
# Create ore:aggreages for all ordinary aggregated files
+ # First build a list of all the urls which are already referred to in the existing rem
+ existing_a = []
+ existing_aggregates = aggregation.findall(self.ns.ORE + "aggregates")
+ for ea in existing_aggregates:
+ existing_a.append(ea.get(self.ns.RDF + "resource"))
for uri in self.aggregates:
- aggregates = etree.SubElement(description, self.ns.ORE + "aggregates")
+ if uri in existing_a:
+ continue
+ aggregates = etree.SubElement(aggregation, self.ns.ORE + "aggregates")
aggregates.set(self.ns.RDF + "resource", uri)
+ existing_a.append(uri) # remember that we've added this aggregation, in case there are duplicates in original_deposits
# Create ore:aggregates and sword:originalDeposit relations for the original deposits
+ existing_od = []
+ existing_ods = aggregation.findall(self.ns.SWORD + "originalDeposit")
+ for eo in existing_ods:
+ existing_od.append(eo.get(self.ns.RDF + "resource"))
for (uri, datestamp, format, by, obo) in self.original_deposits:
# standard ORE aggregates statement
- aggregates = etree.SubElement(description, self.ns.ORE + "aggregates")
- aggregates.set(self.ns.RDF + "resource", uri)
+ if uri not in existing_a:
+ aggregates = etree.SubElement(aggregation, self.ns.ORE + "aggregates")
+ aggregates.set(self.ns.RDF + "resource", uri)
# assert that this is an original package
- original = etree.SubElement(description, self.ns.SWORD + "originalDeposit")
- original.set(self.ns.RDF + "resource", uri)
+ if uri not in existing_od:
+ original = etree.SubElement(aggregation, self.ns.SWORD + "originalDeposit")
+ original.set(self.ns.RDF + "resource", uri)
# now do the state information
- state_uri = self.in_progress_uri if self.in_progress else self.archived_uri
- state = etree.SubElement(description, self.ns.SWORD + "state")
- state.set(self.ns.RDF + "resource", state_uri)
+ for state_uri, state_description in self.states:
+ state = etree.SubElement(aggregation, self.ns.SWORD + "state")
+ state.set(self.ns.RDF + "resource", state_uri)
+
+ sdesc = etree.SubElement(rdf, self.ns.RDF + "Description")
+ sdesc.set(self.ns.RDF + "about", state_uri)
+ meaning = etree.SubElement(sdesc, self.ns.SWORD + "stateDescription")
+ meaning.text = state_description
# Build the Description elements for the original deposits, with their sword:depositedOn and sword:packaging
# relations
@@ -851,12 +916,6 @@
deposit_obo.set(self.ns.RDF + "datatype", "http://www.w3.org/2001/XMLSchema#string")
deposit_obo.text = obo
- # finally do a description for the state
- sdesc = etree.SubElement(rdf, self.ns.RDF + "Description")
- sdesc.set(self.ns.RDF + "about", state_uri)
- meaning = etree.SubElement(sdesc, self.ns.SWORD + "stateDescription")
- meaning.text = self.states[state_uri]
-
return rdf
class WebUI(object):
Modified: sss/branches/sss-2/sss/pylons_sword_controller.py
===================================================================
--- sss/branches/sss-2/sss/pylons_sword_controller.py 2012-01-17 15:05:22 UTC (rev 453)
+++ sss/branches/sss-2/sss/pylons_sword_controller.py 2012-01-19 15:50:28 UTC (rev 454)
@@ -4,7 +4,7 @@
from pylons.templating import render_mako as render
import re, base64, urllib, uuid, inspect
-from core import Auth, SWORDSpec, SwordError, AuthException, DepositRequest, DeleteRequest
+from core import Auth, SwordError, AuthException, DepositRequest, DeleteRequest
from negotiator import ContentNegotiator, AcceptParameters, ContentType
from spec import Errors, HttpHeaders, ValidationException
Modified: sss/branches/sss-2/sss/repository.py
===================================================================
--- sss/branches/sss-2/sss/repository.py 2012-01-17 15:05:22 UTC (rev 453)
+++ sss/branches/sss-2/sss/repository.py 2012-01-19 15:50:28 UTC (rev 454)
@@ -1,5 +1,5 @@
import os, hashlib, uuid, urllib
-from core import Statement, DepositResponse, MediaResourceResponse, DeleteResponse, SWORDSpec, Auth, AuthException, SwordError, ServiceDocument, SDCollection, EntryDocument, Authenticator, SwordServer, WebUI
+from core import Statement, DepositResponse, MediaResourceResponse, DeleteResponse, Auth, AuthException, SwordError, ServiceDocument, SDCollection, EntryDocument, Authenticator, SwordServer, WebUI
from spec import Namespaces, Errors
from lxml import etree
from datetime import datetime
@@ -153,7 +153,17 @@
# create a URIManager for us to use
self.um = URIManager(self.configuration)
+
+ # URIs to use for the two supported states in SSS
+ self.in_progress_uri = "http://purl.org/net/sword/state/in-progress"
+ self.archived_uri = "http://purl.org/net/sword/state/archived"
+ # the descriptions to associated with the two supported states in SSS
+ self.states = {
+ self.in_progress_uri : "The work is currently in progress, and has not passed to a reviewer",
+ self.archived_uri : "The work has passed through review and is now in the archive"
+ }
+
# build the namespace maps that we will use during serialisation
# self.sdmap = {None : self.ns.APP_NS, "sword" : self.ns.SWORD_NS, "atom" : self.ns.ATOM_NS, "dcterms" : self.ns.DC_NS}
self.cmap = {None: self.ns.ATOM_NS}
@@ -314,7 +324,11 @@
# the Edit-URI
edit_uri = self.um.edit_uri(collection, id)
-
+
+ # State information
+ state_uri = self.in_progress_uri if deposit.in_progress else self.archived_uri
+ state_description = self.states[state_uri]
+
# create the initial statement
s = Statement()
s.aggregation_uri = agg_uri
@@ -323,9 +337,9 @@
obo = deposit.auth.obo if deposit.auth is not None else None
if deposit_uri is not None:
s.original_deposit(deposit_uri, datetime.now(), deposit.packaging, by, obo)
- s.in_progress = deposit.in_progress
s.aggregates = derived_resource_uris
-
+ s.add_state(state_uri, state_description)
+
# store the statement by itself
self.dao.store_statement(collection, id, s)
@@ -464,6 +478,10 @@
# the Edit-URI
edit_uri = self.um.edit_uri(collection, id)
+
+ # State information
+ state_uri = self.in_progress_uri if deposit.in_progress else self.archived_uri
+ state_description = self.states[state_uri]
# create the new statement
s = Statement()
@@ -473,7 +491,7 @@
by = deposit.auth.by if deposit.auth is not None else None
obo = deposit.auth.obo if deposit.auth is not None else None
s.original_deposit(deposit_uri, datetime.now(), deposit.packaging, by, obo)
- s.in_progress = deposit.in_progress
+ s.add_state(state_uri, state_description)
s.aggregates = derived_resource_uris
# store the statement by itself
@@ -527,11 +545,15 @@
# the Edit-URI
edit_uri = self.um.edit_uri(collection, id)
+ # State information
+ state_uri = self.in_progress_uri if delete.in_progress else self.archived_uri
+ state_description = self.states[state_uri]
+
# create the statement
s = Statement()
s.aggregation_uri = agg_uri
s.rem_uri = edit_uri
- s.in_progress = delete.in_progress
+ s.add_state(state_uri, state_description)
# store the statement by itself
self.dao.store_statement(collection, id, s)
@@ -567,9 +589,13 @@
if not self.exists(oid):
raise SwordError(status=404, empty=True)
+ # State information
+ state_uri = self.in_progress_uri if deposit.in_progress else self.archived_uri
+ state_description = self.states[state_uri]
+
# load the statement
s = self.dao.load_statement(collection, id)
- s.in_progress = deposit.in_progress
+ s.set_state(state_uri, state_description)
# store the content file if one exists, and do some processing on it
location_uri = None
@@ -675,12 +701,16 @@
if not self.exists(oid):
raise SwordError(status=404, empty=True)
+ # State information
+ state_uri = self.in_progress_uri if deposit.in_progress else self.archived_uri
+ state_description = self.states[state_uri]
+
# load the statement
s = self.dao.load_statement(collection, id)
# do the in-progress first, as some deposits will be empty, and will
# just be telling us that the client has finished working on this item
- s.in_progress = deposit.in_progress
+ s.set_state(state_uri, state_description)
# just do some useful logging
if deposit.atom is None and deposit.content is None:
@@ -1001,7 +1031,7 @@
""" Store the supplied statement document content in the object idenfied by the id in the specified collection """
# store the RDF version
sfile = os.path.join(self.configuration.store_dir, collection, id, "sss_statement.xml")
- self.save(sfile, statement.serialise())
+ self.save(sfile, statement.serialise_rdf())
# store the Atom Feed version
sfile = os.path.join(self.configuration.store_dir, collection, id, "sss_statement.atom.xml")
self.save(sfile, statement.serialise_atom())
Modified: sss/branches/sss-2/sss/webpy.py
===================================================================
--- sss/branches/sss-2/sss/webpy.py 2012-01-17 15:05:22 UTC (rev 453)
+++ sss/branches/sss-2/sss/webpy.py 2012-01-19 15:50:28 UTC (rev 454)
@@ -1,6 +1,6 @@
import web, re, base64, urllib, uuid
from web.wsgiserver import CherryPyWSGIServer
-from core import Auth, SWORDSpec, SwordError, AuthException, DepositRequest, DeleteRequest
+from core import Auth, SwordError, AuthException, DepositRequest, DeleteRequest
from negotiator import ContentNegotiator, AcceptParameters, ContentType
from spec import Errors, HttpHeaders, ValidationException
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: SVN c. m. f. t. SWORD-A. p. <swo...@li...> - 2012-01-17 15:05:33
|
Revision: 453
http://sword-app.svn.sourceforge.net/sword-app/?rev=453&view=rev
Author: richard-jones
Date: 2012-01-17 15:05:22 +0000 (Tue, 17 Jan 2012)
Log Message:
-----------
fix handling of max_upload_size; this now doesn't cause an error if it is triggered, and can be set to unlimited by not setting the config value
Modified Paths:
--------------
sss/branches/sss-2/sss/config.py
sss/branches/sss-2/sss/core.py
sss/branches/sss-2/sss/pylons_sword_controller.py
sss/branches/sss-2/sss/webpy.py
Modified: sss/branches/sss-2/sss/config.py
===================================================================
--- sss/branches/sss-2/sss/config.py 2012-01-16 16:52:59 UTC (rev 452)
+++ sss/branches/sss-2/sss/config.py 2012-01-17 15:05:22 UTC (rev 453)
@@ -63,6 +63,7 @@
"max_upload_size" : 16777216,
# used to generate errors
# "max_upload_size" : 0,
+ # Just omit the max_upload_size parameter if you don't want any limit
# list of package formats that SSS can provide when retrieving the Media Resource
"sword_disseminate_package" : [
Modified: sss/branches/sss-2/sss/core.py
===================================================================
--- sss/branches/sss-2/sss/core.py 2012-01-16 16:52:59 UTC (rev 452)
+++ sss/branches/sss-2/sss/core.py 2012-01-17 15:05:22 UTC (rev 453)
@@ -316,7 +316,7 @@
version.text = self.version
# max upload size
- if self.max_upload_size != 0:
+ if self.max_upload_size is not None:
mus = etree.SubElement(service, self.ns.SWORD + "maxUploadSize")
mus.text = str(self.max_upload_size)
Modified: sss/branches/sss-2/sss/pylons_sword_controller.py
===================================================================
--- sss/branches/sss-2/sss/pylons_sword_controller.py 2012-01-16 16:52:59 UTC (rev 452)
+++ sss/branches/sss-2/sss/pylons_sword_controller.py 2012-01-17 15:05:22 UTC (rev 453)
@@ -189,10 +189,10 @@
if d.content_length == 0:
ssslog.info("Received empty deposit request")
empty_request = True
- if d.content_length > config.max_upload_size:
+ if config.max_upload_size is not None and d.content_length > config.max_upload_size:
raise SwordError(error_uri=Errors.max_upload_size_exceeded,
- msg="Max upload size is " + config.max_upload_size +
- "; incoming content length was " + str(cl))
+ msg="Max upload size is " + str(config.max_upload_size) +
+ "; incoming content length was " + str(d.content_length))
# FIXME: this method does NOT support multipart
# find out if this is a multipart or not
Modified: sss/branches/sss-2/sss/webpy.py
===================================================================
--- sss/branches/sss-2/sss/webpy.py 2012-01-16 16:52:59 UTC (rev 452)
+++ sss/branches/sss-2/sss/webpy.py 2012-01-17 15:05:22 UTC (rev 453)
@@ -209,8 +209,8 @@
empty_request = True
if d.content_length > config.max_upload_size:
raise SwordError(error_uri=Errors.max_upload_size_exceeded,
- msg="Max upload size is " + config.max_upload_size +
- "; incoming content length was " + str(cl))
+ msg="Max upload size is " + str(config.max_upload_size) +
+ "; incoming content length was " + str(d.content_length))
# find out if this is a multipart or not
is_multipart = False
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: SVN c. m. f. t. SWORD-A. p. <swo...@li...> - 2012-01-16 16:53:05
|
Revision: 452
http://sword-app.svn.sourceforge.net/sword-app/?rev=452&view=rev
Author: richard-jones
Date: 2012-01-16 16:52:59 +0000 (Mon, 16 Jan 2012)
Log Message:
-----------
fix empty elements in service document
Modified Paths:
--------------
sss/branches/sss-2/sss/core.py
sss/branches/sss-2/sss/webpy.py
Modified: sss/branches/sss-2/sss/core.py
===================================================================
--- sss/branches/sss-2/sss/core.py 2012-01-15 23:37:11 UTC (rev 451)
+++ sss/branches/sss-2/sss/core.py 2012-01-16 16:52:59 UTC (rev 452)
@@ -347,20 +347,23 @@
mraccepts.set("alternate", "multipart-related")
# SWORD collection policy
- collectionPolicy = etree.SubElement(collection, self.ns.SWORD + "collectionPolicy")
- collectionPolicy.text = col.collection_policy
+ if col.collection_policy is not None:
+ collectionPolicy = etree.SubElement(collection, self.ns.SWORD + "collectionPolicy")
+ collectionPolicy.text = col.collection_policy
# Collection abstract
- abstract = etree.SubElement(collection, self.ns.DC + "abstract")
- abstract.text = col.description
+ if col.description is not None:
+ abstract = etree.SubElement(collection, self.ns.DC + "abstract")
+ abstract.text = col.description
# support for mediation
mediation = etree.SubElement(collection, self.ns.SWORD + "mediation")
mediation.text = "true" if col.mediation else "false"
# treatment
- treatment = etree.SubElement(collection, self.ns.SWORD + "treatment")
- treatment.text = col.treatment
+ if col.treatment is not None:
+ treatment = etree.SubElement(collection, self.ns.SWORD + "treatment")
+ treatment.text = col.treatment
# SWORD packaging formats accepted
for format in col.accept_package:
Modified: sss/branches/sss-2/sss/webpy.py
===================================================================
--- sss/branches/sss-2/sss/webpy.py 2012-01-15 23:37:11 UTC (rev 451)
+++ sss/branches/sss-2/sss/webpy.py 2012-01-16 16:52:59 UTC (rev 452)
@@ -392,6 +392,7 @@
# can get hold of the media resource
media_resource = ss.get_media_resource(path, accept_parameters)
except SwordError as e:
+ ssslog.debug("Raised error")
return self.manage_error(e)
# either send the client a redirect, or stream the content out
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: SVN c. m. f. t. SWORD-A. p. <swo...@li...> - 2012-01-15 23:37:18
|
Revision: 451
http://sword-app.svn.sourceforge.net/sword-app/?rev=451&view=rev
Author: richard-jones
Date: 2012-01-15 23:37:11 +0000 (Sun, 15 Jan 2012)
Log Message:
-----------
add some null checking to class loader
Modified Paths:
--------------
sss/branches/sss-2/sss/config.py
Modified: sss/branches/sss-2/sss/config.py
===================================================================
--- sss/branches/sss-2/sss/config.py 2012-01-15 16:30:27 UTC (rev 450)
+++ sss/branches/sss-2/sss/config.py 2012-01-15 23:37:11 UTC (rev 451)
@@ -1,6 +1,7 @@
import os, uuid, sys, json
from ingesters_disseminators import DefaultEntryIngester, DefaultDisseminator, FeedDisseminator, BinaryIngester, SimpleZipIngester, METSDSpaceIngester
from negotiator import AcceptParameters, ContentType
+from core import SwordServer, Authenticator, WebUI
from sss_logging import logging
ssslog = logging.getLogger(__name__)
@@ -141,13 +142,22 @@
# the json string. How much does this matter?
def get_server_implementation(self):
- return self._get_class(self.sword_server)
+ if self.sword_server is not None:
+ return self._get_class(self.sword_server)
+ else:
+ return SwordServer
def get_authenticator_implementation(self):
- return self._get_class(self.authenticator)
+ if self.authenticator is not None:
+ return self._get_class(self.authenticator)
+ else:
+ return Authenticator
def get_webui_implementation(self):
- return self._get_class(self.webui)
+ if self.webui is not None:
+ return self._get_class(self.webui)
+ else:
+ return WebUI
def get_container_formats(self):
default_params = self._get_accept_params(self.container_format_default)
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: SVN c. m. f. t. SWORD-A. p. <swo...@li...> - 2012-01-15 16:30:34
|
Revision: 450
http://sword-app.svn.sourceforge.net/sword-app/?rev=450&view=rev
Author: richard-jones
Date: 2012-01-15 16:30:27 +0000 (Sun, 15 Jan 2012)
Log Message:
-----------
separate html interface from web layer in both web.py and pylons implementations
Modified Paths:
--------------
sss/branches/sss-2/sss/__init__.py
sss/branches/sss-2/sss/config.py
sss/branches/sss-2/sss/core.py
sss/branches/sss-2/sss/pylons_sword_controller.py
sss/branches/sss-2/sss/repository.py
sss/branches/sss-2/sss/webpy.py
Modified: sss/branches/sss-2/sss/__init__.py
===================================================================
--- sss/branches/sss-2/sss/__init__.py 2012-01-14 13:34:07 UTC (rev 449)
+++ sss/branches/sss-2/sss/__init__.py 2012-01-15 16:30:27 UTC (rev 450)
@@ -1,6 +1,6 @@
from info import __version__, __author__, __license__
-from core import Auth, AuthException, Authenticator, DeleteRequest, DeleteResponse, DepositRequest, DepositResponse, EntryDocument, SDCollection, SWORDRequest, ServiceDocument, Statement, SwordError, SwordServer
+from core import Auth, AuthException, Authenticator, DeleteRequest, DeleteResponse, DepositRequest, DepositResponse, EntryDocument, SDCollection, SWORDRequest, ServiceDocument, Statement, SwordError, SwordServer, WebUI
from config import Configuration, SSS_CONFIG_FILE
from spec import Errors, HttpHeaders
Modified: sss/branches/sss-2/sss/config.py
===================================================================
--- sss/branches/sss-2/sss/config.py 2012-01-14 13:34:07 UTC (rev 449)
+++ sss/branches/sss-2/sss/config.py 2012-01-15 16:30:27 UTC (rev 450)
@@ -119,7 +119,8 @@
},
"sword_server" : "sss.repository.SSS",
- "authenticator" : "sss.repository.SSSAuthenticator"
+ "authenticator" : "sss.repository.SSSAuthenticator",
+ "webui" : "sss.repository.WebInterface"
}
"""
@@ -144,6 +145,9 @@
def get_authenticator_implementation(self):
return self._get_class(self.authenticator)
+
+ def get_webui_implementation(self):
+ return self._get_class(self.webui)
def get_container_formats(self):
default_params = self._get_accept_params(self.container_format_default)
@@ -213,6 +217,9 @@
return self._load_class(modpath, classname)
else:
raise e
+ except AttributeError as e:
+ ssslog.debug("Tried and failed to load " + classname + " from " + modpath)
+ raise e
def _load_json(self):
if not os.path.isfile(self.SSS_CONFIG_FILE):
Modified: sss/branches/sss-2/sss/core.py
===================================================================
--- sss/branches/sss-2/sss/core.py 2012-01-14 13:34:07 UTC (rev 449)
+++ sss/branches/sss-2/sss/core.py 2012-01-15 16:30:27 UTC (rev 450)
@@ -856,3 +856,9 @@
return rdf
+class WebUI(object):
+ def __init__(self, config):
+ self.config = config
+
+ def get(self, path=None):
+ return
Modified: sss/branches/sss-2/sss/pylons_sword_controller.py
===================================================================
--- sss/branches/sss-2/sss/pylons_sword_controller.py 2012-01-14 13:34:07 UTC (rev 449)
+++ sss/branches/sss-2/sss/pylons_sword_controller.py 2012-01-15 16:30:27 UTC (rev 450)
@@ -7,8 +7,8 @@
from core import Auth, SWORDSpec, SwordError, AuthException, DepositRequest, DeleteRequest
from negotiator import ContentNegotiator, AcceptParameters, ContentType
from spec import Errors, HttpHeaders, ValidationException
-from webui import HomePage, CollectionPage, ItemPage
+
import logging
ssslog = logging.getLogger(__name__)
@@ -17,6 +17,7 @@
config = Configuration()
Authenticator = config.get_authenticator_implementation()
SwordServer = config.get_server_implementation()
+WebInterface = config.get_webui_implementation()
__controller__ = "SwordController"
@@ -336,7 +337,6 @@
abort(405, "Method Not Allowed")
return
-
def part(self, path=None):
http_method = request.environ['REQUEST_METHOD']
if http_method == "GET":
@@ -819,16 +819,8 @@
return
def _GET_webui(self, path=None):
- if path is not None:
- if path.find("/") >= 0:
- ip = ItemPage(config)
- return ip.get_item_page(path)
- else:
- cp = CollectionPage(config)
- return cp.get_collection_page(path)
- else:
- hp = HomePage(config)
- return hp.get_home_page()
+ w = WebInterface(config)
+ return w.get(path)
def _GET_part(self, path):
ss = SwordServer(config, None)
Modified: sss/branches/sss-2/sss/repository.py
===================================================================
--- sss/branches/sss-2/sss/repository.py 2012-01-14 13:34:07 UTC (rev 449)
+++ sss/branches/sss-2/sss/repository.py 2012-01-15 16:30:27 UTC (rev 450)
@@ -1,5 +1,5 @@
import os, hashlib, uuid, urllib
-from core import Statement, DepositResponse, MediaResourceResponse, DeleteResponse, SWORDSpec, Auth, AuthException, SwordError, ServiceDocument, SDCollection, EntryDocument, Authenticator, SwordServer
+from core import Statement, DepositResponse, MediaResourceResponse, DeleteResponse, SWORDSpec, Auth, AuthException, SwordError, ServiceDocument, SDCollection, EntryDocument, Authenticator, SwordServer, WebUI
from spec import Namespaces, Errors
from lxml import etree
from datetime import datetime
@@ -10,6 +10,19 @@
from sss_logging import logging
ssslog = logging.getLogger(__name__)
+class WebInterface(WebUI):
+ def get(self, path=None):
+ if path is not None:
+ if path.find("/") >= 0:
+ ip = ItemPage(self.config)
+ return ip.get_item_page(path)
+ else:
+ cp = CollectionPage(self.config)
+ return cp.get_collection_page(path)
+ else:
+ hp = HomePage(self.config)
+ return hp.get_home_page()
+
class SSSAuthenticator(Authenticator):
def __init__(self, config):
Authenticator.__init__(self, config)
@@ -1111,4 +1124,106 @@
cfiles = [f for f in os.listdir(odir) if not f.startswith("sss_") and not f in exclude]
return cfiles
+# Basic Web Interface
+#######################################################################
+class WebPage(object):
+ def _wrap_html(self, title, frag, head_frag=None):
+ return "<html><head><title>" + title + "</title>" + head_frag + "</head><body>" + frag + "</body></html>"
+
+class HomePage(WebPage):
+ """
+ Welcome / home page
+ """
+ def __init__(self, config):
+ self.config = config
+ self.dao = DAO(self.config)
+ self.um = URIManager(config)
+
+ def get_home_page(self):
+ frag = "<h1>Simple SWORDv2 Server</h1>"
+ frag += "<p><strong>Service Document (SD-IRI)</strong>: <a href=\"" + self.config.base_url + "sd-uri\">" + self.config.base_url + "sd-uri</a></p>"
+ frag += "<p>If prompted, use the username <strong>" + self.config.user + "</strong> and the password <strong>" + self.config.password + "</strong></p>"
+ frag += "<p>The On-Behalf-Of user to use is <strong>" + self.config.obo + "</strong></p>"
+
+ # list the collections
+ frag += "<h2>Collections</h2><ul>"
+ for col in self.dao.get_collection_names():
+ frag += "<li><a href=\"" + self.um.html_url(col) + "\">" + col + "</a></li>"
+ frag += "</ul>"
+
+ head_frag = "<link rel=\"http://purl.org/net/sword/discovery/service-document\" href=\"" + self.config.base_url + "sd-uri\"/>"
+
+ return self._wrap_html("Simple SWORDv2 Server", frag, head_frag)
+
+class CollectionPage(WebPage):
+ def __init__(self, config):
+ self.config = config
+ self.dao = DAO(config)
+ self.um = URIManager(config)
+
+ def get_collection_page(self, id):
+ frag = "<h1>Collection: " + id + "</h1>"
+
+ # list all of the containers in the collection
+ cpath = self.dao.get_store_path(id)
+ containers = os.listdir(cpath)
+ frag += "<h2>Containers</h2><ul>"
+ for container in containers:
+ frag += "<li><a href=\"" + self.um.html_url(id, container) + "\">" + container + "</a></li>"
+ frag += "</ul>"
+
+ head_frag = "<link rel=\"http://purl.org/net/sword/terms/deposit\" href=\"" + self.um.col_uri(id) + "\"/>"
+
+ return self._wrap_html("Collection: " + id, frag, head_frag)
+
+class ItemPage(WebPage):
+ def __init__(self, config):
+ self.config = config
+ self.dao = DAO(config)
+ self.um = URIManager(config)
+
+ def get_item_page(self, oid):
+ collection, id = self.um.interpret_oid(oid)
+ statement = self.dao.load_statement(collection, id)
+ metadata = self.dao.get_metadata(collection, id)
+
+ state_frag = self._get_state_frag(statement)
+ md_frag = self._layout_metadata(metadata)
+ file_frag = self._layout_files(statement)
+
+ frag = "<h1>Item: " + id + "</h1>"
+ frag += "<strong>State</strong>: " + state_frag
+ frag += self._layout_sections(md_frag, file_frag)
+
+ head_frag = "<link rel=\"http://purl.org/net/sword/terms/edit\" href=\"" + self.um.edit_uri(collection, id) + "\"/>"
+ head_frag += "<link rel=\"http://purl.org/net/sword/terms/statement\" href=\"" + self.um.state_uri(collection, id, "atom") + "\"/>"
+ head_frag += "<link rel=\"http://purl.org/net/sword/terms/statement\" href=\"" + self.um.state_uri(collection, id, "ore") + "\"/>"
+
+ return self._wrap_html("Item: " + id, frag, head_frag)
+
+ def _layout_metadata(self, metadata):
+ frag = "<h2>Metadata</h2>"
+ for key, vals in metadata.iteritems():
+ frag += "<strong>" + key + "</strong>: " + ", ".join(vals) + "<br/>"
+ if len(metadata) == 0:
+ frag += "No metadata associated with this item"
+ return frag
+
+ def _layout_files(self, statement):
+ frag = "<h2>Files</h2>"
+ frag += "<table border=\"1\"><tr><th>URI</th><th>deposited on</th><th>format</th><th>deposited by</th><th>on behalf of</th></tr>"
+ for uri, deposit_time, format, by, obo in statement.original_deposits:
+ frag += "<tr><td><a href=\"" + uri + "\">" + uri + "</a></td><td>" + str(deposit_time) + "</td><td>" + format
+ frag += "</td><td>" + by + "</td><td>" + str(obo) + "</td></tr>"
+ frag += "</table>"
+ return frag
+
+ def _get_state_frag(self, statement):
+ if statement.in_progress:
+ return statement.in_progress_uri
+ else:
+ return statement.archived_uri
+
+ def _layout_sections(self, metadata, files):
+ return "<table border=\"0\"><tr><td valign=\"top\">" + metadata + "</td><td valign=\"top\">" + files + "</td></tr></table>"
Modified: sss/branches/sss-2/sss/webpy.py
===================================================================
--- sss/branches/sss-2/sss/webpy.py 2012-01-14 13:34:07 UTC (rev 449)
+++ sss/branches/sss-2/sss/webpy.py 2012-01-15 16:30:27 UTC (rev 450)
@@ -2,7 +2,6 @@
from web.wsgiserver import CherryPyWSGIServer
from core import Auth, SWORDSpec, SwordError, AuthException, DepositRequest, DeleteRequest
from negotiator import ContentNegotiator, AcceptParameters, ContentType
-from webui import HomePage, CollectionPage, ItemPage
from spec import Errors, HttpHeaders, ValidationException
from sss_logging import logging
@@ -13,6 +12,7 @@
config = Configuration()
Authenticator = config.get_authenticator_implementation()
SwordServer = config.get_server_implementation()
+WebInterface = config.get_webui_implementation()
# Whether to run using SSL. This uses a default self-signed certificate. Change the paths to
# use an alternative set of keys
@@ -734,16 +734,8 @@
Class to provide a basic web interface to the store for convenience
"""
def GET(self, path=None):
- if path is not None:
- if path.find("/") >= 0:
- ip = ItemPage(config)
- return ip.get_item_page(path)
- else:
- cp = CollectionPage(config)
- return cp.get_collection_page(path)
- else:
- hp = HomePage(config)
- return hp.get_home_page()
+ w = WebInterface(config)
+ return w.get(path)
class Part(SwordHttpHandler):
"""
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: SVN c. m. f. t. SWORD-A. p. <swo...@li...> - 2012-01-14 13:34:14
|
Revision: 449
http://sword-app.svn.sourceforge.net/sword-app/?rev=449&view=rev
Author: richard-jones
Date: 2012-01-14 13:34:07 +0000 (Sat, 14 Jan 2012)
Log Message:
-----------
fix bugs with primitive webui, and wiring into webpy and pylons interfaces
Modified Paths:
--------------
sss/branches/sss-2/PYLONS.txt
sss/branches/sss-2/sss/pylons_sword_controller.py
sss/branches/sss-2/sss/webpy.py
sss/branches/sss-2/sss/webui.py
Modified: sss/branches/sss-2/PYLONS.txt
===================================================================
--- sss/branches/sss-2/PYLONS.txt 2012-01-14 12:08:07 UTC (rev 448)
+++ sss/branches/sss-2/PYLONS.txt 2012-01-14 13:34:07 UTC (rev 449)
@@ -19,6 +19,9 @@
routing.py
+In order to make the routes work properly, you need to remove the public/index.html
+to make the "/" route work properly
+
"""Routes configuration
The more specific and detailed routes should be defined first so they
Modified: sss/branches/sss-2/sss/pylons_sword_controller.py
===================================================================
--- sss/branches/sss-2/sss/pylons_sword_controller.py 2012-01-14 12:08:07 UTC (rev 448)
+++ sss/branches/sss-2/sss/pylons_sword_controller.py 2012-01-14 13:34:07 UTC (rev 449)
@@ -3,10 +3,11 @@
from pylons.controllers import WSGIController
from pylons.templating import render_mako as render
-import re, base64, urllib, uuid
+import re, base64, urllib, uuid, inspect
from core import Auth, SWORDSpec, SwordError, AuthException, DepositRequest, DeleteRequest
from negotiator import ContentNegotiator, AcceptParameters, ContentType
from spec import Errors, HttpHeaders, ValidationException
+from webui import HomePage, CollectionPage, ItemPage
import logging
ssslog = logging.getLogger(__name__)
@@ -263,7 +264,7 @@
if http_method == "GET":
return self._GET_service_document(sub_path)
else:
- ssslog.info("Returning (405) Method Not Allowed; Received " + http_method + " request on " + __name__)
+ ssslog.info("Returning (405) Method Not Allowed; Received " + http_method + " request on " + inspect.stack()[0][3])
abort(405, "Method Not Allowed")
return
@@ -274,7 +275,7 @@
elif http_method == "POST":
return self._POST_collection(path)
else:
- ssslog.info("Returning (405) Method Not Allowed; Received " + http_method + " request on " + __name__)
+ ssslog.info("Returning (405) Method Not Allowed; Received " + http_method + " request on " + inspect.stack()[0][3])
abort(405, "Method Not Allowed")
return
@@ -289,7 +290,7 @@
elif http_method == "DELETE":
return self._DELETE_media_resource(path)
else:
- ssslog.info("Returning (405) Method Not Allowed; Received " + http_method + " request on " + __name__)
+ ssslog.info("Returning (405) Method Not Allowed; Received " + http_method + " request on " + inspect.stack()[0][3])
abort(405, "Method Not Allowed")
return
@@ -304,7 +305,7 @@
elif http_method == "DELETE":
return self._DELETE_container(path)
else:
- ssslog.info("Returning (405) Method Not Allowed; Received " + http_method + " request on " + __name__)
+ ssslog.info("Returning (405) Method Not Allowed; Received " + http_method + " request on " + inspect.stack()[0][3])
abort(405, "Method Not Allowed")
return
@@ -313,14 +314,40 @@
if http_method == "GET":
return self._GET_statement(path)
else:
- ssslog.info("Returning (405) Method Not Allowed; Received " + http_method + " request on " + __name__)
+ ssslog.info("Returning (405) Method Not Allowed; Received " + http_method + " request on " + inspect.stack()[0][3])
abort(405, "Method Not Allowed")
return
- def aggregation(self, path=None): pass
- def part(self, path=None): pass
- def webui(self, path=None): pass
+ def aggregation(self, path=None):
+ http_method = request.environ['REQUEST_METHOD']
+ if http_method == "GET":
+ return self._GET_aggregation(path)
+ else:
+ ssslog.info("Returning (405) Method Not Allowed; Received " + http_method + " request on " + inspect.stack()[0][3])
+ abort(405, "Method Not Allowed")
+ return
+ def webui(self, path=None):
+ http_method = request.environ['REQUEST_METHOD']
+ if http_method == "GET":
+ return self._GET_webui(path)
+ else:
+ ssslog.info("Returning (405) Method Not Allowed; Received " + http_method + " request on " + inspect.stack()[0][3])
+ abort(405, "Method Not Allowed")
+ return
+
+
+ def part(self, path=None):
+ http_method = request.environ['REQUEST_METHOD']
+ if http_method == "GET":
+ return self._GET_part(path)
+ elif http_method == "PUT":
+ return self._PUT_part(path)
+ else:
+ ssslog.info("Returning (405) Method Not Allowed; Received " + http_method + " request on " + inspect.stack()[0][3])
+ abort(405, "Method Not Allowed")
+ return
+
# SWORD Protocol Operations
###########################
@@ -341,7 +368,7 @@
ss = SwordServer(config, auth)
sd = ss.service_document(path)
response.content_type = "text/xml"
- ssslog.info("Returning " + response.status + " from request on " + __name__)
+ ssslog.info("Returning " + response.status + " from request on " + inspect.stack()[0][3])
return sd
def _GET_collection(self, path=None):
@@ -363,7 +390,7 @@
ss = SwordServer(config, auth)
cl = ss.list_collection(path)
response.content_type = "text/xml"
- ssslog.info("Returning " + response.status + " from request on " + __name__)
+ ssslog.info("Returning " + response.status + " from request on " + inspect.stack()[0][3])
return cl
def _POST_collection(self, path=None):
@@ -398,11 +425,11 @@
response.status = "201 Created"
if config.return_deposit_receipt:
ssslog.info("Returning deposit receipt")
- ssslog.info("Returning " + response.status + " from request on " + __name__)
+ ssslog.info("Returning " + response.status + " from request on " + inspect.stack()[0][3])
return result.receipt
else:
ssslog.info("Omitting deposit receipt")
- ssslog.info("Returning " + response.status + " from request on " + __name__)
+ ssslog.info("Returning " + response.status + " from request on " + inspect.stack()[0][3])
return
except SwordError as e:
@@ -456,7 +483,7 @@
f = open(media_resource.filepath, "r")
response.status_int = 200
response.status = "200 OK"
- ssslog.info("Returning " + response.status + " from request on " + __name__)
+ ssslog.info("Returning " + response.status + " from request on " + inspect.stack()[0][3])
return f.read()
def _PUT_media_resource(self, path=None):
@@ -493,7 +520,7 @@
ssslog.info("Content replaced")
response.status_int = 204
response.status = "204 No Content" # notice that this is different from the POST as per AtomPub
- ssslog.info("Returning " + response.status + " from request on " + __name__)
+ ssslog.info("Returning " + response.status + " from request on " + inspect.stack()[0][3])
return
except SwordError as e:
@@ -532,11 +559,11 @@
response.status = "201 Created"
if config.return_deposit_receipt:
ssslog.info("Returning Receipt")
- ssslog.info("Returning " + response.status + " from request on " + __name__)
+ ssslog.info("Returning " + response.status + " from request on " + inspect.stack()[0][3])
return result.receipt
else:
ssslog.info("Omitting Receipt")
- ssslog.info("Returning " + response.status + " from request on " + __name__)
+ ssslog.info("Returning " + response.status + " from request on " + inspect.stack()[0][3])
return
except SwordError as e:
@@ -574,7 +601,7 @@
# just return, no need to give any more feedback
response.status_int = 204
response.status = "204 No Content" # No Content
- ssslog.info("Returning " + response.status + " from request on " + __name__)
+ ssslog.info("Returning " + response.status + " from request on " + inspect.stack()[0][3])
return
except SwordError as e:
@@ -619,7 +646,7 @@
# now actually get hold of the representation of the container and send it to the client
cont = ss.get_container(path, accept_parameters)
- ssslog.info("Returning " + response.status + " from request on " + __name__)
+ ssslog.info("Returning " + response.status + " from request on " + inspect.stack()[0][3])
return cont
except SwordError as e:
@@ -656,13 +683,13 @@
response.status_int = 200
response.status = "200 OK"
ssslog.info("Returning Deposit Receipt")
- ssslog.info("Returning " + response.status + " from request on " + __name__)
+ ssslog.info("Returning " + response.status + " from request on " + inspect.stack()[0][3])
return result.receipt
else:
response.status_int = 204
response.status = "204 No Content"
ssslog.info("Omitting Deposit Receipt")
- ssslog.info("Returning " + response.status + " from request on " + __name__)
+ ssslog.info("Returning " + response.status + " from request on " + inspect.stack()[0][3])
return
except SwordError as e:
@@ -706,11 +733,11 @@
if config.return_deposit_receipt:
response.content_type = "application/atom+xml;type=entry"
ssslog.info("Returning Deposit Receipt")
- ssslog.info("Returning " + response.status + " from request on " + __name__)
+ ssslog.info("Returning " + response.status + " from request on " + inspect.stack()[0][3])
return result.receipt
else:
ssslog.info("Omitting Deposit Receipt")
- ssslog.info("Returning " + response.status + " from request on " + __name__)
+ ssslog.info("Returning " + response.status + " from request on " + inspect.stack()[0][3])
return
except SwordError as e:
@@ -746,7 +773,7 @@
# no need to return any content
response.status_int = 204
response.status = "204 No Content"
- ssslog.info("Returning " + response.status + " from request on " + __name__)
+ ssslog.info("Returning " + response.status + " from request on " + inspect.stack()[0][3])
return
except SwordError as e:
@@ -769,8 +796,59 @@
# now actually get hold of the representation of the statement and send it to the client
cont = ss.get_statement(path)
- ssslog.info("Returning " + response.status + " from request on " + __name__)
+ ssslog.info("Returning " + response.status + " from request on " + inspect.stack()[0][3])
return cont
except SwordError as e:
return self.manage_error(e)
+
+
+ # OTHER HTTP HANDLERS
+ #############################################################################
+ # Define a set of handlers for the various URLs defined above to be used by web.py
+ # These ones aren't anything to do with the SWORD standard, they are just
+ # convenient to support the additional URIs produced
+
+ def _GET_aggregation(self, path=None):
+ # in this case we just redirect back to the Edit-URI with a 303 See Other
+ ss = SwordServer(config, None)
+ edit_uri = ss.get_edit_uri()
+ response.status_int = 303
+ response.status = "303 See Other"
+ response.headers["Content-Location"] = edit_uri
+ return
+
+ def _GET_webui(self, path=None):
+ if path is not None:
+ if path.find("/") >= 0:
+ ip = ItemPage(config)
+ return ip.get_item_page(path)
+ else:
+ cp = CollectionPage(config)
+ return cp.get_collection_page(path)
+ else:
+ hp = HomePage(config)
+ return hp.get_home_page()
+
+ def _GET_part(self, path):
+ ss = SwordServer(config, None)
+
+ # if we did, we can get hold of the media resource
+ fh = ss.get_part(path)
+
+ if fh is None:
+ return self.manage_error(SwordError(status=404, empty=True))
+
+ response.content_type = "application/octet-stream" # FIXME: we're not keeping track of content types
+ response.status_int = 200
+ response.status = "200 OK"
+ return fh.read()
+
+ def _PUT_part(self, path):
+ # FIXME: the spec says that we should either support this or return
+ # 405 Method Not Allowed.
+ # This would be useful for DepositMO compliance, so we should consider
+ # implementing this when time permits
+ response.status_int = 405
+ response.status = "405 Method Not Allowed"
+ return
Modified: sss/branches/sss-2/sss/webpy.py
===================================================================
--- sss/branches/sss-2/sss/webpy.py 2012-01-14 12:08:07 UTC (rev 448)
+++ sss/branches/sss-2/sss/webpy.py 2012-01-14 13:34:07 UTC (rev 449)
@@ -736,10 +736,10 @@
def GET(self, path=None):
if path is not None:
if path.find("/") >= 0:
- ip = ItemPage()
+ ip = ItemPage(config)
return ip.get_item_page(path)
else:
- cp = CollectionPage()
+ cp = CollectionPage(config)
return cp.get_collection_page(path)
else:
hp = HomePage(config)
Modified: sss/branches/sss-2/sss/webui.py
===================================================================
--- sss/branches/sss-2/sss/webui.py 2012-01-14 12:08:07 UTC (rev 448)
+++ sss/branches/sss-2/sss/webui.py 2012-01-14 13:34:07 UTC (rev 449)
@@ -18,15 +18,13 @@
def __init__(self, config):
self.config = config
self.dao = DAO(self.config)
- self.um = URIManager()
+ self.um = URIManager(config)
def get_home_page(self):
- cfg = self.config
-
frag = "<h1>Simple SWORDv2 Server</h1>"
- frag += "<p><strong>Service Document (SD-IRI)</strong>: <a href=\"" + cfg.base_url + "sd-uri\">" + cfg.base_url + "sd-uri</a></p>"
- frag += "<p>If prompted, use the username <strong>" + cfg.user + "</strong> and the password <strong>" + cfg.password + "</strong></p>"
- frag += "<p>The On-Behalf-Of user to use is <strong>" + cfg.obo + "</strong></p>"
+ frag += "<p><strong>Service Document (SD-IRI)</strong>: <a href=\"" + self.config.base_url + "sd-uri\">" + self.config.base_url + "sd-uri</a></p>"
+ frag += "<p>If prompted, use the username <strong>" + self.config.user + "</strong> and the password <strong>" + self.config.password + "</strong></p>"
+ frag += "<p>The On-Behalf-Of user to use is <strong>" + self.config.obo + "</strong></p>"
# list the collections
frag += "<h2>Collections</h2><ul>"
@@ -34,14 +32,15 @@
frag += "<li><a href=\"" + self.um.html_url(col) + "\">" + col + "</a></li>"
frag += "</ul>"
- head_frag = "<link rel=\"http://purl.org/net/sword/discovery/service-document\" href=\"" + cfg.base_url + "sd-uri\"/>"
+ head_frag = "<link rel=\"http://purl.org/net/sword/discovery/service-document\" href=\"" + self.config.base_url + "sd-uri\"/>"
return self._wrap_html("Simple SWORDv2 Server", frag, head_frag)
class CollectionPage(WebPage):
- def __init__(self):
- self.dao = DAO()
- self.um = URIManager()
+ def __init__(self, config):
+ self.config = config
+ self.dao = DAO(config)
+ self.um = URIManager(config)
def get_collection_page(self, id):
frag = "<h1>Collection: " + id + "</h1>"
@@ -59,9 +58,10 @@
return self._wrap_html("Collection: " + id, frag, head_frag)
class ItemPage(WebPage):
- def __init__(self):
- self.dao = DAO()
- self.um = URIManager()
+ def __init__(self, config):
+ self.config = config
+ self.dao = DAO(config)
+ self.um = URIManager(config)
def get_item_page(self, oid):
collection, id = self.um.interpret_oid(oid)
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: SVN c. m. f. t. SWORD-A. p. <swo...@li...> - 2012-01-14 12:08:14
|
Revision: 448
http://sword-app.svn.sourceforge.net/sword-app/?rev=448&view=rev
Author: richard-jones
Date: 2012-01-14 12:08:07 +0000 (Sat, 14 Jan 2012)
Log Message:
-----------
finish protocol operation implementations for pylon (still no multipart support), plus more logging. Have also duplicated any useful logging changes from pylons into the webpy impl
Modified Paths:
--------------
sss/branches/sss-2/sss/pylons_sword_controller.py
sss/branches/sss-2/sss/webpy.py
Modified: sss/branches/sss-2/sss/pylons_sword_controller.py
===================================================================
--- sss/branches/sss-2/sss/pylons_sword_controller.py 2012-01-13 14:41:25 UTC (rev 447)
+++ sss/branches/sss-2/sss/pylons_sword_controller.py 2012-01-14 12:08:07 UTC (rev 448)
@@ -61,7 +61,7 @@
ssslog.error("unable to interpret authentication header: " + auth_header)
raise SwordError(error_uri=Errors.bad_request, msg="unable to interpret authentication header")
- ssslog.info("Authentication details: " + str(username) + ":" + str(password) + "; On Behalf Of: " + str(obo))
+ ssslog.info("Authentication details: " + str(username) + ":[**password**]; On Behalf Of: " + str(obo))
authenticator = Authenticator(config)
try:
@@ -76,7 +76,7 @@
def manage_error(self, sword_error):
response.status_int = sword_error.status
- ssslog.info("Returning error status: " + str(sword_error.status))
+ ssslog.info("Returning error (" + str(sword_error.status) + ") - " + str(sword_error.error_uri))
if not sword_error.empty:
response.content_type = "text/xml"
return sword_error.error_document
@@ -129,7 +129,10 @@
#if len(webin) != 2: # if it is not multipart
# FIXME: this is reading everything in, and should be re-evaluated for performance/scalability
wsgi_input = request.environ['wsgi.input']
- wsgi_input.seek(0, 0)
+ if hasattr(wsgi_input, "seek"):
+ # in empty requests, the wsgi input object doesn't have a seek() method
+ # so we have to check for it
+ wsgi_input.seek(0, 0)
if wsgi_input is None or wsgi_input.read().strip() == "": # FIXME: this IS NOT safe to scale
if allow_empty:
@@ -182,6 +185,7 @@
empty_request = False
if d.content_length == 0:
+ ssslog.info("Received empty deposit request")
empty_request = True
if d.content_length > config.max_upload_size:
raise SwordError(error_uri=Errors.max_upload_size_exceeded,
@@ -259,6 +263,7 @@
if http_method == "GET":
return self._GET_service_document(sub_path)
else:
+ ssslog.info("Returning (405) Method Not Allowed; Received " + http_method + " request on " + __name__)
abort(405, "Method Not Allowed")
return
@@ -269,6 +274,7 @@
elif http_method == "POST":
return self._POST_collection(path)
else:
+ ssslog.info("Returning (405) Method Not Allowed; Received " + http_method + " request on " + __name__)
abort(405, "Method Not Allowed")
return
@@ -283,6 +289,7 @@
elif http_method == "DELETE":
return self._DELETE_media_resource(path)
else:
+ ssslog.info("Returning (405) Method Not Allowed; Received " + http_method + " request on " + __name__)
abort(405, "Method Not Allowed")
return
@@ -297,10 +304,18 @@
elif http_method == "DELETE":
return self._DELETE_container(path)
else:
+ ssslog.info("Returning (405) Method Not Allowed; Received " + http_method + " request on " + __name__)
abort(405, "Method Not Allowed")
return
- def statement(self, path=None): pass
+ def statement(self, path=None):
+ http_method = request.environ['REQUEST_METHOD']
+ if http_method == "GET":
+ return self._GET_statement(path)
+ else:
+ ssslog.info("Returning (405) Method Not Allowed; Received " + http_method + " request on " + __name__)
+ abort(405, "Method Not Allowed")
+ return
def aggregation(self, path=None): pass
def part(self, path=None): pass
@@ -326,6 +341,7 @@
ss = SwordServer(config, auth)
sd = ss.service_document(path)
response.content_type = "text/xml"
+ ssslog.info("Returning " + response.status + " from request on " + __name__)
return sd
def _GET_collection(self, path=None):
@@ -347,6 +363,7 @@
ss = SwordServer(config, auth)
cl = ss.list_collection(path)
response.content_type = "text/xml"
+ ssslog.info("Returning " + response.status + " from request on " + __name__)
return cl
def _POST_collection(self, path=None):
@@ -381,9 +398,11 @@
response.status = "201 Created"
if config.return_deposit_receipt:
ssslog.info("Returning deposit receipt")
+ ssslog.info("Returning " + response.status + " from request on " + __name__)
return result.receipt
else:
ssslog.info("Omitting deposit receipt")
+ ssslog.info("Returning " + response.status + " from request on " + __name__)
return
except SwordError as e:
@@ -407,8 +426,7 @@
# 406 Not Acceptable without looking first to see if there is even any media to content negotiate for
# which would be weird from a client perspective
if not ss.media_resource_exists(path):
- abort(404)
- return
+ return self.manage_error(SwordError(status=404, empty=True))
# get the content negotiation headers
accept_header = request.environ.get("HTTP_ACCEPT")
@@ -438,6 +456,7 @@
f = open(media_resource.filepath, "r")
response.status_int = 200
response.status = "200 OK"
+ ssslog.info("Returning " + response.status + " from request on " + __name__)
return f.read()
def _PUT_media_resource(self, path=None):
@@ -474,12 +493,93 @@
ssslog.info("Content replaced")
response.status_int = 204
response.status = "204 No Content" # notice that this is different from the POST as per AtomPub
+ ssslog.info("Returning " + response.status + " from request on " + __name__)
return
except SwordError as e:
return self.manage_error(e)
+ def _POST_media_resource(self, path=None):
+ """
+ POST a simple package into the specified media resource
+ Args:
+ - id: The ID of the media resource as specified in the requested URL
+ Returns a Deposit Receipt
+ """
+ ssslog.debug("POST to Media Resource (add new file); Incoming HTTP headers: " + str(request.environ))
+
+ # find out if update is allowed
+ if not config.allow_update:
+ error = SwordError(error_uri=Errors.method_not_allowed, msg="Update operations not currently permitted")
+ return self.manage_error(error)
+
+ # authenticate
+ try:
+ auth = self.http_basic_authenticate()
+
+ # check the validity of the request
+ self.validate_deposit_request(None, "6.7.1", None, allow_multipart=False)
+
+ deposit = self.get_deposit(auth)
+
+ # if we get here authentication was successful and we carry on
+ ss = SwordServer(config, auth)
+ result = ss.add_content(path, deposit)
+
+ response.content_type = "application/atom+xml;type=entry"
+ response.headers["Location"] = result.location
+ response.status_int = 201
+ response.status = "201 Created"
+ if config.return_deposit_receipt:
+ ssslog.info("Returning Receipt")
+ ssslog.info("Returning " + response.status + " from request on " + __name__)
+ return result.receipt
+ else:
+ ssslog.info("Omitting Receipt")
+ ssslog.info("Returning " + response.status + " from request on " + __name__)
+ return
+
+ except SwordError as e:
+ return self.manage_error(e)
+ def _DELETE_media_resource(self, path=None):
+ """
+ DELETE the contents of an object in the store (but not the object's container), leaving behind an empty
+ container for further use
+ Args:
+ - id: the ID of the object to have its content removed as per the requested URI
+ Return a Deposit Receipt
+ """
+ ssslog.debug("DELETE on Media Resource (remove content, leave container); Incoming HTTP headers: " + str(request.environ))
+
+ # find out if delete is allowed
+ if not config.allow_delete:
+ error = SwordError(error_uri=Errors.method_not_allowed, msg="Delete operations not currently permitted")
+ return self.manage_error(error)
+
+ # authenticate
+ try:
+ auth = self.http_basic_authenticate()
+
+ # check the validity of the request
+ self.validate_delete_request("6.6")
+
+ # parse the delete request out of the HTTP request
+ delete = self.get_delete(auth)
+
+ # carry out the delete
+ ss = SwordServer(config, auth)
+ result = ss.delete_content(path, delete)
+
+ # just return, no need to give any more feedback
+ response.status_int = 204
+ response.status = "204 No Content" # No Content
+ ssslog.info("Returning " + response.status + " from request on " + __name__)
+ return
+
+ except SwordError as e:
+ return self.manage_error(e)
+
def _GET_container(self, path=None):
"""
GET a representation of the container in the appropriate (content negotiated) format as identified by
@@ -501,8 +601,7 @@
# 415 Unsupported Media Type without looking first to see if there is even any media to content negotiate for
# which would be weird from a client perspective
if not ss.container_exists(path):
- abort(404)
- return
+ return self.manage_error(SwordError(status=404, empty=True))
# get the content negotiation headers
accept_header = request.environ.get("HTTP_ACCEPT")
@@ -520,8 +619,158 @@
# now actually get hold of the representation of the container and send it to the client
cont = ss.get_container(path, accept_parameters)
+ ssslog.info("Returning " + response.status + " from request on " + __name__)
return cont
except SwordError as e:
return self.manage_error(e)
+
+ def _PUT_container(self, path=None):
+ """
+ PUT a new Entry over the existing entry, or a multipart request over
+ both the existing metadata and the existing content
+ """
+ ssslog.debug("PUT on Container (replace); Incoming HTTP headers: " + str(request.environ))
+
+ # find out if update is allowed
+ if not config.allow_update:
+ error = SwordError(error_uri=Errors.method_not_allowed, msg="Update operations not currently permitted")
+ return self.manage_error(error)
+
+ try:
+ # authenticate
+ auth = self.http_basic_authenticate()
+
+ # check the validity of the request
+ self.validate_deposit_request("6.5.2", None, "6.5.3")
+
+ # get the deposit object
+ deposit = self.get_deposit(auth)
+
+ ss = SwordServer(config, auth)
+ result = ss.replace(path, deposit)
+
+ response.headers["Location"] = result.location
+ if config.return_deposit_receipt:
+ response.content_type = "application/atom+xml;type=entry"
+ response.status_int = 200
+ response.status = "200 OK"
+ ssslog.info("Returning Deposit Receipt")
+ ssslog.info("Returning " + response.status + " from request on " + __name__)
+ return result.receipt
+ else:
+ response.status_int = 204
+ response.status = "204 No Content"
+ ssslog.info("Omitting Deposit Receipt")
+ ssslog.info("Returning " + response.status + " from request on " + __name__)
+ return
+
+ except SwordError as e:
+ return self.manage_error(e)
+
+ def _POST_container(self, path=None):
+ """
+ POST some new content into the container identified by the supplied id,
+ or complete an existing deposit (using the In-Progress header)
+ Args:
+ - id: The ID of the container as contained in the URL
+ Returns a Deposit Receipt
+ """
+ ssslog.debug("POST to Container (add new content and metadata); Incoming HTTP headers: " + str(request.environ))
+
+ # find out if update is allowed
+ if not config.allow_update:
+ error = SwordError(error_uri=Errors.method_not_allowed, msg="Update operations not currently permitted")
+ return self.manage_error(error)
+
+ try:
+ # authenticate
+ auth = self.http_basic_authenticate()
+
+ # check the validity of the request
+ self.validate_deposit_request("6.7.2", None, "6.7.3", "9.3", allow_empty=True)
+
+ deposit = self.get_deposit(auth)
+
+ ss = SwordServer(config, auth)
+ result = ss.deposit_existing(path, deposit)
+
+ # NOTE: spec says 201 Created for multipart and 200 Ok for metadata only
+ # we have implemented 200 OK across the board, in the understanding that
+ # in this case the spec is incorrect (correction need to be implemented
+ # asap)
+
+ response.headers["Location"] = result.location
+ response.status_int = 200
+ response.status = "200 OK"
+ if config.return_deposit_receipt:
+ response.content_type = "application/atom+xml;type=entry"
+ ssslog.info("Returning Deposit Receipt")
+ ssslog.info("Returning " + response.status + " from request on " + __name__)
+ return result.receipt
+ else:
+ ssslog.info("Omitting Deposit Receipt")
+ ssslog.info("Returning " + response.status + " from request on " + __name__)
+ return
+
+ except SwordError as e:
+ return self.manage_error(e)
+
+ def _DELETE_container(self, path=None):
+ """
+ DELETE the container (and everything in it) from the store, as identified by the supplied id
+ Args:
+ - id: the ID of the container
+ Returns nothing, as there is nothing to return (204 No Content)
+ """
+ ssslog.debug("DELETE on Container (remove); Incoming HTTP headers: " + str(request.environ))
+
+ try:
+ # find out if update is allowed
+ if not config.allow_delete:
+ raise SwordError(error_uri=Errors.method_not_allowed, msg="Delete operations not currently permitted")
+
+ # authenticate
+ auth = self.http_basic_authenticate()
+
+ # check the validity of the request
+ self.validate_delete_request("6.8")
+
+ # get the delete request
+ delete = self.get_delete(auth)
+
+ # do the delete
+ ss = SwordServer(config, auth)
+ result = ss.delete_container(path, delete)
+
+ # no need to return any content
+ response.status_int = 204
+ response.status = "204 No Content"
+ ssslog.info("Returning " + response.status + " from request on " + __name__)
+ return
+
+ except SwordError as e:
+ return self.manage_error(e)
+ def _GET_statement(self, path=None):
+ ssslog.debug("GET on Statement (retrieve); Incoming HTTP headers: " + str(request.environ))
+
+ try:
+ # authenticate
+ auth = self.http_basic_authenticate()
+
+ ss = SwordServer(config, auth)
+
+ # first thing we need to do is check that there is an object to return, because otherwise we may throw a
+ # 415 Unsupported Media Type without looking first to see if there is even any media to content negotiate for
+ # which would be weird from a client perspective
+ if not ss.container_exists(path):
+ raise SwordError(status=404, empty=True)
+
+ # now actually get hold of the representation of the statement and send it to the client
+ cont = ss.get_statement(path)
+ ssslog.info("Returning " + response.status + " from request on " + __name__)
+ return cont
+
+ except SwordError as e:
+ return self.manage_error(e)
Modified: sss/branches/sss-2/sss/webpy.py
===================================================================
--- sss/branches/sss-2/sss/webpy.py 2012-01-13 14:41:25 UTC (rev 447)
+++ sss/branches/sss-2/sss/webpy.py 2012-01-14 12:08:07 UTC (rev 448)
@@ -94,7 +94,7 @@
ssslog.error("unable to interpret authentication header: " + auth_header)
raise SwordError(error_uri=Errors.bad_request, msg="unable to interpret authentication header")
- ssslog.info("Authentication details: " + str(username) + ":" + str(password) + "; On Behalf Of: " + str(obo))
+ ssslog.info("Authentication details: " + str(username) + ":[**password**]; On Behalf Of: " + str(obo))
authenticator = Authenticator(config)
try:
@@ -109,6 +109,7 @@
def manage_error(self, sword_error):
status = STATUS_MAP.get(sword_error.status, "400 Bad Request")
+ ssslog.info("Returning error (" + str(sword_error.status) + ") - " + str(sword_error.error_uri))
web.ctx.status = status
if not sword_error.empty:
web.header("Content-Type", "text/xml")
@@ -204,6 +205,7 @@
empty_request = False
if d.content_length == 0:
+ ssslog.info("Received empty deposit request")
empty_request = True
if d.content_length > config.max_upload_size:
raise SwordError(error_uri=Errors.max_upload_size_exceeded,
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: SVN c. m. f. t. SWORD-A. p. <swo...@li...> - 2012-01-13 14:41:36
|
Revision: 447
http://sword-app.svn.sourceforge.net/sword-app/?rev=447&view=rev
Author: richard-jones
Date: 2012-01-13 14:41:25 +0000 (Fri, 13 Jan 2012)
Log Message:
-----------
add GET on container and PUT on media resource
Modified Paths:
--------------
sss/branches/sss-2/sss/pylons_sword_controller.py
Modified: sss/branches/sss-2/sss/pylons_sword_controller.py
===================================================================
--- sss/branches/sss-2/sss/pylons_sword_controller.py 2012-01-13 12:29:00 UTC (rev 446)
+++ sss/branches/sss-2/sss/pylons_sword_controller.py 2012-01-13 14:41:25 UTC (rev 447)
@@ -277,7 +277,7 @@
if http_method == "GET":
return self._GET_media_resource(path)
elif http_method == "PUT":
- return self._PUT_media_resource
+ return self._PUT_media_resource(path)
elif http_method == "POST":
return self._POST_media_resource(path)
elif http_method == "DELETE":
@@ -286,7 +286,20 @@
abort(405, "Method Not Allowed")
return
- def container(self, path=None): pass
+ def container(self, path=None):
+ http_method = request.environ['REQUEST_METHOD']
+ if http_method == "GET":
+ return self._GET_container(path)
+ elif http_method == "PUT":
+ return self._PUT_container(path)
+ elif http_method == "POST":
+ return self._POST_container(path)
+ elif http_method == "DELETE":
+ return self._DELETE_container(path)
+ else:
+ abort(405, "Method Not Allowed")
+ return
+
def statement(self, path=None): pass
def aggregation(self, path=None): pass
@@ -427,5 +440,88 @@
response.status = "200 OK"
return f.read()
+ def _PUT_media_resource(self, path=None):
+ """
+ PUT a new package onto the object identified by the supplied id
+ Args:
+ - id: the ID of the media resource as specified in the URL
+ Returns a Deposit Receipt
+ """
+ ssslog.debug("PUT on Media Resource (replace); Incoming HTTP headers: " + str(request.environ))
+
+ # find out if update is allowed
+ if not config.allow_update:
+ error = SwordError(error_uri=Errors.method_not_allowed, msg="Update operations not currently permitted")
+ return self.manage_error(error)
+
+ # authenticate
+ try:
+ auth = self.http_basic_authenticate()
+
+ # check the validity of the request (note that multipart requests
+ # and atom-only are not permitted in this method)
+ self.validate_deposit_request(None, "6.5.1", None, allow_multipart=False)
+
+ # get a deposit object. The PUT operation only supports a single binary deposit, not an Atom Multipart one
+ # so if the deposit object has an atom part we should return an error
+ deposit = self.get_deposit(auth)
+
+ # now replace the content of the container
+ ss = SwordServer(config, auth)
+ result = ss.replace(path, deposit)
+
+ # replaced
+ ssslog.info("Content replaced")
+ response.status_int = 204
+ response.status = "204 No Content" # notice that this is different from the POST as per AtomPub
+ return
+
+ except SwordError as e:
+ return self.manage_error(e)
+ def _GET_container(self, path=None):
+ """
+ GET a representation of the container in the appropriate (content negotiated) format as identified by
+ the supplied id
+ Args:
+ - id: The ID of the container as supplied in the request URL
+ Returns a representation of the container: SSS will return either the Atom Entry identical to the one supplied
+ as a deposit receipt or the pure RDF/XML Statement depending on the Accept header
+ """
+ ssslog.debug("GET on Container (retrieve deposit receipt or statement); Incoming HTTP headers: " + str(request.environ))
+
+ # authenticate
+ try:
+ auth = self.http_basic_authenticate()
+
+ ss = SwordServer(config, auth)
+
+ # first thing we need to do is check that there is an object to return, because otherwise we may throw a
+ # 415 Unsupported Media Type without looking first to see if there is even any media to content negotiate for
+ # which would be weird from a client perspective
+ if not ss.container_exists(path):
+ abort(404)
+ return
+
+ # get the content negotiation headers
+ accept_header = request.environ.get("HTTP_ACCEPT")
+ accept_packaging_header = request.environ.get("HTTP_ACCEPT_PACKAGING")
+
+ # do the negotiation
+ default_accept_parameters, acceptable = config.get_container_formats()
+ cn = ContentNegotiator(default_accept_parameters, acceptable)
+ accept_parameters = cn.negotiate(accept=accept_header)
+ ssslog.info("Container requested in format: " + str(accept_parameters))
+
+ # did we successfully negotiate a content type?
+ if accept_parameters is None:
+ raise SwordError(error_uri=Error.content, status=415, empty=True)
+
+ # now actually get hold of the representation of the container and send it to the client
+ cont = ss.get_container(path, accept_parameters)
+ return cont
+
+ except SwordError as e:
+ return self.manage_error(e)
+
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: SVN c. m. f. t. SWORD-A. p. <swo...@li...> - 2012-01-13 12:29:09
|
Revision: 446
http://sword-app.svn.sourceforge.net/sword-app/?rev=446&view=rev
Author: richard-jones
Date: 2012-01-13 12:29:00 +0000 (Fri, 13 Jan 2012)
Log Message:
-----------
pylons: add GET on media resource and update routing documentation
Modified Paths:
--------------
sss/branches/sss-2/PYLONS.txt
sss/branches/sss-2/sss/pylons_sword_controller.py
Modified: sss/branches/sss-2/PYLONS.txt
===================================================================
--- sss/branches/sss-2/PYLONS.txt 2012-01-13 11:38:36 UTC (rev 445)
+++ sss/branches/sss-2/PYLONS.txt 2012-01-13 12:29:00 UTC (rev 446)
@@ -34,28 +34,23 @@
always_scan=config['debug'])
map.minimization = False
- # The ErrorController route (handles 404/500 error pages); it should
- # likely stay at the top, ensuring it can always be resolved
- map.connect('/error/{action}', controller='error')
- map.connect('/error/{action}/{id}', controller='error')
-
# CUSTOM ROUTES HERE
map.connect('/', controller="sword", action="webui") # Home page, with an intro and some handy links
map.connect('/sd-uri', controller="sword", action="service_document") # From which to retrieve the service document
- map.connect('/sd-uri/{sub_path}', controller="sword", action="service_document") # for sub-service documents
- map.connect('/col-uri/{path}', controller="sword", action="collection") # Representing a Collection as listed in the service document
- map.connect('/cont-uri/{path}', controller="sword", action="media_resource") # The URI used in atom:content@src
- map.connect('/em-uri/{path}', controller="sword", action="media_resource") # The URI used in atom:link@rel=edit-media
- map.connect('/edit-uri/{path}', controller="sword", action="container") # The URI used in atom:link@rel=edit
- map.connect('/state-uri/{path}', controller="sword", action="statement") # The URI used in atom:link@rel=sword:statement
+ map.connect('/sd-uri/{path:.*?}', controller="sword", action="service_document") # for sub-service documents
+ map.connect('/col-uri/{path:.*?}', controller="sword", action="collection") # Representing a Collection as listed in the service document
+ map.connect('/cont-uri/{path:.*?}', controller="sword", action="media_resource") # The URI used in atom:content@src
+ map.connect('/em-uri/{path:.*?}', controller="sword", action="media_resource") # The URI used in atom:link@rel=edit-media
+ map.connect('/edit-uri/{path:.*?}', controller="sword", action="container") # The URI used in atom:link@rel=edit
+ map.connect('/state-uri/{path:.*?}', controller="sword", action="statement") # The URI used in atom:link@rel=sword:statement
- map.connect('/agg-uri/{path}', controller="sword", action="aggregation") # The URI used to represent the ORE aggregation
+ map.connect('/agg-uri/{path:.*?}', controller="sword", action="aggregation") # The URI used to represent the ORE aggregation
# NOT PART OF SWORD: sword says nothing about how components of the item are identified, but here we use the
# PART-URI prefix to denote parts of the object in the server
- map.connect('/part-uri/{path}', controller="sword", action="part")
+ map.connect('/part-uri/{path:.*?}', controller="sword", action="part")
# NOT PART OF SWORD: for convenience to supply HTML pages of deposited content
- map.connect('/html/{path}', controller="sword", action="webui")
+ map.connect('/html/{path:.*?}', controller="sword", action="webui")
return map
Modified: sss/branches/sss-2/sss/pylons_sword_controller.py
===================================================================
--- sss/branches/sss-2/sss/pylons_sword_controller.py 2012-01-13 11:38:36 UTC (rev 445)
+++ sss/branches/sss-2/sss/pylons_sword_controller.py 2012-01-13 12:29:00 UTC (rev 446)
@@ -242,7 +242,7 @@
d = DeleteRequest()
# map the webpy headers to something more standard
- mapped_headers = self._map_webpy_headers(web.ctx.environ)
+ mapped_headers = self._map_webpy_headers(request.environ)
h = HttpHeaders()
d.set_from_headers(h.get_sword_headers(mapped_headers))
@@ -272,7 +272,20 @@
abort(405, "Method Not Allowed")
return
- def media_resource(self, path=None): pass
+ def media_resource(self, path=None):
+ http_method = request.environ['REQUEST_METHOD']
+ if http_method == "GET":
+ return self._GET_media_resource(path)
+ elif http_method == "PUT":
+ return self._PUT_media_resource
+ elif http_method == "POST":
+ return self._POST_media_resource(path)
+ elif http_method == "DELETE":
+ return self._DELETE_media_resource(path)
+ else:
+ abort(405, "Method Not Allowed")
+ return
+
def container(self, path=None): pass
def statement(self, path=None): pass
@@ -283,7 +296,7 @@
# SWORD Protocol Operations
###########################
- def _GET_service_document(self, sub_path=None):
+ def _GET_service_document(self, path=None):
"""
GET the service document - returns an XML document
- sub_path - the path provided for the sub-service document
@@ -298,7 +311,7 @@
# if we get here authentication was successful and we carry on (we don't care who authenticated)
ss = SwordServer(config, auth)
- sd = ss.service_document(sub_path)
+ sd = ss.service_document(path)
response.content_type = "text/xml"
return sd
@@ -362,6 +375,57 @@
except SwordError as e:
return self.manage_error(e)
+
+ def _GET_media_resource(self, path=None):
+ """
+ GET the media resource content in the requested format (web request will include content negotiation via
+ Accept header)
+ Args:
+ - id: the ID of the object in the store
+ Returns the content in the requested format
+ """
+ ssslog.debug("GET on MediaResource; Incoming HTTP headers: " + str(request.environ))
+
+ # NOTE: this method is not authenticated - we imagine sharing this URL with end-users who will just want
+ # to retrieve the content.
+ ss = SwordServer(config, None)
+ # first thing we need to do is check that there is an object to return, because otherwise we may throw a
+ # 406 Not Acceptable without looking first to see if there is even any media to content negotiate for
+ # which would be weird from a client perspective
+ if not ss.media_resource_exists(path):
+ abort(404)
+ return
+
+ # get the content negotiation headers
+ accept_header = request.environ.get("HTTP_ACCEPT")
+ accept_packaging_header = request.environ.get("HTTP_ACCEPT_PACKAGING")
+
+ # do the negotiation
+ default_accept_parameters, acceptable = config.get_media_resource_formats()
+ cn = ContentNegotiator(default_accept_parameters, acceptable)
+ accept_parameters = cn.negotiate(accept=accept_header, accept_packaging=accept_packaging_header)
+
+ ssslog.info("Conneg format: " + str(accept_parameters))
+
+ try:
+ # can get hold of the media resource
+ media_resource = ss.get_media_resource(path, accept_parameters)
+ except SwordError as e:
+ return self.manage_error(e)
+
+ # either send the client a redirect, or stream the content out
+ if media_resource.redirect:
+ redirect_to(media_resource.url, _code=302) # FOUND (not SEE OTHER)
+ return
+ else:
+ response.content_type = media_resource.content_type
+ if media_resource.packaging is not None:
+ response.headers["Packaging"] = media_resource.packaging
+ f = open(media_resource.filepath, "r")
+ response.status_int = 200
+ response.status = "200 OK"
+ return f.read()
+
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: SVN c. m. f. t. SWORD-A. p. <swo...@li...> - 2012-01-13 11:38:47
|
Revision: 445
http://sword-app.svn.sourceforge.net/sword-app/?rev=445&view=rev
Author: richard-jones
Date: 2012-01-13 11:38:36 +0000 (Fri, 13 Jan 2012)
Log Message:
-----------
add support for POST to collections, with binary or entry deposits. Multipart/related in Pylons simply does not work at this stage, and significant further effort is required to provide custom multipart handling. This will be deferred for the moment, and for the time being the Pylons implementation WILL NOT support multipart deposit
Modified Paths:
--------------
sss/branches/sss-2/PYLONS.txt
sss/branches/sss-2/sss/pylons_sword_controller.py
sss/branches/sss-2/sss/webpy.py
Modified: sss/branches/sss-2/PYLONS.txt
===================================================================
--- sss/branches/sss-2/PYLONS.txt 2012-01-13 09:17:11 UTC (rev 444)
+++ sss/branches/sss-2/PYLONS.txt 2012-01-13 11:38:36 UTC (rev 445)
@@ -1,3 +1,15 @@
+Controller:
+
+To use the native pylons controller in your pylons app, you need to set up
+a controller (which we assume to be called "sword.py" for the duration of this
+document, with the content:
+
+from sss.pylons_sword_controller import SwordController
+__controller__ = "SwordController"
+
+That is all.
+
+
middleware.py
You need to comment out the error handling code in middleware.py otherwise
Modified: sss/branches/sss-2/sss/pylons_sword_controller.py
===================================================================
--- sss/branches/sss-2/sss/pylons_sword_controller.py 2012-01-13 09:17:11 UTC (rev 444)
+++ sss/branches/sss-2/sss/pylons_sword_controller.py 2012-01-13 11:38:36 UTC (rev 445)
@@ -108,6 +108,12 @@
# run the validation
try:
# there must be both an "atom" and "payload" input or data in web.data()
+
+ # FIXME: deposit does NOT support multipart
+ if request.environ["CONTENT_TYPE"].startswith("multipart"):
+ raise SwordError(error_uri=Errors.method_not_allowed, msg="Pylons implementation does not currently support multipart/related requests")
+ """
+ # leave this out until we can get multipart sorted (at a later date)
webin = request.POST
if len(webin) != 2 and len(webin) > 0:
raise ValidationException("Multipart request does not contain exactly 2 parts")
@@ -115,28 +121,30 @@
raise ValidationException("Multipart request must contain Content-Dispositions with names 'atom' and 'payload'")
if len(webin) > 0 and not allow_multipart:
raise ValidationException("Multipart request not permitted in this context")
+ """
# if we get to here then we have a valid multipart or no multipart
is_multipart = False
is_empty = False
- if len(webin) != 2: # if it is not multipart
+ #if len(webin) != 2: # if it is not multipart
# FIXME: this is reading everything in, and should be re-evaluated for performance/scalability
- data = request.environ['wsgi.input'].read(int(request.environ['CONTENT_LENGTH']))
-
- if data is None or data.strip() == "": # FIXME: this does not look safe to scale
- if allow_empty:
- ssslog.info("Validating an empty deposit (could be a control operation)")
- is_empty = True
- else:
- raise ValidationException("No content sent to the server")
- else:
- ssslog.info("Validating a multipart deposit")
- is_multipart = True
+ wsgi_input = request.environ['wsgi.input']
+ wsgi_input.seek(0, 0)
+ if wsgi_input is None or wsgi_input.read().strip() == "": # FIXME: this IS NOT safe to scale
+ if allow_empty:
+ ssslog.info("Validating an empty deposit (could be a control operation)")
+ is_empty = True
+ else:
+ raise ValidationException("No content sent to the server")
+ #else:
+ # ssslog.info("Validating a multipart deposit")
+ # is_multipart = True
+
is_entry = False
content_type = mapped_headers.get("CONTENT-TYPE")
if content_type is not None and content_type.startswith("application/atom+xml"):
- ssslog.info("Validating a atom-only deposit")
+ ssslog.info("Validating an atom-only deposit")
is_entry = True
if not is_entry and not is_multipart and not is_empty:
@@ -180,34 +188,41 @@
msg="Max upload size is " + config.max_upload_size +
"; incoming content length was " + str(cl))
+ # FIXME: this method does NOT support multipart
# find out if this is a multipart or not
is_multipart = False
# FIXME: these headers aren't populated yet, because the webpy api doesn't
# appear to have a mechanism to retrieve them. urgh.
- entry_part_headers = {}
- media_part_headers = {}
- webin = request.POST
- if len(webin) == 2:
- ssslog.info("Received multipart deposit request")
- d.atom = webin['atom']
+ #entry_part_headers = {}
+ #media_part_headers = {}
+ #webin = request.POST
+ #ssslog.debug(webin)
+ #if len(webin) == 2:
+ # ssslog.info("Received multipart deposit request")
+ # d.atom = webin['atom']
# FIXME: this reads the payload into memory, we need to sort that out
# read the zip file from the base64 encoded string
- d.content = base64.decodestring(webin['payload'])
- is_multipart = True
- elif not empty_request:
- # if this wasn't a multipart, and isn't an empty request, then the data is in web.data(). This could be a binary deposit or
- # an atom entry deposit - reply on the passed/determined argument to determine which
+ # d.content = base64.decodestring(webin['payload'])
+ # is_multipart = True
+ #elif not empty_request:
+ if not empty_request:
+ # for this section, we have to reset the file pointer in the wsgi.input
+ # part of the request back to the start, since it may have
+ # already been read once
+ wsgi_input = request.environ['wsgi.input']
+ wsgi_input.seek(0, 0)
+
+ # if this wasn't a multipart, and isn't an empty request, then read the
+ # data from the wsgi input
if atom_only:
ssslog.info("Received Entry deposit request")
# FIXME: this is reading everything in, and should be re-evaluated for performance/scalability
- data = request.environ['wsgi.input'].read(int(request.environ['CONTENT_LENGTH']))
- d.atom = data
+ d.atom = wsgi_input.read()
else:
ssslog.info("Received Binary deposit request")
# FIXME: this is reading everything in, and should be re-evaluated for performance/scalability
- data = request.environ['wsgi.input'].read(int(request.environ['CONTENT_LENGTH']))
- d.content = data
+ d.content = wsgi_input.read()
if is_multipart:
d.filename = h.extract_filename(media_part_headers)
@@ -309,6 +324,44 @@
return cl
def _POST_collection(self, path=None):
- pass
+ """
+ POST either an Atom Multipart request, or a simple package into the specified collection
+ Args:
+ - collection: The ID of the collection as specified in the requested URL
+ Returns a Deposit Receipt
+ """
+ ssslog.debug("POST to Collection (create new item); Incoming HTTP headers: " + str(request.environ))
+
+ try:
+ # authenticate
+ auth = self.http_basic_authenticate()
+
+ # check the validity of the request
+ self.validate_deposit_request("6.3.3", "6.3.1", "6.3.2")
+
+ # take the HTTP request and extract a Deposit object from it
+ deposit = self.get_deposit(auth)
+
+ # go ahead and process the deposit. Anything other than a success
+ # will be raised as a sword error
+ ss = SwordServer(config, auth)
+ result = ss.deposit_new(path, deposit)
+
+ # created
+ ssslog.info("Item created")
+ response.content_type = "application/atom+xml;type=entry"
+ response.headers["Location"] = result.location
+ response.status_int = 201
+ response.status = "201 Created"
+ if config.return_deposit_receipt:
+ ssslog.info("Returning deposit receipt")
+ return result.receipt
+ else:
+ ssslog.info("Omitting deposit receipt")
+ return
+
+ except SwordError as e:
+ return self.manage_error(e)
+
Modified: sss/branches/sss-2/sss/webpy.py
===================================================================
--- sss/branches/sss-2/sss/webpy.py 2012-01-13 09:17:11 UTC (rev 444)
+++ sss/branches/sss-2/sss/webpy.py 2012-01-13 11:38:36 UTC (rev 445)
@@ -166,7 +166,7 @@
is_entry = False
content_type = mapped_headers.get("CONTENT-TYPE")
if content_type is not None and content_type.startswith("application/atom+xml"):
- ssslog.info("Validating a atom-only deposit")
+ ssslog.info("Validating an atom-only deposit")
is_entry = True
if not is_entry and not is_multipart and not is_empty:
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: SVN c. m. f. t. SWORD-A. p. <swo...@li...> - 2012-01-13 09:17:21
|
Revision: 444
http://sword-app.svn.sourceforge.net/sword-app/?rev=444&view=rev
Author: richard-jones
Date: 2012-01-13 09:17:11 +0000 (Fri, 13 Jan 2012)
Log Message:
-----------
add list collection content request support, and prove concept of request routing inside pylons controller
Modified Paths:
--------------
sss/branches/sss-2/sss/pylons_sword_controller.py
Modified: sss/branches/sss-2/sss/pylons_sword_controller.py
===================================================================
--- sss/branches/sss-2/sss/pylons_sword_controller.py 2012-01-12 22:31:35 UTC (rev 443)
+++ sss/branches/sss-2/sss/pylons_sword_controller.py 2012-01-13 09:17:11 UTC (rev 444)
@@ -80,12 +80,195 @@
if not sword_error.empty:
response.content_type = "text/xml"
return sword_error.error_document
- return ""
+ return
+ def _map_webpy_headers(self, headers):
+ return dict([(c[0][5:].replace("_", "-") if c[0].startswith("HTTP_") else c[0].replace("_", "-"), c[1]) for c in headers.items()])
+
+ def validate_delete_request(self, section):
+ h = HttpHeaders()
+
+ # map the headers to standard http
+ mapped_headers = self._map_webpy_headers(request.environ)
+ ssslog.debug("Validating on header dictionary: " + str(mapped_headers))
+
+ try:
+ # now validate the http headers
+ h.validate(mapped_headers, section)
+ except ValidationError as e:
+ raise SwordError(error_uri=Errors.bad_request, msg=e.message)
+
+ def validate_deposit_request(self, entry_section=None, binary_section=None, multipart_section=None, empty_section=None, allow_multipart=True, allow_empty=False):
+ h = HttpHeaders()
+
+ # map the headers to standard http
+ mapped_headers = self._map_webpy_headers(request.environ)
+ ssslog.debug("Validating on header dictionary: " + str(mapped_headers))
+
+ # run the validation
+ try:
+ # there must be both an "atom" and "payload" input or data in web.data()
+ webin = request.POST
+ if len(webin) != 2 and len(webin) > 0:
+ raise ValidationException("Multipart request does not contain exactly 2 parts")
+ if len(webin) >= 2 and not webin.has_key("atom") and not webin.has_key("payload"):
+ raise ValidationException("Multipart request must contain Content-Dispositions with names 'atom' and 'payload'")
+ if len(webin) > 0 and not allow_multipart:
+ raise ValidationException("Multipart request not permitted in this context")
+
+ # if we get to here then we have a valid multipart or no multipart
+ is_multipart = False
+ is_empty = False
+ if len(webin) != 2: # if it is not multipart
+ # FIXME: this is reading everything in, and should be re-evaluated for performance/scalability
+ data = request.environ['wsgi.input'].read(int(request.environ['CONTENT_LENGTH']))
+
+ if data is None or data.strip() == "": # FIXME: this does not look safe to scale
+ if allow_empty:
+ ssslog.info("Validating an empty deposit (could be a control operation)")
+ is_empty = True
+ else:
+ raise ValidationException("No content sent to the server")
+ else:
+ ssslog.info("Validating a multipart deposit")
+ is_multipart = True
+
+ is_entry = False
+ content_type = mapped_headers.get("CONTENT-TYPE")
+ if content_type is not None and content_type.startswith("application/atom+xml"):
+ ssslog.info("Validating a atom-only deposit")
+ is_entry = True
+
+ if not is_entry and not is_multipart and not is_empty:
+ ssslog.info("Validating a binary deposit")
+
+ section = entry_section if is_entry else multipart_section if is_multipart else empty_section if is_empty else binary_section
+
+ # now validate the http headers
+ h.validate(mapped_headers, section)
+
+ except ValidationException as e:
+ raise SwordError(error_uri=Errors.bad_request, msg=e.message)
+
+ def get_deposit(self, auth=None, atom_only=False):
+ # FIXME: this reads files into memory, and therefore does not scale
+ # FIXME: this does not deal with the Media Part headers on a multipart deposit
+ """
+ Take a web.py web object and extract from it the parameters and content required for a SWORD deposit. This
+ includes determining whether this is an Atom Multipart request or not, and extracting the atom/payload where
+ appropriate. It also includes extracting the HTTP headers which are relevant to deposit, and for those not
+ supplied providing their defaults in the returned DepositRequest object
+ """
+ d = DepositRequest()
+
+ # map the webpy headers to something more standard
+ mapped_headers = self._map_webpy_headers(request.environ)
+
+ # get the headers that have been provided. Any headers which have not been provided will
+ # will have default values applied
+ h = HttpHeaders()
+ d.set_from_headers(h.get_sword_headers(mapped_headers))
+
+ if d.content_type.startswith("application/atom+xml"):
+ atom_only=True
+
+ empty_request = False
+ if d.content_length == 0:
+ empty_request = True
+ if d.content_length > config.max_upload_size:
+ raise SwordError(error_uri=Errors.max_upload_size_exceeded,
+ msg="Max upload size is " + config.max_upload_size +
+ "; incoming content length was " + str(cl))
+
+ # find out if this is a multipart or not
+ is_multipart = False
+
+ # FIXME: these headers aren't populated yet, because the webpy api doesn't
+ # appear to have a mechanism to retrieve them. urgh.
+ entry_part_headers = {}
+ media_part_headers = {}
+ webin = request.POST
+ if len(webin) == 2:
+ ssslog.info("Received multipart deposit request")
+ d.atom = webin['atom']
+ # FIXME: this reads the payload into memory, we need to sort that out
+ # read the zip file from the base64 encoded string
+ d.content = base64.decodestring(webin['payload'])
+ is_multipart = True
+ elif not empty_request:
+ # if this wasn't a multipart, and isn't an empty request, then the data is in web.data(). This could be a binary deposit or
+ # an atom entry deposit - reply on the passed/determined argument to determine which
+ if atom_only:
+ ssslog.info("Received Entry deposit request")
+ # FIXME: this is reading everything in, and should be re-evaluated for performance/scalability
+ data = request.environ['wsgi.input'].read(int(request.environ['CONTENT_LENGTH']))
+ d.atom = data
+ else:
+ ssslog.info("Received Binary deposit request")
+ # FIXME: this is reading everything in, and should be re-evaluated for performance/scalability
+ data = request.environ['wsgi.input'].read(int(request.environ['CONTENT_LENGTH']))
+ d.content = data
+
+ if is_multipart:
+ d.filename = h.extract_filename(media_part_headers)
+ else:
+ d.filename = h.extract_filename(mapped_headers)
+
+ # now just attach the authentication data and return
+ d.auth = auth
+ return d
+
+ def get_delete(self, web, auth=None):
+ """
+ Take a web.py web object and extract from it the parameters and content required for a SWORD delete request.
+ It mainly extracts the HTTP headers which are relevant to delete, and for those not supplied provides thier
+ defaults in the returned DeleteRequest object
+ """
+ d = DeleteRequest()
+
+ # map the webpy headers to something more standard
+ mapped_headers = self._map_webpy_headers(web.ctx.environ)
+
+ h = HttpHeaders()
+ d.set_from_headers(h.get_sword_headers(mapped_headers))
+
+ # now just attach the authentication data and return
+ d.auth = auth
+ return d
+
+ # Request Routing Methods (used by URL Routing)
+ ###############################################
+
+ def service_document(self, sub_path=None):
+ http_method = request.environ['REQUEST_METHOD']
+ if http_method == "GET":
+ return self._GET_service_document(sub_path)
+ else:
+ abort(405, "Method Not Allowed")
+ return
+
+ def collection(self, path=None):
+ http_method = request.environ['REQUEST_METHOD']
+ if http_method == "GET":
+ return self._GET_collection(path)
+ elif http_method == "POST":
+ return self._POST_collection(path)
+ else:
+ abort(405, "Method Not Allowed")
+ return
+
+ def media_resource(self, path=None): pass
+ def container(self, path=None): pass
+ def statement(self, path=None): pass
+
+ def aggregation(self, path=None): pass
+ def part(self, path=None): pass
+ def webui(self, path=None): pass
+
# SWORD Protocol Operations
###########################
-
- def service_document(self, sub_path=None):
+
+ def _GET_service_document(self, sub_path=None):
"""
GET the service document - returns an XML document
- sub_path - the path provided for the sub-service document
@@ -103,3 +286,29 @@
sd = ss.service_document(sub_path)
response.content_type = "text/xml"
return sd
+
+ def _GET_collection(self, path=None):
+ """
+ GET a representation of the collection in XML
+ Args:
+ - collection: The ID of the collection as specified in the requested URL
+ Returns an XML document with some metadata about the collection and the contents of that collection
+ """
+ ssslog.debug("GET on Collection (list collection contents); Incoming HTTP headers: " + str(request.environ))
+
+ # authenticate
+ try:
+ auth = self.http_basic_authenticate()
+ except SwordError as e:
+ return self.manage_error(e)
+
+ # if we get here authentication was successful and we carry on (we don't care who authenticated)
+ ss = SwordServer(config, auth)
+ cl = ss.list_collection(path)
+ response.content_type = "text/xml"
+ return cl
+
+ def _POST_collection(self, path=None):
+ pass
+
+
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: SVN c. m. f. t. SWORD-A. p. <swo...@li...> - 2012-01-12 22:31:41
|
Revision: 443
http://sword-app.svn.sourceforge.net/sword-app/?rev=443&view=rev
Author: richard-jones
Date: 2012-01-12 22:31:35 +0000 (Thu, 12 Jan 2012)
Log Message:
-----------
add routing configuration documentation for pylons
Modified Paths:
--------------
sss/branches/sss-2/PYLONS.txt
Modified: sss/branches/sss-2/PYLONS.txt
===================================================================
--- sss/branches/sss-2/PYLONS.txt 2012-01-12 21:57:04 UTC (rev 442)
+++ sss/branches/sss-2/PYLONS.txt 2012-01-12 22:31:35 UTC (rev 443)
@@ -7,4 +7,43 @@
routing.py
-... document routes here ...
+"""Routes configuration
+
+The more specific and detailed routes should be defined first so they
+may take precedent over the more generic routes. For more information
+refer to the routes manual at http://routes.groovie.org/docs/
+"""
+from pylons import config
+from routes import Mapper
+
+def make_map():
+ """Create, configure and return the routes Mapper"""
+ map = Mapper(directory=config['pylons.paths']['controllers'],
+ always_scan=config['debug'])
+ map.minimization = False
+
+ # The ErrorController route (handles 404/500 error pages); it should
+ # likely stay at the top, ensuring it can always be resolved
+ map.connect('/error/{action}', controller='error')
+ map.connect('/error/{action}/{id}', controller='error')
+
+ # CUSTOM ROUTES HERE
+
+ map.connect('/', controller="sword", action="webui") # Home page, with an intro and some handy links
+ map.connect('/sd-uri', controller="sword", action="service_document") # From which to retrieve the service document
+ map.connect('/sd-uri/{sub_path}', controller="sword", action="service_document") # for sub-service documents
+ map.connect('/col-uri/{path}', controller="sword", action="collection") # Representing a Collection as listed in the service document
+ map.connect('/cont-uri/{path}', controller="sword", action="media_resource") # The URI used in atom:content@src
+ map.connect('/em-uri/{path}', controller="sword", action="media_resource") # The URI used in atom:link@rel=edit-media
+ map.connect('/edit-uri/{path}', controller="sword", action="container") # The URI used in atom:link@rel=edit
+ map.connect('/state-uri/{path}', controller="sword", action="statement") # The URI used in atom:link@rel=sword:statement
+
+ map.connect('/agg-uri/{path}', controller="sword", action="aggregation") # The URI used to represent the ORE aggregation
+
+ # NOT PART OF SWORD: sword says nothing about how components of the item are identified, but here we use the
+ # PART-URI prefix to denote parts of the object in the server
+ map.connect('/part-uri/{path}', controller="sword", action="part")
+ # NOT PART OF SWORD: for convenience to supply HTML pages of deposited content
+ map.connect('/html/{path}', controller="sword", action="webui")
+
+ return map
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: SVN c. m. f. t. SWORD-A. p. <swo...@li...> - 2012-01-12 21:57:11
|
Revision: 442
http://sword-app.svn.sourceforge.net/sword-app/?rev=442&view=rev
Author: richard-jones
Date: 2012-01-12 21:57:04 +0000 (Thu, 12 Jan 2012)
Log Message:
-----------
add opening shots at pylons controller; this supports the retrieval of service documents
Added Paths:
-----------
sss/branches/sss-2/PYLONS.txt
sss/branches/sss-2/sss/pylons_sword_controller.py
Added: sss/branches/sss-2/PYLONS.txt
===================================================================
--- sss/branches/sss-2/PYLONS.txt (rev 0)
+++ sss/branches/sss-2/PYLONS.txt 2012-01-12 21:57:04 UTC (rev 442)
@@ -0,0 +1,10 @@
+middleware.py
+
+You need to comment out the error handling code in middleware.py otherwise
+it will bork the sword error messages, and authentication with http basic will
+become impossible
+
+
+routing.py
+
+... document routes here ...
Added: sss/branches/sss-2/sss/pylons_sword_controller.py
===================================================================
--- sss/branches/sss-2/sss/pylons_sword_controller.py (rev 0)
+++ sss/branches/sss-2/sss/pylons_sword_controller.py 2012-01-12 21:57:04 UTC (rev 442)
@@ -0,0 +1,105 @@
+from pylons import request, response, session, tmpl_context as c
+from pylons.controllers.util import abort, redirect_to
+from pylons.controllers import WSGIController
+from pylons.templating import render_mako as render
+
+import re, base64, urllib, uuid
+from core import Auth, SWORDSpec, SwordError, AuthException, DepositRequest, DeleteRequest
+from negotiator import ContentNegotiator, AcceptParameters, ContentType
+from spec import Errors, HttpHeaders, ValidationException
+
+import logging
+ssslog = logging.getLogger(__name__)
+
+# create the global configuration and import the implementation classes
+from sss import Configuration
+config = Configuration()
+Authenticator = config.get_authenticator_implementation()
+SwordServer = config.get_server_implementation()
+
+__controller__ = "SwordController"
+
+HEADER_MAP = {
+ HttpHeaders.in_progress : "HTTP_IN_PROGRESS",
+ HttpHeaders.metadata_relevant : "HTTP_METADATA_RELEVANT",
+ HttpHeaders.on_behalf_of : "HTTP_ON_BEHALF_OF"
+}
+
+class SwordController(WSGIController):
+
+ # WSGI Controller Stuff
+ #######################
+
+ def __call__(self, environ, start_response):
+ """Invoke the Controller"""
+ # WSGIController.__call__ dispatches to the Controller method
+ # the request is routed to. This routing information is
+ # available in environ['pylons.routes_dict']
+ return WSGIController.__call__(self, environ, start_response)
+
+ # Generically useful methods
+ ############################
+
+ def http_basic_authenticate(self):
+ # extract the appropriate HTTP headers
+ auth_header = request.environ.get('HTTP_AUTHORIZATION')
+ obo = request.environ.get(HEADER_MAP[HttpHeaders.on_behalf_of])
+
+ # if we're not supplied with an auth header, bounce
+ if auth_header is None:
+ ssslog.info("No auth header supplied; will return 401 with SSS realm")
+ response.headers['WWW-Authenticate'] = 'Basic realm="SSS"'
+ raise SwordError(status=401, empty=True)
+
+ # deconstruct the BASIC auth header
+ try:
+ auth_header = re.sub('^Basic ', '', auth_header)
+ username, password = base64.decodestring(auth_header).split(':')
+ ssslog.debug("successfully interpreted Basic Auth header")
+ except Exception as e:
+ # could be exceptions in either decoding the header or in doing a split
+ ssslog.error("unable to interpret authentication header: " + auth_header)
+ raise SwordError(error_uri=Errors.bad_request, msg="unable to interpret authentication header")
+
+ ssslog.info("Authentication details: " + str(username) + ":" + str(password) + "; On Behalf Of: " + str(obo))
+
+ authenticator = Authenticator(config)
+ try:
+ auth = authenticator.basic_authenticate(username, password, obo)
+ except AuthException as e:
+ if e.authentication_failed:
+ raise SwordError(status=401, empty=True)
+ elif e.target_owner_unknown:
+ raise SwordError(error_uri=Errors.target_owner_unknown, msg="unknown user " + str(obo) + " as on behalf of user")
+
+ return auth
+
+ def manage_error(self, sword_error):
+ response.status_int = sword_error.status
+ ssslog.info("Returning error status: " + str(sword_error.status))
+ if not sword_error.empty:
+ response.content_type = "text/xml"
+ return sword_error.error_document
+ return ""
+
+ # SWORD Protocol Operations
+ ###########################
+
+ def service_document(self, sub_path=None):
+ """
+ GET the service document - returns an XML document
+ - sub_path - the path provided for the sub-service document
+ """
+ ssslog.debug("GET on Service Document (retrieve service document); Incoming HTTP headers: " + str(request.environ))
+
+ # authenticate
+ try:
+ auth = self.http_basic_authenticate()
+ except SwordError as e:
+ return self.manage_error(e)
+
+ # if we get here authentication was successful and we carry on (we don't care who authenticated)
+ ss = SwordServer(config, auth)
+ sd = ss.service_document(sub_path)
+ response.content_type = "text/xml"
+ return sd
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: SVN c. m. f. t. SWORD-A. p. <swo...@li...> - 2012-01-12 19:16:43
|
Revision: 441
http://sword-app.svn.sourceforge.net/sword-app/?rev=441&view=rev
Author: richard-jones
Date: 2012-01-12 19:16:33 +0000 (Thu, 12 Jan 2012)
Log Message:
-----------
add contents of setup file
Modified Paths:
--------------
sss/branches/sss-2/setup.py
Modified: sss/branches/sss-2/setup.py
===================================================================
--- sss/branches/sss-2/setup.py 2012-01-11 20:54:58 UTC (rev 440)
+++ sss/branches/sss-2/setup.py 2012-01-12 19:16:33 UTC (rev 441)
@@ -0,0 +1,13 @@
+#!/usr/bin/env python
+
+from setuptools import setup, find_packages
+
+setup(
+ name='SSS',
+ version='2.0',
+ description='Simple Sword Server',
+ author='Richard Jones',
+ author_email='ric...@gm...',
+ url='http://www.swordapp.org/',
+ packages=find_packages(exclude=['tests']),
+)
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: SVN c. m. f. t. SWORD-A. p. <swo...@li...> - 2012-01-11 20:55:04
|
Revision: 440
http://sword-app.svn.sourceforge.net/sword-app/?rev=440&view=rev
Author: richard-jones
Date: 2012-01-11 20:54:58 +0000 (Wed, 11 Jan 2012)
Log Message:
-----------
fix up module init file
Modified Paths:
--------------
sss/branches/sss-2/sss/__init__.py
Modified: sss/branches/sss-2/sss/__init__.py
===================================================================
--- sss/branches/sss-2/sss/__init__.py 2012-01-11 20:40:14 UTC (rev 439)
+++ sss/branches/sss-2/sss/__init__.py 2012-01-11 20:54:58 UTC (rev 440)
@@ -1,3 +1,9 @@
+from info import __version__, __author__, __license__
+
+from core import Auth, AuthException, Authenticator, DeleteRequest, DeleteResponse, DepositRequest, DepositResponse, EntryDocument, SDCollection, SWORDRequest, ServiceDocument, Statement, SwordError, SwordServer
from config import Configuration, SSS_CONFIG_FILE
+from spec import Errors, HttpHeaders
+
import ingesters_disseminators
import negotiator
+import repository
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: SVN c. m. f. t. SWORD-A. p. <swo...@li...> - 2012-01-11 20:40:21
|
Revision: 439
http://sword-app.svn.sourceforge.net/sword-app/?rev=439&view=rev
Author: richard-jones
Date: 2012-01-11 20:40:14 +0000 (Wed, 11 Jan 2012)
Log Message:
-----------
enforce configurable interface between server implementation and web front end
Modified Paths:
--------------
sss/branches/sss-2/sss/config.py
sss/branches/sss-2/sss/core.py
sss/branches/sss-2/sss/repository.py
sss/branches/sss-2/sss/webpy.py
Modified: sss/branches/sss-2/sss/config.py
===================================================================
--- sss/branches/sss-2/sss/config.py 2012-01-11 20:02:27 UTC (rev 438)
+++ sss/branches/sss-2/sss/config.py 2012-01-11 20:40:14 UTC (rev 439)
@@ -116,8 +116,10 @@
],
"container_format_default" : {
"content_type" : "application/atom+xml;type=entry"
- }
+ },
+ "sword_server" : "sss.repository.SSS",
+ "authenticator" : "sss.repository.SSSAuthenticator"
}
"""
@@ -137,6 +139,12 @@
# it's a bit of a faff to include the code that was there before into
# the json string. How much does this matter?
+ def get_server_implementation(self):
+ return self._get_class(self.sword_server)
+
+ def get_authenticator_implementation(self):
+ return self._get_class(self.authenticator)
+
def get_container_formats(self):
default_params = self._get_accept_params(self.container_format_default)
Modified: sss/branches/sss-2/sss/core.py
===================================================================
--- sss/branches/sss-2/sss/core.py 2012-01-11 20:02:27 UTC (rev 438)
+++ sss/branches/sss-2/sss/core.py 2012-01-11 20:40:14 UTC (rev 439)
@@ -7,6 +7,139 @@
from sss_logging import logging
ssslog = logging.getLogger(__name__)
+class SwordServer(object):
+ """
+ The main SWORD Server class. This class deals with all the CRUD requests as provided by the web.py HTTP
+ handlers
+ """
+ def __init__(self, config, auth):
+ # get the configuration
+ self.configuration = config
+ self.auth_credentials = auth
+
+ def container_exists(self, path):
+ raise NotImplementedError()
+
+ def media_resource_exists(self, path):
+ raise NotImplementedError()
+
+ def service_document(self, path=None):
+ """
+ Construct the Service Document. This takes the set of collections that are in the store, and places them in
+ an Atom Service document as the individual entries
+ """
+ raise NotImplementedError()
+
+ def list_collection(self, path):
+ """
+ List the contents of a collection identified by the supplied id
+ """
+ raise NotImplementedError()
+
+ def deposit_new(self, path, deposit):
+ """
+ Take the supplied deposit and treat it as a new container with content to be created in the specified collection
+ Args:
+ -collection: the ID of the collection to be deposited into
+ -deposit: the DepositRequest object to be processed
+ Returns a DepositResponse object which will contain the Deposit Receipt or a SWORD Error
+ """
+ raise NotImplementedError()
+
+ def get_media_resource(self, path, accept_parameters):
+ """
+ Get a representation of the media resource for the given id as represented by the specified content type
+ -id: The ID of the object in the store
+ -content_type A ContentType object describing the type of the object to be retrieved
+ """
+ raise NotImplementedError()
+
+ def replace(self, path, deposit):
+ """
+ Replace all the content represented by the supplied id with the supplied deposit
+ Args:
+ - oid: the object ID in the store
+ - deposit: a DepositRequest object
+ Return a DepositResponse containing the Deposit Receipt or a SWORD Error
+ """
+ raise NotImplementedError()
+
+ def delete_content(self, path, delete):
+ """
+ Delete all of the content from the object identified by the supplied id. the parameters of the delete
+ request must also be supplied
+ - oid: The ID of the object to delete the contents of
+ - delete: The DeleteRequest object
+ Return a DeleteResponse containing the Deposit Receipt or the SWORD Error
+ """
+ raise NotImplementedError()
+
+ def add_content(self, path, deposit):
+ """
+ Take the supplied deposit and treat it as a new container with content to be created in the specified collection
+ Args:
+ -collection: the ID of the collection to be deposited into
+ -deposit: the DepositRequest object to be processed
+ Returns a DepositResponse object which will contain the Deposit Receipt or a SWORD Error
+ """
+ raise NotImplementedError()
+
+ def get_container(self, path, accept_parameters):
+ """
+ Get a representation of the container in the requested content type
+ Args:
+ -oid: The ID of the object in the store
+ -content_type A ContentType object describing the required format
+ Returns a representation of the container in the appropriate format
+ """
+ raise NotImplementedError()
+
+ def deposit_existing(self, path, deposit):
+ """
+ Deposit the incoming content into an existing object as identified by the supplied identifier
+ Args:
+ -oid: The ID of the object we are depositing into
+ -deposit: The DepositRequest object
+ Returns a DepositResponse containing the Deposit Receipt or a SWORD Error
+ """
+ raise NotImplementedError()
+
+ def delete_container(self, path, delete):
+ """
+ Delete the entire object in the store
+ Args:
+ -oid: The ID of the object in the store
+ -delete: The DeleteRequest object
+ Return a DeleteResponse object with may contain a SWORD Error document or nothing at all
+ """
+ raise NotImplementedError()
+
+ def get_statement(self, path):
+ raise NotImplementedError()
+
+ # NOT PART OF STANDARD, BUT USEFUL
+ # These are used by the webpy interface to provide easy access to certain
+ # resources. Not implementing them is fine. If they are not implemented
+ # then you just have to make sure that your file paths don't rely on the
+ # Part http handler
+
+ def get_part(self, path):
+ """
+ Get a file handle to the part identified by the supplied path
+ - path: The URI part which is the path to the file
+ """
+ raise NotImplementedError()
+
+ def get_edit_uri(self, path):
+ raise NotImplementedError()
+
+class Authenticator(object):
+ def __init__(self, config):
+ self.config = config
+
+ def basic_authenticate(self, username, password, obo):
+ raise NotImplementedError()
+
class EntryDocument(object):
def __init__(self, atom_id=None, alternate_uri=None, content_uri=None, edit_uri=None, se_uri=None, em_uris=[],
Modified: sss/branches/sss-2/sss/repository.py
===================================================================
--- sss/branches/sss-2/sss/repository.py 2012-01-11 20:02:27 UTC (rev 438)
+++ sss/branches/sss-2/sss/repository.py 2012-01-11 20:40:14 UTC (rev 439)
@@ -1,5 +1,5 @@
import os, hashlib, uuid, urllib
-from core import Statement, DepositResponse, MediaResourceResponse, DeleteResponse, SWORDSpec, Auth, AuthException, SwordError, ServiceDocument, SDCollection, EntryDocument
+from core import Statement, DepositResponse, MediaResourceResponse, DeleteResponse, SWORDSpec, Auth, AuthException, SwordError, ServiceDocument, SDCollection, EntryDocument, Authenticator, SwordServer
from spec import Namespaces, Errors
from lxml import etree
from datetime import datetime
@@ -10,9 +10,9 @@
from sss_logging import logging
ssslog = logging.getLogger(__name__)
-class SSSAuthenticator(object):
+class SSSAuthenticator(Authenticator):
def __init__(self, config):
- self.config = config
+ Authenticator.__init__(self, config)
def basic_authenticate(self, username, password, obo):
# we may have turned authentication off for development purposes
@@ -124,18 +124,14 @@
collection, id, fn = path.split("/", 2)
return collection, id, fn
-class SWORDServer(object):
+class SSS(SwordServer):
"""
The main SWORD Server class. This class deals with all the CRUD requests as provided by the web.py HTTP
handlers
"""
def __init__(self, config, auth):
+ SwordServer.__init__(self, config, auth)
- # get the configuration
- self.configuration = config
-
- self.auth_credentials = auth
-
# create a DAO for us to use
self.dao = DAO(self.configuration)
Modified: sss/branches/sss-2/sss/webpy.py
===================================================================
--- sss/branches/sss-2/sss/webpy.py 2012-01-11 20:02:27 UTC (rev 438)
+++ sss/branches/sss-2/sss/webpy.py 2012-01-11 20:40:14 UTC (rev 439)
@@ -8,13 +8,12 @@
from sss_logging import logging
ssslog = logging.getLogger(__name__)
-# create the global configuration
+# create the global configuration and import the implementation classes
from config import Configuration
config = Configuration()
+Authenticator = config.get_authenticator_implementation()
+SwordServer = config.get_server_implementation()
-# FIXME: we need to separate this dependence, probably in configuration
-from repository import SWORDServer, SSSAuthenticator
-
# Whether to run using SSL. This uses a default self-signed certificate. Change the paths to
# use an alternative set of keys
ssl = False
@@ -97,7 +96,7 @@
ssslog.info("Authentication details: " + str(username) + ":" + str(password) + "; On Behalf Of: " + str(obo))
- authenticator = SSSAuthenticator(config)
+ authenticator = Authenticator(config)
try:
auth = authenticator.basic_authenticate(username, password, obo)
except AuthException as e:
@@ -281,7 +280,7 @@
return self.manage_error(e)
# if we get here authentication was successful and we carry on (we don't care who authenticated)
- ss = SWORDServer(config, auth)
+ ss = SwordServer(config, auth)
sd = ss.service_document(sub_path)
web.header("Content-Type", "text/xml")
return sd
@@ -306,7 +305,7 @@
return self.manage_error(e)
# if we get here authentication was successful and we carry on (we don't care who authenticated)
- ss = SWORDServer(config, auth)
+ ss = SwordServer(config, auth)
cl = sss.list_collection(collection)
web.header("Content-Type", "text/xml")
return cl
@@ -332,7 +331,7 @@
# go ahead and process the deposit. Anything other than a success
# will be raised as a sword error
- ss = SWORDServer(config, auth)
+ ss = SwordServer(config, auth)
result = ss.deposit_new(collection, deposit)
# created
@@ -368,7 +367,7 @@
# NOTE: this method is not authenticated - we imagine sharing this URL with end-users who will just want
# to retrieve the content. It's only for the purposes of example, anyway
- ss = SWORDServer(config, None)
+ ss = SwordServer(config, None)
# first thing we need to do is check that there is an object to return, because otherwise we may throw a
# 406 Not Acceptable without looking first to see if there is even any media to content negotiate for
@@ -438,7 +437,7 @@
deposit = self.get_deposit(web, auth)
# now replace the content of the container
- ss = SWORDServer(config, auth)
+ ss = SwordServer(config, auth)
result = ss.replace(path, deposit)
# replaced
@@ -475,7 +474,7 @@
delete = self.get_delete(web, auth)
# carry out the delete
- ss = SWORDServer(config, auth)
+ ss = SwordServer(config, auth)
result = ss.delete_content(path, delete)
# just return, no need to give any more feedback
@@ -509,7 +508,7 @@
deposit = self.get_deposit(web, auth)
# if we get here authentication was successful and we carry on
- ss = SWORDServer(config, auth)
+ ss = SwordServer(config, auth)
result = ss.add_content(path, deposit)
web.header("Content-Type", "application/atom+xml;type=entry")
@@ -543,7 +542,7 @@
try:
auth = self.http_basic_authenticate(web)
- ss = SWORDServer(config, auth)
+ ss = SwordServer(config, auth)
# first thing we need to do is check that there is an object to return, because otherwise we may throw a
# 415 Unsupported Media Type without looking first to see if there is even any media to content negotiate for
@@ -594,7 +593,7 @@
# get the deposit object
deposit = self.get_deposit(web, auth)
- ss = SWORDServer(config, auth)
+ ss = SwordServer(config, auth)
result = ss.replace(path, deposit)
web.header("Location", result.location)
@@ -636,7 +635,7 @@
deposit = self.get_deposit(web, auth)
- ss = SWORDServer(config, auth)
+ ss = SwordServer(config, auth)
result = ss.deposit_existing(path, deposit)
# NOTE: spec says 201 Created for multipart and 200 Ok for metadata only
@@ -679,7 +678,7 @@
delete = self.get_delete(web, auth)
# do the delete
- ss = SWORDServer(config, auth)
+ ss = SwordServer(config, auth)
result = ss.delete_container(path, delete)
# no need to return any content
@@ -697,7 +696,7 @@
# authenticate
auth = self.http_basic_authenticate(web)
- ss = SWORDServer(config, auth)
+ ss = SwordServer(config, auth)
# first thing we need to do is check that there is an object to return, because otherwise we may throw a
# 415 Unsupported Media Type without looking first to see if there is even any media to content negotiate for
@@ -722,7 +721,7 @@
class Aggregation(SwordHttpHandler):
def GET(self, path):
# in this case we just redirect back to the Edit-URI with a 303 See Other
- ss = SWORDServer(config, None)
+ ss = SwordServer(config, None)
edit_uri = ss.get_edit_uri()
web.ctx.status = "303 See Other"
web.header("Content-Location", edit_uri)
@@ -749,7 +748,7 @@
Class to provide access to the component parts of the object on the server
"""
def GET(self, path):
- ss = SWORDServer(config, None)
+ ss = SwordServer(config, None)
# if we did, we can get hold of the media resource
fh = ss.get_part(path)
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: SVN c. m. f. t. SWORD-A. p. <swo...@li...> - 2012-01-11 20:02:33
|
Revision: 438
http://sword-app.svn.sourceforge.net/sword-app/?rev=438&view=rev
Author: richard-jones
Date: 2012-01-11 20:02:27 +0000 (Wed, 11 Jan 2012)
Log Message:
-----------
finish cleaning up repository.py, which is now fully decoupled from the core layer
Modified Paths:
--------------
sss/branches/sss-2/sss/repository.py
Modified: sss/branches/sss-2/sss/repository.py
===================================================================
--- sss/branches/sss-2/sss/repository.py 2012-01-11 19:34:47 UTC (rev 437)
+++ sss/branches/sss-2/sss/repository.py 2012-01-11 20:02:27 UTC (rev 438)
@@ -854,72 +854,16 @@
else:
return None
- def sword_error(self, uri, msg=None):
- entry = etree.Element(self.ns.SWORD + "error", nsmap=self.emap)
- entry.set("href", uri)
-
- author = etree.SubElement(entry, self.ns.ATOM + "author")
- name = etree.SubElement(author, self.ns.ATOM + "name")
- name.text = "SSS"
-
- title = etree.SubElement(entry, self.ns.ATOM + "title")
- title.text = "ERROR: " + uri
-
- # Date last updated (i.e. NOW)
- updated = etree.SubElement(entry, self.ns.ATOM + "updated")
- updated.text = datetime.now().strftime("%Y-%m-%dT%H:%M:%SZ")
-
- # Generator - identifier for this server software
- generator = etree.SubElement(entry, self.ns.ATOM + "generator")
- generator.set("uri", "http://www.swordapp.org/sss")
- generator.set("version", "1.0")
-
- # Summary field from metadata
- summary = etree.SubElement(entry, self.ns.ATOM + "summary")
- summary.set("type", "text")
- text = "Error Description: " + uri
- if msg is not None:
- text += " ; " + msg
- summary.text = text
-
- # treatment
- treatment = etree.SubElement(entry, self.ns.SWORD + "treatment")
- treatment.text = "processing failed"
-
- # verbose description
- vb = etree.SubElement(entry, self.ns.SWORD + "verboseDescription")
- vb.text = "Verbose Description Here"
-
- return etree.tostring(entry, pretty_print=True)
-
def check_delete_errors(self, delete):
# have we been asked to do a mediated delete, when this is not allowed?
if delete.auth is not None:
if delete.auth.obo is not None and not self.configuration.mediation:
raise SwordError(Errors.mediation_not_allowed)
- def check_mediated_error(self, deposit):
- # have we been asked to do a mediated deposit, when this is not allowed?
- if deposit.auth is not None:
- if deposit.auth.obo is not None and not self.configuration.mediation:
- spec = SWORDSpec(self.configuration)
- dr = DepositResponse()
- error_doc = self.sword_error(spec.error_mediation_not_allowed_uri)
- dr.error = error_doc
- dr.error_code = "412 Precondition Failed"
- return dr
- return None
-
def check_deposit_errors(self, deposit):
# have we been asked for an invalid package format
if deposit.packaging == self.configuration.error_content_package:
raise SwordError(error_uri=Errors.content, status=415, msg="Unsupported Packaging format specified")
- #spec = SWORDSpec(self.configuration)
- #dr = DepositResponse()
- #error_doc = self.sword_error(spec.error_content_uri, "Unsupported Packaging format specified")
- #dr.error = error_doc
- #dr.error_code = "415 Unsupported Media Type"
- #return dr
# have we been given an incompatible MD5?
if deposit.content_md5 is not None:
@@ -928,23 +872,11 @@
digest = m.hexdigest()
if digest != deposit.content_md5:
raise SwordError(error_uri=Errors.checksum_mismatch, msg="Content-MD5 header does not match file checksum")
- #spec = SWORDSpec(self.configuration)
- #dr = DepositResponse()
- #error_doc = self.sword_error(spec.error_checksum_mismatch_uri, "Content-MD5 header does not match file checksum")
- #dr.error = error_doc
- #dr.error_code = "412 Precondition Failed"
- #return dr
# have we been asked to do a mediated deposit, when this is not allowed?
if deposit.auth is not None:
if deposit.auth.obo is not None and not self.configuration.mediation:
raise SwordError(error_uri=Errors.mediation_not_allowed)
- #spec = SWORDSpec(self.configuration)
- #dr = DepositResponse()
- #error_doc = self.sword_error(spec.error_mediation_not_allowed_uri)
- #dr.error = error_doc
- #dr.error_code = "412 Precondition Failed"
- #return dr
return None
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: SVN c. m. f. t. SWORD-A. p. <swo...@li...> - 2012-01-11 19:34:57
|
Revision: 437
http://sword-app.svn.sourceforge.net/sword-app/?rev=437&view=rev
Author: richard-jones
Date: 2012-01-11 19:34:47 +0000 (Wed, 11 Jan 2012)
Log Message:
-----------
factor deposit receipt out of repository layer and into core
Modified Paths:
--------------
sss/branches/sss-2/sss/core.py
sss/branches/sss-2/sss/repository.py
Modified: sss/branches/sss-2/sss/core.py
===================================================================
--- sss/branches/sss-2/sss/core.py 2012-01-11 17:02:41 UTC (rev 436)
+++ sss/branches/sss-2/sss/core.py 2012-01-11 19:34:47 UTC (rev 437)
@@ -7,6 +7,140 @@
from sss_logging import logging
ssslog = logging.getLogger(__name__)
+class EntryDocument(object):
+
+ def __init__(self, atom_id=None, alternate_uri=None, content_uri=None, edit_uri=None, se_uri=None, em_uris=[],
+ packaging=[], state_uris=[], updated=None, dc_metadata={},
+ generator=("http://www.swordapp.org/sss", __version__),
+ verbose_description=None, treatment=None, original_deposit_uri=None, derived_resource_uris=[], nsmap=None):
+ self.ns = Namespaces()
+ self.drmap = {None: self.ns.ATOM_NS, "sword" : self.ns.SWORD_NS, "dcterms" : self.ns.DC_NS}
+ if nsmap is not None:
+ self.drmap = nsmap
+
+ self.dc_metadata = dc_metadata
+ self.atom_id = atom_id if atom_id is not None else "urn:uuid:" + str(uuid.uuid4())
+ self.updated = updated if updated is not None else datetime.now()
+ self.generator = generator
+ self.verbose_description = verbose_description
+ self.treatment = treatment
+ self.alternate_uri = alternate_uri
+ self.content_uri = content_uri
+ self.edit_uri = edit_uri
+ self.em_uris = em_uris
+ self.se_uri = se_uri
+ self.packaging = packaging
+ self.state_uris = state_uris
+ self.original_deposit_uri = original_deposit_uri
+ self.derived_resource_uris = derived_resource_uris
+
+ def serialise(self):
+ # the main entry document room
+ entry = etree.Element(self.ns.ATOM + "entry", nsmap=self.drmap)
+
+ # Title from metadata
+ title = etree.SubElement(entry, self.ns.ATOM + "title")
+ title.text = self.dc_metadata.get('title', ['untitled'])[0]
+
+ # Atom Entry ID
+ id = etree.SubElement(entry, self.ns.ATOM + "id")
+ id.text = self.atom_id
+
+ # Date last updated
+ updated = etree.SubElement(entry, self.ns.ATOM + "updated")
+ updated.text = self.updated.strftime("%Y-%m-%dT%H:%M:%SZ")
+
+ # Author field from metadata
+ author = etree.SubElement(entry, self.ns.ATOM + "author")
+ name = etree.SubElement(author, self.ns.ATOM + "name")
+ name.text = self.dc_metadata.get('creator', ["unknown"])[0]
+
+ # Summary field from metadata
+ summary = etree.SubElement(entry, self.ns.ATOM + "summary")
+ summary.set("type", "text")
+ summary.text = self.dc_metadata.get('abstract', [""])[0]
+
+
+ # Generator - identifier for this server software
+ gen = etree.SubElement(entry, self.ns.ATOM + "generator")
+ gen_uri, version = self.generator
+ gen.set("uri", gen_uri)
+ gen.set("version", version)
+
+ # now embed all the metadata as foreign markup
+ for field in self.dc_metadata.keys():
+ for v in self.dc_metadata[field]:
+ fdc = etree.SubElement(entry, self.ns.DC + field)
+ fdc.text = v
+
+ # verbose description
+ if self.verbose_description is not None:
+ vd = etree.SubElement(entry, self.ns.SWORD + "verboseDescription")
+ vd.text = self.verbose_description
+
+ # treatment
+ if self.treatment is not None:
+ treatment = etree.SubElement(entry, self.ns.SWORD + "treatment")
+ treatment.text = self.treatment
+
+ # link to splash page
+ if self.alternate_uri is not None:
+ alt = etree.SubElement(entry, self.ns.ATOM + "link")
+ alt.set("rel", "alternate")
+ alt.set("href", self.alternate_uri)
+
+ # Media Resource Content URI (Cont-URI)
+ if self.content_uri is not None:
+ content = etree.SubElement(entry, self.ns.ATOM + "content")
+ content.set("type", "application/zip")
+ content.set("src", self.content_uri)
+
+ # Edit-URI
+ if self.edit_uri is not None:
+ editlink = etree.SubElement(entry, self.ns.ATOM + "link")
+ editlink.set("rel", "edit")
+ editlink.set("href", self.edit_uri)
+
+ # EM-URI (Media Resource)
+ for uri, format in self.em_uris:
+ emfeedlink = etree.SubElement(entry, self.ns.ATOM + "link")
+ emfeedlink.set("rel", "edit-media")
+ if format is not None:
+ emfeedlink.set("type", format)
+ emfeedlink.set("href", uri)
+
+ # SE-URI (Sword edit - same as media resource)
+ if self.se_uri is not None:
+ selink = etree.SubElement(entry, self.ns.ATOM + "link")
+ selink.set("rel", "http://purl.org/net/sword/terms/add")
+ selink.set("href", self.se_uri)
+
+ # supported packaging formats
+ for disseminator in self.packaging:
+ sp = etree.SubElement(entry, self.ns.SWORD + "packaging")
+ sp.text = disseminator
+
+ for uri, format in self.state_uris:
+ state1 = etree.SubElement(entry, self.ns.ATOM + "link")
+ state1.set("rel", "http://purl.org/net/sword/terms/statement")
+ state1.set("type", format)
+ state1.set("href", uri)
+
+ # Original Deposit
+ if self.original_deposit_uri is not None:
+ od = etree.SubElement(entry, self.ns.ATOM + "link")
+ od.set("rel", "http://purl.org/net/sword/terms/originalDeposit")
+ od.set("href", self.original_deposit_uri)
+
+ # Derived Resources
+ if self.derived_resource_uris is not None:
+ for uri in self.derived_resource_uris:
+ dr = etree.SubElement(entry, self.ns.ATOM + "link")
+ dr.set("rel", "http://purl.org/net/sword/terms/derivedResource")
+ dr.set("href", uri)
+
+ return etree.tostring(entry, pretty_print=True)
+
class SDCollection(object):
def __init__(self, href, title, accept=["*/*"], multipart_accept=["*/*"],
description=None, accept_package=[], collection_policy=None,
Modified: sss/branches/sss-2/sss/repository.py
===================================================================
--- sss/branches/sss-2/sss/repository.py 2012-01-11 17:02:41 UTC (rev 436)
+++ sss/branches/sss-2/sss/repository.py 2012-01-11 19:34:47 UTC (rev 437)
@@ -1,10 +1,11 @@
import os, hashlib, uuid, urllib
-from core import Statement, DepositResponse, MediaResourceResponse, DeleteResponse, SWORDSpec, Auth, AuthException, SwordError, ServiceDocument, SDCollection
+from core import Statement, DepositResponse, MediaResourceResponse, DeleteResponse, SWORDSpec, Auth, AuthException, SwordError, ServiceDocument, SDCollection, EntryDocument
from spec import Namespaces, Errors
from lxml import etree
from datetime import datetime
from zipfile import ZipFile
from negotiator import AcceptParameters, ContentType
+from info import __version__
from sss_logging import logging
ssslog = logging.getLogger(__name__)
@@ -147,7 +148,7 @@
# build the namespace maps that we will use during serialisation
# self.sdmap = {None : self.ns.APP_NS, "sword" : self.ns.SWORD_NS, "atom" : self.ns.ATOM_NS, "dcterms" : self.ns.DC_NS}
self.cmap = {None: self.ns.ATOM_NS}
- self.drmap = {None: self.ns.ATOM_NS, "sword" : self.ns.SWORD_NS, "dcterms" : self.ns.DC_NS}
+ # self.drmap = {None: self.ns.ATOM_NS, "sword" : self.ns.SWORD_NS, "dcterms" : self.ns.DC_NS}
self.smap = {"rdf" : self.ns.RDF_NS, "ore" : self.ns.ORE_NS, "sword" : self.ns.SWORD_NS}
self.emap = {"sword" : self.ns.SWORD_NS, "atom" : self.ns.ATOM_NS}
@@ -332,7 +333,7 @@
# finally, assemble the deposit response and return
dr = DepositResponse()
- dr.receipt = receipt
+ dr.receipt = receipt.serialise()
dr.location = edit_uri
dr.created = True
@@ -482,7 +483,7 @@
# finally, assemble the deposit response and return
dr = DepositResponse()
- dr.receipt = receipt
+ dr.receipt = receipt.serialise()
dr.location = edit_uri
dr.created = True
return dr
@@ -535,7 +536,7 @@
# finally, assemble the delete response and return
dr = DeleteResponse()
- dr.receipt = receipt
+ dr.receipt = receipt.serialise()
return dr
def add_content(self, oid, deposit):
@@ -610,7 +611,7 @@
# finally, assemble the deposit response and return
dr = DepositResponse()
- dr.receipt = receipt
+ dr.receipt = receipt.serialise()
dr.location = location_uri
dr.created = True
return dr
@@ -741,7 +742,7 @@
# finally, assemble the deposit response and return
dr = DepositResponse()
- dr.receipt = receipt
+ dr.receipt = receipt.serialise()
# NOTE: in the spec, this is different for 6.7.2 and 6.7.3 (edit-iri and eiri respectively)
# in this case, we have always gone for the approach of 6.7.2, and contend that the
# spec is INCORRECT for 6.7.3 (also, section 9.3, which comes into play here
@@ -778,20 +779,9 @@
return uris
def augmented_receipt(self, receipt, original_deposit_uri, derived_resource_uris=[]):
- # Original Deposit
- if original_deposit_uri is not None:
- od = etree.SubElement(receipt, self.ns.ATOM + "link")
- od.set("rel", "http://purl.org/net/sword/terms/originalDeposit")
- od.set("href", original_deposit_uri)
-
- # Derived Resources
- if derived_resource_uris is not None:
- for uri in derived_resource_uris:
- dr = etree.SubElement(receipt, self.ns.ATOM + "link")
- dr.set("rel", "http://purl.org/net/sword/terms/derivedResource")
- dr.set("href", uri)
-
- return etree.tostring(receipt, pretty_print=True)
+ receipt.original_deposit_uri = original_deposit_uri
+ receipt.derived_resource_uris = derived_resource_uris
+ return receipt
def deposit_receipt(self, collection, id, deposit, statement, metadata):
"""
@@ -812,10 +802,11 @@
# the Cont-URI
cont_uri = self.um.cont_uri(collection, id)
- # the EM-URI and SE-IRI
+ # the EM-URI
em_uri = self.um.em_uri(collection, id)
+ em_uris = [(em_uri, None), (em_uri + ".atom", "application/atom+xml;type=feed")]
- # the Edit-URI
+ # the Edit-URI and SE-IRI
edit_uri = self.um.edit_uri(collection, id)
se_uri = edit_uri
@@ -825,6 +816,7 @@
# the two statement uris
atom_statement_uri = self.um.state_uri(collection, id, "atom")
ore_statement_uri = self.um.state_uri(collection, id, "ore")
+ state_uris = [(atom_statement_uri, "application/atom+xml;type=feed"), (ore_statement_uri, "application/rdf+xml")]
# ensure that there is a metadata object, and that it is populated with enough information to build the
# deposit receipt
@@ -837,99 +829,20 @@
if not metadata.has_key("abstract"):
metadata["abstract"] = ["Content deposited with SWORD client"]
- # Now assemble the deposit receipt
-
- # the main entry document room
- entry = etree.Element(self.ns.ATOM + "entry", nsmap=self.drmap)
-
- # Title from metadata
- title = etree.SubElement(entry, self.ns.ATOM + "title")
- title.text = metadata['title'][0]
-
- # Atom Entry ID
- id = etree.SubElement(entry, self.ns.ATOM + "id")
- id.text = drid
-
- # Date last updated (i.e. NOW)
- updated = etree.SubElement(entry, self.ns.ATOM + "updated")
- updated.text = datetime.now().strftime("%Y-%m-%dT%H:%M:%SZ")
-
- # Author field from metadata
- author = etree.SubElement(entry, self.ns.ATOM + "author")
- name = etree.SubElement(author, self.ns.ATOM + "name")
- name.text = metadata['creator'][0]
-
- # Summary field from metadata
- summary = etree.SubElement(entry, self.ns.ATOM + "summary")
- summary.set("type", "text")
- summary.text = metadata['abstract'][0]
-
-
- # Generator - identifier for this server software
- generator = etree.SubElement(entry, self.ns.ATOM + "generator")
- generator.set("uri", "http://www.swordapp.org/sss")
- generator.set("version", "1.0")
-
- # now embed all the metadata as foreign markup
- for field in metadata.keys():
- for v in metadata[field]:
- fdc = etree.SubElement(entry, self.ns.DC + field)
- fdc.text = v
-
- # verbose description
- vd = etree.SubElement(entry, self.ns.SWORD + "verboseDescription")
- vd.text = "SSS has done this, that and the other to process the deposit"
-
- # treatment
- treatment = etree.SubElement(entry, self.ns.SWORD + "treatment")
- treatment.text = "Treatment description"
-
- # link to splash page
- alt = etree.SubElement(entry, self.ns.ATOM + "link")
- alt.set("rel", "alternate")
- alt.set("href", splash_uri)
-
- # Media Resource Content URI (Cont-URI)
- content = etree.SubElement(entry, self.ns.ATOM + "content")
- content.set("type", "application/zip")
- content.set("src", cont_uri)
-
- # Edit-URI
- editlink = etree.SubElement(entry, self.ns.ATOM + "link")
- editlink.set("rel", "edit")
- editlink.set("href", edit_uri)
-
- # EM-URI (Media Resource)
- emlink = etree.SubElement(entry, self.ns.ATOM + "link")
- emlink.set("rel", "edit-media")
- emlink.set("href", em_uri)
- emfeedlink = etree.SubElement(entry, self.ns.ATOM + "link")
- emfeedlink.set("rel", "edit-media")
- emfeedlink.set("type", "application/atom+xml;type=feed")
- emfeedlink.set("href", em_uri + ".atom")
-
- # SE-URI (Sword edit - same as media resource)
- selink = etree.SubElement(entry, self.ns.ATOM + "link")
- selink.set("rel", "http://purl.org/net/sword/terms/add")
- selink.set("href", se_uri)
-
- # supported packaging formats
+ packaging = []
for disseminator in self.configuration.sword_disseminate_package:
- sp = etree.SubElement(entry, self.ns.SWORD + "packaging")
- sp.text = disseminator
+ packaging.append(disseminator)
- # now the two statement uris
- state1 = etree.SubElement(entry, self.ns.ATOM + "link")
- state1.set("rel", "http://purl.org/net/sword/terms/statement")
- state1.set("type", "application/atom+xml;type=feed")
- state1.set("href", atom_statement_uri)
+ verbose_description = "SSS has done this, that and the other to process the deposit"
+ treatment="Treatment description"
- state2 = etree.SubElement(entry, self.ns.ATOM + "link")
- state2.set("rel", "http://purl.org/net/sword/terms/statement")
- state2.set("type", "application/rdf+xml")
- state2.set("href", ore_statement_uri)
+ # Now assemble the deposit receipt
+ dr = EntryDocument(atom_id=drid, alternate_uri=splash_uri, content_uri=cont_uri,
+ edit_uri=edit_uri, se_uri=se_uri, em_uris=em_uris,
+ packaging=packaging, state_uris=state_uris, dc_metadata=metadata,
+ verbose_description=verbose_description, treatment=treatment)
- return entry
+ return dr
def get_statement(self, oid):
accept_parameters, path = self.um.interpret_statement_path(oid)
@@ -1156,7 +1069,7 @@
""" Store the supplied receipt document content in the object idenfied by the id in the specified collection """
drfile = os.path.join(self.configuration.store_dir, collection, id, "sss_deposit-receipt.xml")
if not isinstance(receipt, str):
- receipt = etree.tostring(receipt, pretty_print=True)
+ receipt = receipt.serialise()
self.save(drfile, receipt)
def store_metadata(self, collection, id, metadata):
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: SVN c. m. f. t. SWORD-A. p. <swo...@li...> - 2012-01-11 17:02:51
|
Revision: 436
http://sword-app.svn.sourceforge.net/sword-app/?rev=436&view=rev
Author: richard-jones
Date: 2012-01-11 17:02:41 +0000 (Wed, 11 Jan 2012)
Log Message:
-----------
factor service document handling out of the repository layer and into core
Modified Paths:
--------------
sss/branches/sss-2/sss/core.py
sss/branches/sss-2/sss/repository.py
Modified: sss/branches/sss-2/sss/core.py
===================================================================
--- sss/branches/sss-2/sss/core.py 2012-01-10 20:09:24 UTC (rev 435)
+++ sss/branches/sss-2/sss/core.py 2012-01-11 17:02:41 UTC (rev 436)
@@ -7,10 +7,108 @@
from sss_logging import logging
ssslog = logging.getLogger(__name__)
-# FIXME: SWORDSpec has a lot of webpy stuff in it; needs to be cleaned and
-# divided
+class SDCollection(object):
+ def __init__(self, href, title, accept=["*/*"], multipart_accept=["*/*"],
+ description=None, accept_package=[], collection_policy=None,
+ mediation=False, treatment=None, sub_service=[]):
+ self.href = href
+ self.title = title
+ self.description = description
+ self.accept = accept
+ self.multipart_accept = multipart_accept
+ self.accept_package = accept_package
+ self.collection_policy = collection_policy
+ self.mediation = mediation
+ self.treatment = treatment
+ self.sub_service = sub_service
+
+class ServiceDocument(object):
+ def __init__(self, version="2.0", max_upload_size=0, nsmap=None):
+ # set up the namespace declarations that will be used
+ self.ns = Namespaces()
+ self.sdmap = {None : self.ns.APP_NS, "sword" : self.ns.SWORD_NS, "atom" : self.ns.ATOM_NS, "dcterms" : self.ns.DC_NS}
+ if nsmap is not None:
+ self.sdmap = nsmap
+
+ self.version = version
+ self.max_upload_size = max_upload_size
+
+ self.workspaces = {}
+
+ def add_workspace(self, name, collections):
+ self.workspaces[name] = collections
+
+ def serialise(self):
+ # Start by creating the root of the service document, supplying to it the namespace map in this first instance
+ service = etree.Element(self.ns.APP + "service", nsmap=self.sdmap)
+
+ # version element
+ version = etree.SubElement(service, self.ns.SWORD + "version")
+ version.text = self.version
+
+ # max upload size
+ if self.max_upload_size != 0:
+ mus = etree.SubElement(service, self.ns.SWORD + "maxUploadSize")
+ mus.text = str(self.max_upload_size)
+
+ # workspace element
+ for ws in self.workspaces.keys():
+ workspace = etree.SubElement(service, self.ns.APP + "workspace")
+
+ # title element
+ title = etree.SubElement(workspace, self.ns.ATOM + "title")
+ title.text = ws
+
+ # now for each collection create a collection element
+ for col in self.workspaces[ws]:
+ collection = etree.SubElement(workspace, self.ns.APP + "collection")
+ collection.set("href", col.href)
+
+ # collection title
+ ctitle = etree.SubElement(collection, self.ns.ATOM + "title")
+ ctitle.text = col.title
+
+ for acc in col.accept:
+ accepts = etree.SubElement(collection, self.ns.APP + "accept")
+ accepts.text = acc
+
+ for acc in col.multipart_accept:
+ mraccepts = etree.SubElement(collection, self.ns.APP + "accept")
+ mraccepts.text = acc
+ mraccepts.set("alternate", "multipart-related")
+
+ # SWORD collection policy
+ collectionPolicy = etree.SubElement(collection, self.ns.SWORD + "collectionPolicy")
+ collectionPolicy.text = col.collection_policy
+
+ # Collection abstract
+ abstract = etree.SubElement(collection, self.ns.DC + "abstract")
+ abstract.text = col.description
+
+ # support for mediation
+ mediation = etree.SubElement(collection, self.ns.SWORD + "mediation")
+ mediation.text = "true" if col.mediation else "false"
+
+ # treatment
+ treatment = etree.SubElement(collection, self.ns.SWORD + "treatment")
+ treatment.text = col.treatment
+
+ # SWORD packaging formats accepted
+ for format in col.accept_package:
+ acceptPackaging = etree.SubElement(collection, self.ns.SWORD + "acceptPackaging")
+ acceptPackaging.text = format
+
+ # provide a sub service element if appropriate
+ for sub in col.sub_service:
+ subservice = etree.SubElement(collection, self.ns.SWORD + "service")
+ subservice.text = sub
+
+ # pretty print and return
+ return etree.tostring(service, pretty_print=True)
+
+
# REQUEST/RESPONSE CLASSES
#######################################################################
# These classes are used as the glue between the web.py web interface layer and the underlying sword server, allowing
Modified: sss/branches/sss-2/sss/repository.py
===================================================================
--- sss/branches/sss-2/sss/repository.py 2012-01-10 20:09:24 UTC (rev 435)
+++ sss/branches/sss-2/sss/repository.py 2012-01-11 17:02:41 UTC (rev 436)
@@ -1,5 +1,5 @@
import os, hashlib, uuid, urllib
-from core import Statement, DepositResponse, MediaResourceResponse, DeleteResponse, SWORDSpec, Auth, AuthException, SwordError
+from core import Statement, DepositResponse, MediaResourceResponse, DeleteResponse, SWORDSpec, Auth, AuthException, SwordError, ServiceDocument, SDCollection
from spec import Namespaces, Errors
from lxml import etree
from datetime import datetime
@@ -145,7 +145,7 @@
self.um = URIManager(self.configuration)
# build the namespace maps that we will use during serialisation
- self.sdmap = {None : self.ns.APP_NS, "sword" : self.ns.SWORD_NS, "atom" : self.ns.ATOM_NS, "dcterms" : self.ns.DC_NS}
+ # self.sdmap = {None : self.ns.APP_NS, "sword" : self.ns.SWORD_NS, "atom" : self.ns.ATOM_NS, "dcterms" : self.ns.DC_NS}
self.cmap = {None: self.ns.ATOM_NS}
self.drmap = {None: self.ns.ATOM_NS, "sword" : self.ns.SWORD_NS, "dcterms" : self.ns.DC_NS}
self.smap = {"rdf" : self.ns.RDF_NS, "ore" : self.ns.ORE_NS, "sword" : self.ns.SWORD_NS}
@@ -176,81 +176,62 @@
"""
use_sub = self.configuration.use_sub if path is None else False
- # Start by creating the root of the service document, supplying to it the namespace map in this first instance
- service = etree.Element(self.ns.APP + "service", nsmap=self.sdmap)
-
- # version element
- version = etree.SubElement(service, self.ns.SWORD + "version")
- version.text = self.configuration.sword_version
-
- # max upload size
- mus = etree.SubElement(service, self.ns.SWORD + "maxUploadSize")
- mus.text = str(self.configuration.max_upload_size)
-
- # workspace element
- workspace = etree.SubElement(service, self.ns.APP + "workspace")
-
- # title element
- title = etree.SubElement(workspace, self.ns.ATOM + "title")
- title.text = "Main Site"
-
- # now for each collection create a collection element
- for col in self.dao.get_collection_names():
- collection = etree.SubElement(workspace, self.ns.APP + "collection")
- collection.set("href", self.um.col_uri(col))
-
- # collection title
- ctitle = etree.SubElement(collection, self.ns.ATOM + "title")
- ctitle.text = "Collection " + col
-
+ service = ServiceDocument(version=self.configuration.sword_version,
+ max_upload_size=self.configuration.max_upload_size)
+
+ # now for each collection create an sdcollection
+ collections = []
+ for col_name in self.dao.get_collection_names():
+ href = self.um.col_uri(col_name)
+ title = "Collection " + col_name
+ policy = "Collection Policy"
+ abstract = "Collection Description"
+ mediation = self.configuration.mediation
+ treatment = "Treatment description"
+
+ # content types accepted
+ accept = []
+ multipart_accept = []
if not self.configuration.accept_nothing:
- # accepts declaration
if self.configuration.app_accept is not None:
for acc in self.configuration.app_accept:
- accepts = etree.SubElement(collection, self.ns.APP + "accept")
- accepts.text = acc
+ accept.append(acc)
if self.configuration.multipart_accept is not None:
for acc in self.configuration.multipart_accept:
- mraccepts = etree.SubElement(collection, self.ns.APP + "accept")
- mraccepts.text = acc
- mraccepts.set("alternate", "multipart-related")
- else:
- accepts = etree.SubElement(collection, self.ns.APP + "accept")
-
- # SWORD collection policy
- collectionPolicy = etree.SubElement(collection, self.ns.SWORD + "collectionPolicy")
- collectionPolicy.text = "Collection Policy"
-
- # Collection abstract
- abstract = etree.SubElement(collection, self.ns.DC + "abstract")
- abstract.text = "Collection Description"
-
- # support for mediation
- mediation = etree.SubElement(collection, self.ns.SWORD + "mediation")
- mediation.text = "true" if self.configuration.mediation else "false"
-
- # treatment
- treatment = etree.SubElement(collection, self.ns.SWORD + "treatment")
- treatment.text = "Treatment description"
-
+ multipart_accept.append(acc)
+
# SWORD packaging formats accepted
+ accept_package = []
for format in self.configuration.sword_accept_package:
- acceptPackaging = etree.SubElement(collection, self.ns.SWORD + "acceptPackaging")
- acceptPackaging.text = format
+ accept_package.append(format)
# provide a sub service element if appropriate
+ subservice = []
if use_sub:
- subservice = etree.SubElement(collection, self.ns.SWORD + "service")
- subservice.text = self.um.sd_uri(True)
+ subservice.append(self.um.sd_uri(True))
+
+ col = SDCollection(href=href, title=title, accept=accept, multipart_accept=multipart_accept,
+ description=abstract, accept_package=accept_package,
+ collection_policy=policy, mediation=mediation, treatment=treatment,
+ sub_service=subservice)
+
+ collections.append(col)
+
+ service.add_workspace("Main Site", collections)
- # pretty print and return
- return etree.tostring(service, pretty_print=True)
+ # serialise and return
+ return service.serialise()
def list_collection(self, id):
"""
List the contents of a collection identified by the supplied id
"""
+ # FIXME: would be good to have this in the generic implementation (section
+ # 6.2), but that's a future task; for the time being this remains a
+ # repository specific piece of code, and a generic implementation will
+ # be done later
+
# create an empty feed element for the collection
feed = etree.Element(self.ns.ATOM + "feed", nsmap=self.cmap)
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|