diff --git a/resync/resource_container.py b/resync/resource_container.py index d2ed00f..0aec561 100644 --- a/resync/resource_container.py +++ b/resync/resource_container.py @@ -14,6 +14,22 @@ import collections.abc from .w3c_datetime import datetime_to_str +def _str_datetime_now(x='now'): + """Return datetime string for use with time attributes. + + Handling depends on input: + 'now' - returns datetime for now + other string - no change, return same value + otherwise - assume datetime value, generate string + """ + if (x == 'now' or not isinstance(x, str)): + # Now, this is wht datetime_to_str() with no arg gives + return datetime_to_str(x) + else: + # Assume valid datetime string + return x + + class ResourceContainer(object): """Class containing resource-like objects. @@ -26,6 +42,11 @@ class ResourceContainer(object): - uri is optional identifier of this container object - capability_name - name of this capability + The properties md_from, md_at, md_until and md_completed are datetime + strings. The setters acceopt either a string value (not checked), the value + 'now' to generate the current datetime, or otherwise an integer timestamp + value that is converted to a string. + Derived classes may add extra functionality such as len() etc.. However, any code designed to work with any ResourceContainer should use only the core functionality. @@ -45,19 +66,16 @@ class ResourceContainer(object): Baseline implementation use iterator given by resources property. """ - return(iter(self.resources)) + return iter(self.resources) def __getitem__(self, index): """Feed through for __getitem__ of resources property.""" - return(self.resources[index]) + return self.resources[index] @property def capability(self): - """Get/set the attribute.""" - if ('capability' in self.md): - return(self.md['capability']) - else: - return(None) + """Get/set the attribute, None if not set.""" + return self.md.get('capability') @capability.setter def capability(self, capability): @@ -65,65 +83,53 @@ class ResourceContainer(object): @property def md_from(self): - """Get/set the attribute.""" - if ('md_from' in self.md): - return(self.md['md_from']) - else: - return(None) + """Get/set the attribute, None if not set.""" + return self.md.get('md_from') @md_from.setter def md_from(self, md_from): - self.md['md_from'] = self._str_datetime_now(md_from) + self.md['md_from'] = _str_datetime_now(md_from) @property def md_until(self): - """Get/set the attribute.""" - if ('md_until' in self.md): - return(self.md['md_until']) - else: - return(None) + """Get/set the attribute, None if not set.""" + return self.md.get('md_until') @md_until.setter def md_until(self, md_until): - self.md['md_until'] = self._str_datetime_now(md_until) + self.md['md_until'] = _str_datetime_now(md_until) @property def md_at(self): """Get/set the attribute.""" - if ('md_completed' in self.md): - return(self.md['md_completed']) - else: - return(None) + """Get/set the attribute, None if not set.""" + return self.md.get('md_completed') @md_completed.setter def md_completed(self, md_completed): - self.md['md_completed'] = self._str_datetime_now(md_completed) + self.md['md_completed'] = _str_datetime_now(md_completed) def link(self, rel): """Look for link with specified rel, return else None.""" for link in self.ln: if ('rel' in link and link['rel'] == rel): - return(link) - return(None) + return link + return None 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) + return link def link_set(self, rel, href, **atts): """Set/create link with specified rel, set href and any other attributes. @@ -132,16 +138,16 @@ class ResourceContainer(object): also defines the type attributes and others are permitted also. See description of allowed formats in - http://www.openarchives.org/rs/resourcesync.html#DocumentFormats + http://www.openarchives.org/rs/resourcesync#DocumentFormats """ link = self.link(rel) - if (link is not None): - # overwrite current value - link['href'] = href - else: + if link is None: # create new link link = {'rel': rel, 'href': href} self.ln.append(link) + else: + # overwrite current value + link['href'] = href for k in atts: link[k] = atts[k] @@ -232,29 +238,3 @@ class ResourceContainer(object): pruned2.append(r) self.resources = pruned2 return(n) - - def __str__(self): - """Return string of all resources in order given by interator.""" - s = '' - for resource in self: - s += str(resource) + "\n" - return(s) - - def _str_datetime_now(self, x=None): - """Return datetime string for use with time attributes. - - Handling depends on input: - 'now' - returns datetime for now - number - assume datetime values, generate string - other - no change, return same value - """ - if (x == 'now'): - # Now, this is wht datetime_to_str() with no arg gives - return(datetime_to_str()) - try: - # Test for number - junk = x + 0.0 - return datetime_to_str(x) - except TypeError: - # Didn't look like a number, treat as string - return x diff --git a/tests/test_resource_container.py b/tests/test_resource_container.py index d60031f..844bb8e 100644 --- a/tests/test_resource_container.py +++ b/tests/test_resource_container.py @@ -1,6 +1,8 @@ +"""Tests for resync.resource_container.""" + import unittest from resync.resource import Resource -from resync.resource_container import ResourceContainer +from resync.resource_container import ResourceContainer, _str_datetime_now class TestResourceContainer(unittest.TestCase): @@ -8,9 +10,12 @@ class TestResourceContainer(unittest.TestCase): def test01_create_and_add(self): rc = ResourceContainer(resources=[]) self.assertEqual(len(rc.resources), 0, "empty") - rc.resources.append(Resource('a', timestamp=1)) - rc.resources.append(Resource('b', timestamp=2)) + rc.add(Resource('a', timestamp=1)) + rc.add(Resource('b', timestamp=2)) self.assertEqual(len(rc.resources), 2, "two resources") + # Add two more + rc.add([Resource('c'), Resource('d')]) + self.assertEqual(len(rc.resources), 4, "four resources") def test02_iter(self): rc = ResourceContainer(resources=[]) @@ -93,3 +98,37 @@ class TestResourceContainer(unittest.TestCase): self.assertEqual(rc.up, None) rc.up = "up_uri" self.assertEqual(rc.up, "up_uri") + + def test09_capability(self): + """Test capability property.""" + rc = ResourceContainer() + self.assertEqual(rc.capability, None) + rc.capability = "fixit" + self.assertEqual(rc.capability, 'fixit') + + def test10_link_set(self): + """Test link_set method.""" + rc = ResourceContainer() + self.assertEqual(rc.link('alink'), None) # non-spec rel should be supported + rc.link_set('alink', 'uri:l', extra1='one', extra2='two') + ln = rc.link('alink') + self.assertEqual(rc.link('alink'), {'rel': 'alink', 'href': 'uri:l', + 'extra1': 'one', 'extra2': 'two'}) + # Can update existing link + rc.link_set('alink', 'uri:new', extra1='1') + self.assertEqual(rc.link('alink'), {'rel': 'alink', 'href': 'uri:new', + 'extra1': '1', 'extra2': 'two'}) + + def test11_index(self): + """Test index property.""" + rc = ResourceContainer() + self.assertEqual(rc.index, None) + rc.index = 'uri:i' + self.assertEqual(rc.index, 'uri:i') + + def test12_str_datetime_now(self): + """Test _str_datetime_now function.""" + self.assertTrue(isinstance(_str_datetime_now(), str)) + self.assertTrue(isinstance(_str_datetime_now('now'), str)) + self.assertEqual(_str_datetime_now(0), '1970-01-01T00:00:00Z') + self.assertEqual(_str_datetime_now('2020-12-24T16:01:00Z'), '2020-12-24T16:01:00Z')