diff --git a/feedgenerator.py b/feedgenerator.py index c8c4146..a44f4e4 100644 --- a/feedgenerator.py +++ b/feedgenerator.py @@ -10,7 +10,7 @@ ''' from lxml import etree -from email.Utils import formatdate +from datetime import datetime class FeedGenerator: @@ -18,30 +18,29 @@ class FeedGenerator: ## ATOM # http://www.atomenabled.org/developers/syndication/ # required - __atom_id = '' - __atom_title = '' - __atom_updated = '' + __atom_id = None + __atom_title = None + __atom_updated = datetime.utcnow().isoformat('T')+'Z' # recommended - __atom_author = '' # {name*, uri, email} - __atom_link = '' + __atom_author = None # {name*, uri, email} + __atom_link = None # {href*, rel, type, hreflang, title, length} - ''' # optional - category = '' - contributor = '' - generator = '' - icon = '' - logo = '' - rights = '' - subtitle = '' - + __atom_category = None # {term*, schema, label} + __atom_contributor = None + __atom_generator = {'value':'Lernfunk3 FeedGenerator'} #{value*,uri,version} + __atom_icon = None + __atom_logo = None + __atom_rights = None + __atom_subtitle = None ## RSS - title - link - description + __rss_title = None + __rss_link = None + __rss_description = None + ''' category cloud copyright @@ -64,8 +63,24 @@ class FeedGenerator: # rss -> channel ''' - def __init__(self): - self.__atom_updated = formatdate() + + def __ensure_format(self, val, allowed, required, allowed_values={}): + if not val: + return None + # Make shure that we have a list of dicts. Even if there is only one. + if not isinstance(val, list): + val = [val] + for elem in val: + if not isinstance(elem, dict): + raise ValueError('Invalid data (value is no dictionary)') + if not set(elem.keys()) <= allowed: + raise ValueError('Data contains invalid keys') + if not set(elem.keys()) >= required: + raise ValueError('Data contains not all required keys') + for k,v in allowed_values.iteritems(): + if elem.get(k) and not elem[k] in v: + raise ValueError('Invalid value for %s' % k ) + return val def atom_str(self): @@ -81,34 +96,78 @@ class FeedGenerator: updated.text = self.__atom_updated # Add author elements - if self.__atom_author: - for a in self.__atom_author: - # Atom requires a name. Skip elements without. - if not a.get('name'): - continue - author = etree.SubElement(feed, 'author') - name = etree.SubElement(author, 'name') - name.text = a.get('name') - if a.get('email'): - email = etree.SubElement(author, 'email') - email.text = a.get('email') - if a.get('uri'): - email = etree.SubElement(author, 'url') - email.text = a.get('uri') + for a in self.__atom_author or []: + # Atom requires a name. Skip elements without. + if not a.get('name'): + continue + author = etree.SubElement(feed, 'author') + name = etree.SubElement(author, 'name') + name.text = a.get('name') + if a.get('email'): + email = etree.SubElement(author, 'email') + email.text = a.get('email') + if a.get('uri'): + email = etree.SubElement(author, 'url') + email.text = a.get('uri') - if self.__atom_link: - for l in self.__atom_link: - link = etree.SubElement(feed, 'link', href=l['href']) - if l.get('rel'): - link.attrib['rel'] = l['rel'] - if l.get('type'): - link.attrib['type'] = l['type'] - if l.get('hreflang'): - link.attrib['hreflang'] = l['hreflang'] - if l.get('title'): - link.attrib['title'] = l['title'] - if l.get('length'): - link.attrib['length'] = l['length'] + for l in self.__atom_link or []: + link = etree.SubElement(feed, 'link', href=l['href']) + if l.get('rel'): + link.attrib['rel'] = l['rel'] + if l.get('type'): + link.attrib['type'] = l['type'] + if l.get('hreflang'): + link.attrib['hreflang'] = l['hreflang'] + if l.get('title'): + link.attrib['title'] = l['title'] + if l.get('length'): + link.attrib['length'] = l['length'] + + for c in self.__atom_category or []: + cat = etree.SubElement(feed, 'category', term=c['term']) + if c.get('schema'): + cat.attrib['schema'] = c['schema'] + if c.get('label'): + cat.attrib['label'] = c['label'] + + # Add author elements + for c in self.__atom_contributor or []: + # Atom requires a name. Skip elements without. + if not c.get('name'): + continue + contrib = etree.SubElement(feed, 'contributor') + name = etree.SubElement(contrib, 'name') + name.text = c.get('name') + if c.get('email'): + email = etree.SubElement(contrib, 'email') + email.text = c.get('email') + if c.get('uri'): + email = etree.SubElement(contrib, 'url') + email.text = c.get('uri') + + if self.__atom_generator: + generator = etree.SubElement(feed, 'generator') + generator.text = self.__atom_generator['value'] + if self.__atom_generator.get('uri'): + generator.attrib['uri'] = self.__atom_generator['uri'] + if self.__atom_generator.get('version'): + generator.attrib['version'] = self.__atom_generator['version'] + + if self.__atom_icon: + icon = etree.SubElement(feed, 'icon') + icon.text = self.__atom_icon + + if self.__atom_logo: + logo = etree.SubElement(feed, 'logo') + logo.text = self.__atom_logo + + if self.__atom_rights: + rights = etree.SubElement(feed, 'rights') + rights.text = self.__atom_rights + + if self.__atom_subtitle: + subtitle = etree.SubElement(feed, 'subtitle') + subtitle.text = self.__atom_subtitle return etree.tostring(feed, pretty_print=True) ''' @@ -129,7 +188,7 @@ class FeedGenerator: return self.__atom_id - def author(self, author=None, replace=False): + def author(self, author=None, replace=False, **kwargs): '''Get or set autor data. An author element is a dict containing a name, an email adress and a uri. Name is mandatory for ATOM, email is mandatory for RSS. @@ -139,26 +198,28 @@ class FeedGenerator: Example:: - >>> author( { 'name'='John Doe', 'email'='jdoe@example.com' } ) - [{'name'='John Doe','email'='jdoe@example.com'}] + >>> author( { 'name':'John Doe', 'email':'jdoe@example.com' } ) + [{'name':'John Doe','email':'jdoe@example.com'}] - >>> author([{'name'='John Doe'},{'name'='Max'}]) - [{'name'='John Doe'},{'name'='Max'}] + >>> author([{'name':'Mr. X'},{'name':'Max'}]) + [{'name':'John Doe','email':'jdoe@example.com'}, + {'name':'John Doe'}, {'name':'Max'}] + + >>> author( name='John Doe', email='jdoe@example.com', replace=True ) + [{'name':'John Doe','email':'jdoe@example.com'}] ''' + if author is None and kwargs: + author = kwargs if not author is None: - if not isinstance(author, list): - author = [ author ] - for a in author: - if not isinstance(a, dict): - raise ValueError('Invalid author data (author is no dict)') - if not set(a.keys()) <= set(['name', 'email', 'uri']): - raise ValueError('Invalid author data') - self.__atom_author = author + if replace or self.__atom_author is None: + self.__atom_author = [] + self.__atom_author += self.__ensure_format( author, + set(['name', 'email', 'uri']), set(['name'])) return self.__atom_author - def link(self, link=None, replace=False): + def link(self, link=None, replace=False, **kwargs): '''Get or set link data. An link element is a dict with the fields href, rel, type, hreflang, title, and length. Href is mandatory for ATOM. @@ -170,23 +231,76 @@ class FeedGenerator: link(...) ''' + if link is None and kwargs: + link = kwargs if not link is None: - if not isinstance(link, list): - link = [ link ] - for l in link: - if not isinstance(l, dict): - raise ValueError('Invalid link data (link is no dict)') - if not set(l.keys()) <= set(['href', 'rel', 'type', 'hreflang', 'title', 'length']): - raise ValueError('Invalid link data') - if not set(l.keys()) >= set(['href']): - raise ValueError('Invalid link data (no href)') - if l.get('rel') and l['rel'] not in \ - ['alternate', 'enclosure', 'related', 'self', 'via']: - raise ValueError('Invalid rel type') - self.__atom_link = link + if replace or self.__atom_link is None: + self.__atom_link = [] + self.__atom_link += self.__ensure_format( link, + set(['href', 'rel', 'type', 'hreflang', 'title', 'length']), + set(['href']), + {'rel':['alternate', 'enclosure', 'related', 'self', 'via']} ) return self.__atom_link + def category(self, category=None, replace=False, **kwargs): + if category is None and kwargs: + category = kwargs + if not category is None: + if replace or self.__atom_category is None: + self.__atom_category = [] + self.__atom_category += self.__ensure_format( + category, + set(['term', 'schema', 'label']), + set(['term']) ) + return self.__atom_category + + + def contributor(self, contributor=None, replace=False, **kwargs): + if contributor is None and kwargs: + contributor = kwargs + if not contributor is None: + if replace or self.__atom_contributor is None: + self.__atom_contributor = [] + self.__atom_contributor += self.__ensure_format( contributor, + set(['name', 'email', 'uri']), set(['name'])) + return self.__atom_contributor + + + def generator(self, generator=None, version=None, uri=None): + if not generator is None: + self.__atom_generator = {'value':generator} + if not version in None: + self.__atom_generator['version'] = version + if not uri in None: + self.__atom_generator['uri'] = uri + return self.__atom_generator + + + def icon(self, icon=None): + if not icon is None: + self.__atom_icon = icon + return self.__atom_icon + + + def logo(self, logo=None): + if not logo is None: + self.__atom_logo = logo + return self.__atom_logo + + + def rights(self, rights=None): + if not rights is None: + self.__atom_rights = rights + return self.__atom_rights + + + def subtitle(self, subtitle=None): + if not subtitle is None: + self.__atom_subtitle = subtitle + return self.__atom_subtitle + + class FeedEntry: ''' @@ -229,8 +343,16 @@ class FeedEntry: if __name__ == '__main__': fg = FeedGenerator() - fg.id('123') + fg.id('http://lernfunk.de/_MEDIAID_123') fg.title('Testfeed') fg.author( {'name':'Lars Kiesow','email':'lkiesow@uos.de'} ) - fg.link( {'href':'http://example.com', 'rel':'alternate'} ) + fg.link( href='http://example.com', rel='alternate' ) + fg.category(term='test') + fg.contributor( name='Lars Kiesow', email='lkiesow@uos.de' ) + fg.contributor( name='John Doe', email='jdoe@example.com' ) + fg.icon('http://ex.com/icon.jpg') + fg.logo('http://ex.com/logo.jpg') + fg.rights('cc-by') + fg.subtitle('This is a cool feed!') + fg.link( href='http://larskiesow.de/test.atom', rel='self' ) print fg.atom_str()