Add better support for links in Resource objects
This commit is contained in:
parent
69024d4a07
commit
5394e41670
7
TODO.md
7
TODO.md
@ -6,4 +6,9 @@ See also issues on github tracker. Other big threads that need attention are:
|
||||
- work through and test creation of Resource and Change Dump ZIPs
|
||||
- add use of a Resource and Change Dump ZIPs
|
||||
- add archives (ResoourceListArchive, ChangeListArchive, ResourceDumpArchive, ChangeDumpArchive)
|
||||
- ...
|
||||
- consider breaking out handling of links that is somewhat duplicated in resource.py and resource_container.py
|
||||
|
||||
Issues with specification
|
||||
=========================
|
||||
|
||||
- v1.0 spec Figure 4 shows Capability List indexes but these are not allowed per section 9
|
||||
|
||||
@ -304,27 +304,70 @@ class Resource(object):
|
||||
return(link)
|
||||
return(None)
|
||||
|
||||
def link_add(self,**atts):
|
||||
"""Add a link (ln) with specified attributes
|
||||
|
||||
Simply appends to the set of ln links associated with this
|
||||
object. In all cases the arguments should include rel and
|
||||
href values and an exception will be thrown if that is not
|
||||
the case. Other attributes are descibed in
|
||||
def link_href(self,rel):
|
||||
"""Look for link with specified rel, return href from it or None"""
|
||||
link = self.link(rel)
|
||||
if (link is not None):
|
||||
link = link['href']
|
||||
return(link)
|
||||
|
||||
def link_set(self,rel,href,**atts):
|
||||
"""Set/create link with specified rel, set href and any other attributes
|
||||
|
||||
Any link element must have both rel and href values, the specification
|
||||
also defines the type attributes and others are permitted also. See
|
||||
description of allowed formats in
|
||||
|
||||
http://www.openarchives.org/rs/resourcesync.html#DocumentFormats
|
||||
|
||||
Be aware that adding links to a Resource object will
|
||||
significantly increase the size of the object.
|
||||
"""
|
||||
# sanity check
|
||||
if ('rel' not in atts or
|
||||
'href' not in atts):
|
||||
raise ValueError("Resource link must have at least rel and href attributes")
|
||||
if (self.ln is None):
|
||||
# automagically create a self.ln list
|
||||
self.ln = []
|
||||
link = atts.copy()
|
||||
self.ln.append(link)
|
||||
link = None
|
||||
else:
|
||||
link = self.link(rel)
|
||||
if (link is not None):
|
||||
# overwrite current value
|
||||
link['href'] = href
|
||||
else:
|
||||
# create new link
|
||||
link = {'rel':rel,'href':href}
|
||||
self.ln.append(link)
|
||||
for k in atts:
|
||||
link[k] = atts[k]
|
||||
|
||||
@property
|
||||
def describedby(self):
|
||||
"""Convenient access to <rs:ln rel="describedby" href="uri">"""
|
||||
return(self.link_href('describedby'))
|
||||
|
||||
@describedby.setter
|
||||
def describedby(self,uri):
|
||||
"""Set ResourceSync Description link to given URI"""
|
||||
self.link_set('describedby',uri)
|
||||
|
||||
@property
|
||||
def up(self):
|
||||
"""Get the URI of any ResourceSync rel="up" link"""
|
||||
return(self.link_href('up'))
|
||||
|
||||
@up.setter
|
||||
def up(self,uri):
|
||||
"""Set ResourceSync rel="up" link to given URI"""
|
||||
self.link_set('up',uri)
|
||||
|
||||
@property
|
||||
def index(self):
|
||||
"""Get the URI of and ResourceSync rel="index" link"""
|
||||
return(self.link_href('index'))
|
||||
|
||||
@index.setter
|
||||
def index(self,uri):
|
||||
"""Set index link to given URI"""
|
||||
self.link_set('index',uri)
|
||||
|
||||
@property
|
||||
def basename(self):
|
||||
|
||||
@ -44,6 +44,10 @@ class ResourceContainer(object):
|
||||
"""
|
||||
return(iter(self.resources))
|
||||
|
||||
def __getitem__(self,index):
|
||||
"""Feed through for __getitem__ of resources property"""
|
||||
return(self.resources[index])
|
||||
|
||||
@property
|
||||
def capability(self):
|
||||
"""Get/set the <rs:md capability="" .../> attribute"""
|
||||
@ -123,7 +127,10 @@ class ResourceContainer(object):
|
||||
"""Set/create link with specified rel, set href and any other attributes
|
||||
|
||||
Any link element must have both rel and href values, the specification
|
||||
also defines the type attributes and others are permitted also
|
||||
also defines the type attributes and others are permitted also. See
|
||||
description of allowed formats in
|
||||
|
||||
http://www.openarchives.org/rs/resourcesync.html#DocumentFormats
|
||||
"""
|
||||
link = self.link(rel)
|
||||
if (link is not None):
|
||||
|
||||
@ -1,18 +1,24 @@
|
||||
"""ResourceSync Description object
|
||||
"""ResourceSync Source Description object
|
||||
|
||||
A ResourceSync Description enumerates the Capability Lists
|
||||
offered by a Source. Since a Source has one Capability List
|
||||
per set of resources that it distinguishes, the
|
||||
ResourceSync Description will enumerate as many Capability
|
||||
Lists as the Source has distinct sets of resources.
|
||||
A ResourceSync Source Description enumerates the Capability
|
||||
Lists offered by a Source. Since a Source has one Capability
|
||||
List per set of resources that it distinguishes, the
|
||||
ResourceSync Source Description will enumerate as many
|
||||
Capability Lists as the Source has distinct sets of resources.
|
||||
|
||||
The ResourceSync Description can only be based on the
|
||||
<urlset> format.
|
||||
The ResourceSync Source Description is based on the <urlset>
|
||||
format. The specification allows for a very large Source Descriptions
|
||||
to use and index based on a <sitemapindex>, though this is probably
|
||||
rarely necessary!
|
||||
|
||||
See: http://www.openarchives.org/rs/resourcesync#ResourceSyncDesc
|
||||
There is no meaning in the order of description of sets of resources
|
||||
in a Source Description so the default is to store these descriptions
|
||||
as a set (ResourceSet).
|
||||
|
||||
May also contain metadata and links like other ResourceSync
|
||||
documents.
|
||||
|
||||
See: http://www.openarchives.org/rs/resourcesync#SourceDesc
|
||||
"""
|
||||
|
||||
import collections
|
||||
@ -27,7 +33,8 @@ class SourceDescription(ListBaseWithIndex):
|
||||
Will admit only one resource with any given URI.
|
||||
|
||||
Storage is unordered but the iterator imposes a canonical order
|
||||
which is currently alphabetical by URI.
|
||||
which is currently alphabetical by URI. Pass in a resources object
|
||||
to override.
|
||||
"""
|
||||
|
||||
def __init__(self, resources=None, md=None, ln=None):
|
||||
@ -59,7 +66,7 @@ class SourceDescription(ListBaseWithIndex):
|
||||
r = Resource( uri=capability_list.uri,
|
||||
capability=capability_list.capability_name )
|
||||
if (capability_list.describedby is not None):
|
||||
r.link_add( rel='describedby', href=capability_list.describedby )
|
||||
r.link_set( rel='describedby', href=capability_list.describedby )
|
||||
else:
|
||||
r = Resource( uri=capability_list,
|
||||
capability='capabilitylist')
|
||||
|
||||
@ -115,7 +115,10 @@ class TestExamplesFromSpec(unittest.TestCase):
|
||||
sd=SourceDescription()
|
||||
sd.read(uri='resync/test/testdata/examples_from_spec/resourcesync_ex_7.xml')
|
||||
self.assertEqual( len(sd.resources), 1, '1 capability list' )
|
||||
#FIXIME
|
||||
cl=sd.resources[0]
|
||||
self.assertEqual( cl.uri, 'http://example.com/dataset1/capabilitylist.xml' )
|
||||
self.assertEqual( cl.capability, 'resourcelist' )
|
||||
self.assertEqual( cl.describedby, 'http://example.com/info_about_set1_of_resources.xml' )
|
||||
|
||||
def test_ex_8(self):
|
||||
"""resourcesync_ex_8 is a simple Resource List Index with 2 Resource Lists"""
|
||||
@ -128,6 +131,21 @@ class TestExamplesFromSpec(unittest.TestCase):
|
||||
self.assertEqual( sms, ['http://example.com/resourcelist-part1.xml',
|
||||
'http://example.com/resourcelist-part2.xml'] )
|
||||
|
||||
# Examples 9, 10, 11 in the spec are not XML documents
|
||||
|
||||
def test_ex_12(self):
|
||||
"""resourcesync_ex_12 is a Source Description that talks about 3 sets of resources"""
|
||||
sd=SourceDescription()
|
||||
sd.read(uri='resync/test/testdata/examples_from_spec/resourcesync_ex_12.xml')
|
||||
self.assertEqual( len(sd), 3 )
|
||||
self.assertEqual( sd.uris(), ['http://example.com/capabilitylist1.xml',
|
||||
'http://example.com/capabilitylist2.xml',
|
||||
'http://example.com/capabilitylist3.xml'] )
|
||||
cl1=sd['http://example.com/capabilitylist1.xml']
|
||||
self.assertEqual( cl1.capability, 'capabilitylist' )
|
||||
self.assertEqual( cl1.describedby, 'http://example.com/info_about_set1_of_resources.xml')
|
||||
|
||||
|
||||
##### BUILD EXAMPLES #####
|
||||
|
||||
def test_build_ex_1(self):
|
||||
@ -149,7 +167,7 @@ class TestExamplesFromSpec(unittest.TestCase):
|
||||
r2 = Resource(uri='http://example.com/res2',
|
||||
lastmod='2013-01-02T14:00:00Z',
|
||||
md5='1e0d5cb8ef6ba40c99b14c0237be735e')
|
||||
r2.link_add(rel="duplicate",href="http://mirror.example.com/res2")
|
||||
r2.link_set(rel="duplicate",href="http://mirror.example.com/res2")
|
||||
rl.add( r2 )
|
||||
ex_xml = self._open_ex('resourcesync_ex_2').read()
|
||||
self._assert_xml_equal( rl.as_xml(), ex_xml )
|
||||
@ -209,7 +227,7 @@ class TestExamplesFromSpec(unittest.TestCase):
|
||||
sd.describedby = 'http://example.com/info-about-source.xml'
|
||||
r = Resource( uri='http://example.com/dataset1/capabilitylist.xml',
|
||||
capability='capabilitylist' )
|
||||
r.link_add( rel='describedby',
|
||||
r.link_set( rel='describedby',
|
||||
href='http://example.com/info_about_set1_of_resources.xml' )
|
||||
sd.add( r )
|
||||
ex_xml = self._open_ex('resourcesync_ex_7').read()
|
||||
@ -231,7 +249,7 @@ class TestExamplesFromSpec(unittest.TestCase):
|
||||
ex_xml = self._open_ex('resourcesync_ex_8').read()
|
||||
self._assert_xml_equal( rli.as_xml(), ex_xml )
|
||||
|
||||
# Examples 9, 10, 11 in the spec are not XML documents
|
||||
# Examples 9, 10, 11 in the spec are not XML documents
|
||||
|
||||
def test_build_ex_12(self):
|
||||
"""Source Description document with describedby links"""
|
||||
@ -326,7 +344,7 @@ class TestExamplesFromSpec(unittest.TestCase):
|
||||
length=4765,
|
||||
md_at="2013-01-03T09:00:00Z",
|
||||
md_completed="2013-01-03T09:02:00Z" )
|
||||
z1.link_add( rel="contents",
|
||||
z1.link_set( rel="contents",
|
||||
href="http://example.com/resourcedump_manifest-part1.xml",
|
||||
mime_type="application/xml" )
|
||||
rd.add( z1 )
|
||||
@ -335,7 +353,7 @@ class TestExamplesFromSpec(unittest.TestCase):
|
||||
length=9875,
|
||||
md_at="2013-01-03T09:01:00Z",
|
||||
md_completed="2013-01-03T09:03:00Z" )
|
||||
z2.link_add( rel="contents",
|
||||
z2.link_set( rel="contents",
|
||||
href="http://example.com/resourcedump_manifest-part2.xml",
|
||||
mime_type="application/xml" )
|
||||
rd.add( z2 )
|
||||
@ -344,7 +362,7 @@ class TestExamplesFromSpec(unittest.TestCase):
|
||||
length=2298,
|
||||
md_at="2013-01-03T09:03:00Z",
|
||||
md_completed="2013-01-03T09:04:00Z" )
|
||||
z3.link_add( rel="contents",
|
||||
z3.link_set( rel="contents",
|
||||
href="http://example.com/resourcedump_manifest-part3.xml",
|
||||
mime_type="application/xml" )
|
||||
rd.add( z3 )
|
||||
|
||||
Loading…
Reference in New Issue
Block a user