Add better support for links in Resource objects

This commit is contained in:
Simeon Warner 2014-06-18 14:23:51 +03:00
parent 69024d4a07
commit 5394e41670
5 changed files with 113 additions and 33 deletions

View File

@ -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

View File

@ -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):

View File

@ -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):

View File

@ -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')

View File

@ -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 )