Added extension support and converted PodcastGenerator to extension

This commit is contained in:
Lars Kiesow 2013-05-05 18:17:44 +02:00
parent e9eb3b5e95
commit 840dc7e5b8
8 changed files with 200 additions and 78 deletions

View file

@ -9,7 +9,6 @@
'''
from feedgen.feed import FeedGenerator
from feedgen.podcast import PodcastGenerator
import sys
@ -32,7 +31,7 @@ if __name__ == '__main__':
arg = sys.argv[1]
fg = PodcastGenerator() if arg.endswith('podcast') else FeedGenerator()
fg = FeedGenerator()
fg.id('http://lernfunk.de/_MEDIAID_123')
fg.title('Testfeed')
fg.author( {'name':'Lars Kiesow','email':'lkiesow@uos.de'} )
@ -64,16 +63,21 @@ if __name__ == '__main__':
elif arg == 'rss':
print fg.rss_str(pretty=True)
elif arg == 'podcast':
fg.itunes_author('Lars Kiesow')
fg.itunes_category('Technology', 'Podcasting')
fg.itunes_explicit('no')
fg.itunes_complete('no')
fg.itunes_new_feed_url('http://example.com/new-feed.rss')
fg.itunes_owner('John Doe', 'john@example.com')
fg.itunes_summary('Lorem ipsum dolor sit amet, consectetur adipiscing elit. Verba tu fingas et ea dicas, quae non sentias?')
fe.itunes_author('Lars Kiesow')
# Load the podcast extension. It will automatically be loaded for all
# entries in the feed, too. Thus also for our “fe”.
fg.load_extension('podcast')
fg.podcast.itunes_author('Lars Kiesow')
fg.podcast.itunes_category('Technology', 'Podcasting')
fg.podcast.itunes_explicit('no')
fg.podcast.itunes_complete('no')
fg.podcast.itunes_new_feed_url('http://example.com/new-feed.rss')
fg.podcast.itunes_owner('John Doe', 'john@example.com')
fg.podcast.itunes_summary('Lorem ipsum dolor sit amet, ' + \
'consectetur adipiscing elit. ' + \
'Verba tu fingas et ea dicas, quae non sentias?')
fe.podcast.itunes_author('Lars Kiesow')
print fg.podcast_str(pretty=True)
print fg.rss_str(pretty=True)
elif arg.endswith('atom'):
fg.atom_file(arg)
elif arg.endswith('rss'):

View file

@ -50,8 +50,11 @@ class FeedEntry(object):
__rss_source = None
__rss_title = None
# Extension list:
__extensions = {}
def atom_entry(self, feed):
def atom_entry(self, feed, extensions=True):
'''Insert an ATOM entry into a existing XML structure. Normally you
would pass the feed node of an ATOM feed XML to this function.
@ -139,10 +142,15 @@ class FeedEntry(object):
rights = etree.SubElement(feed, 'rights')
rights.text = self.__atom_rights
if extensions:
for ext in self.__extensions.values() or []:
if ext.get('atom'):
entry = ext['inst'].extend_atom(entry)
return entry
def rss_entry(self, feed):
def rss_entry(self, feed, extensions=True):
'''Insert an RSS item into a existing XML structure. Normally you
would pass the channel node of an RSS feed XML to this function.
@ -184,6 +192,12 @@ class FeedEntry(object):
pubDate = etree.SubElement(channel, 'pubDate')
pubDate.text = self.__rss_pubDate.strftime(
'%a, %e %b %Y %H:%M:%S %z')
if extensions:
for ext in self.__extensions.values() or []:
if ext.get('rss'):
entry = ext['inst'].extend_rss(entry)
return entry
@ -575,3 +589,34 @@ class FeedEntry(object):
if not ttl is None:
self.__rss_ttl = int(ttl)
return self.__rss_ttl
def load_extension(self, name, atom=True, rss=True):
'''Load a specific extension by name.
:param name: Name of the extension to load.
:param atom: If the extension should be used for ATOM feeds.
:param rss: If the extension should be used for RSS feeds.
'''
# Check loaded extensions
if not isinstance(self.__extensions, dict):
self.__extensions = {}
if name in self.__extensions.keys():
raise ImportError('Extension already loaded')
# Load extension
extname = name[0].upper() + name[1:] + 'EntryExtension'
# Try to import extension from dedicated module for entry:
try:
supmod = __import__('feedgen.ext.%s_entry' % name)
extmod = getattr(supmod.ext, name + '_entry')
except ImportError:
# Try the FeedExtension module instead
supmod = __import__('feedgen.ext.%s' % name)
extmod = getattr(supmod.ext, name)
ext = getattr(extmod, extname)
extinst = ext()
setattr(self, name, extinst)
self.__extensions[name] = {'inst':extinst,'atom':atom,'rss':rss}

6
feedgen/ext/__init__.py Normal file
View file

@ -0,0 +1,6 @@
# -*- coding: utf-8 -*-
"""
===========
feedgen.ext
===========
"""

39
feedgen/ext/base.py Normal file
View file

@ -0,0 +1,39 @@
# -*- coding: utf-8 -*-
'''
feedgen.ext.base
~~~~~~~~~~~~~~~~
Basic FeedGenerator which does nothing but provides all necessary methods.
:copyright: 2013, Lars Kiesow <lkiesow@uos.de>
:license: FreeBSD and LGPL, see license.* for more details.
'''
class BaseExtension(object):
'''Basic FeedGenerator extension.
'''
def extend_rss(self, feed):
'''Create an RSS feed xml structure containing all previously set fields.
:param feed: The feed xml root element.
:returns: The feed root element.
'''
return feed
def extend_atom(self, feed, doc=None):
'''Create an ATOM feed xml structure containing all previously set
fields.
:param feed: The feed xml root element.
:returns: The feed root element.
'''
return feed, doc
class BaseEntryExtension(BaseExtension):
'''Basic FeedEntry extension.
'''

View file

@ -14,12 +14,11 @@ from lxml import etree
from datetime import datetime
import dateutil.parser
import dateutil.tz
from feedgen.feed import FeedGenerator
from feedgen.podcast_entry import PodcastEntry
from feedgen.ext.base import BaseExtension
from feedgen.util import ensure_format
class PodcastGenerator(FeedGenerator):
class PodcastExtension(BaseExtension):
'''FeedGenerator extension for podcasts.
'''
@ -40,21 +39,18 @@ class PodcastGenerator(FeedGenerator):
def __create_podcast(self):
def extend_rss(self, rss_feed):
'''Create an RSS feed xml structure containing all previously set fields.
:returns: Tuple containing the feed root element and the element tree.
'''
rss_feed, _ = super(PodcastGenerator,self)._create_rss()
ITUNES_NS = 'http://www.itunes.com/dtds/podcast-1.0.dtd'
# Replace the root element to add the itunes namespace
feed = etree.Element('rss', version='2.0',
nsmap={
'atom' :'http://www.w3.org/2005/Atom',
'itunes':ITUNES_NS} )
nsmap = rss_feed.nsmap
nsmap['itunes'] = ITUNES_NS
feed = etree.Element('rss', version='2.0', nsmap=nsmap )
feed[:] = rss_feed[:]
channel = feed[0]
doc = etree.ElementTree(feed)
if self.__itunes_author:
author = etree.SubElement(channel, '{%s}author' % ITUNES_NS)
@ -102,28 +98,7 @@ class PodcastGenerator(FeedGenerator):
summary = etree.SubElement(channel, '{%s}summary' % ITUNES_NS)
summary.text = self.__itunes_summary
return feed, doc
def podcast_str(self, pretty=False):
'''Generates an RSS feed and returns the feed XML as string.
:param pretty: If the feed should be split into multiple lines and
properly indented.
:returns: String representation of the RSS feed.
'''
feed, doc = self.__create_podcast()
return etree.tostring(feed, pretty_print=pretty)
def podcast_file(self, filename):
'''Generates an RSS feed and write the resulting XML to a file.
:param filename: Name of file to write.
'''
feed, doc = self.__create_podcast()
with open(filename, 'w') as f:
doc.write(f)
return feed
def itunes_author(self, itunes_author=None):
@ -313,20 +288,6 @@ class PodcastGenerator(FeedGenerator):
return self.__itunes_summary
def add_entry(self, podcastEntry=None):
'''This method will add a new entry to the podcast. If the podcastEntry
argument is omittet a new PodcstEntry object is created automatically.
This is the prefered way to add new entries to a feed.
:param podcastEntry: PodcastEntry object to add.
:returns: PodcastEntry object created or passed to this function.
'''
if podcastEntry is None:
podcastEntry = PodcastEntry()
super(PodcastGenerator, self).add_entry( podcastEntry )
return podcastEntry
_itunes_categories = {
'Arts': [ 'Design', 'Fashion & Beauty', 'Food', 'Literature',
'Performing Arts', 'Visual Arts' ],

View file

@ -14,11 +14,11 @@ from lxml import etree
from datetime import datetime
import dateutil.parser
import dateutil.tz
from feedgen.entry import FeedEntry
from feedgen.ext.base import BaseExtension
from feedgen.util import ensure_format
class PodcastEntry(FeedEntry):
class PodcastEntryExtension(BaseExtension):
'''FeedEntry extension for podcasts.
'''
@ -36,13 +36,11 @@ class PodcastEntry(FeedEntry):
__itunes_summary = None
def rss_entry(self, feed):
'''Insert an RSS item into a existing XML structure. Normally you
would pass the channel node of an RSS feed XML to this function.
def extend_rss(self, entry):
'''Add additional fields to an RSS item.
:param feed: The XML element to use as parent node for the item.
:param feed: The RSS item XML element to use.
'''
entry = super(PodcastEntry,self).rss_entry(feed)
ITUNES_NS = 'http://www.itunes.com/dtds/podcast-1.0.dtd'
if self.__itunes_author:

View file

@ -69,9 +69,12 @@ class FeedGenerator(object):
__rss_ttl = None
__rss_webMaster = None
# Extension list:
__extensions = {}
def _create_atom(self):
def _create_atom(self, extensions=True):
'''Create a ATOM feed xml structure containing all previously set fields.
:returns: Tuple containing the feed root element and the element tree.
@ -81,7 +84,6 @@ class FeedGenerator(object):
feed.attrib['{http://www.w3.org/XML/1998/namespace}lang'] = \
self.__atom_feed_xml_lang
doc = etree.ElementTree(feed)
if not ( self.__atom_id and self.__atom_title and self.__atom_updated ):
raise ValueError('Required fields not set')
id = etree.SubElement(feed, 'id')
@ -165,41 +167,50 @@ class FeedGenerator(object):
subtitle = etree.SubElement(feed, 'subtitle')
subtitle.text = self.__atom_subtitle
if extensions:
for ext in self.__extensions.values() or []:
if ext.get('atom'):
feed = ext['inst'].extend_atom(feed)
for entry in self.__feed_entries:
entry.atom_entry(feed)
doc = etree.ElementTree(feed)
return feed, doc
def atom_str(self, pretty=False):
def atom_str(self, pretty=False, extensions=True):
'''Generates an ATOM feed and returns the feed XML as string.
:param pretty: If the feed should be split into multiple lines and
properly indented.
:param extensions: Enable or disable the loaded extensions for the xml
generation (default: enabled).
:returns: String representation of the ATOM feed.
'''
feed, doc = self._create_atom()
feed, doc = self._create_atom(extensions=extensions)
return etree.tostring(feed, pretty_print=pretty)
def atom_file(self, filename):
def atom_file(self, filename, extensions=True):
'''Generates an ATOM feed and write the resulting XML to a file.
:param filename: Name of file to write.
:param extensions: Enable or disable the loaded extensions for the xml
generation (default: enabled).
'''
feed, doc = self._create_atom()
feed, doc = self._create_atom(extensions=extensions)
with open(filename, 'w') as f:
doc.write(f)
def _create_rss(self):
def _create_rss(self, extensions=True):
'''Create an RSS feed xml structure containing all previously set fields.
:returns: Tuple containing the feed root element and the element tree.
'''
feed = etree.Element('rss', version='2.0',
nsmap={'atom': 'http://www.w3.org/2005/Atom'} )
doc = etree.ElementTree(feed)
channel = etree.SubElement(feed, 'channel')
if not ( self.__rss_title and self.__rss_link and self.__rss_description ):
raise ValueError('Required fields not set')
@ -306,29 +317,39 @@ class FeedGenerator(object):
webMaster = etree.SubElement(channel, 'webMaster')
webMaster.text = self.__rss_webMaster
if extensions:
for ext in self.__extensions.values() or []:
if ext.get('rss'):
feed = ext['inst'].extend_rss(feed)
for entry in self.__feed_entries:
entry.rss_entry(channel)
doc = etree.ElementTree(feed)
return feed, doc
def rss_str(self, pretty=False):
def rss_str(self, pretty=False, extensions=True):
'''Generates an RSS feed and returns the feed XML as string.
:param pretty: If the feed should be split into multiple lines and
properly indented.
:param extensions: Enable or disable the loaded extensions for the xml
generation (default: enabled).
:returns: String representation of the RSS feed.
'''
feed, doc = self._create_rss()
feed, doc = self._create_rss(extensions=extensions)
return etree.tostring(feed, pretty_print=pretty)
def rss_file(self, filename):
def rss_file(self, filename, extensions=True):
'''Generates an RSS feed and write the resulting XML to a file.
:param filename: Name of file to write.
:param extensions: Enable or disable the loaded extensions for the xml
generation (default: enabled).
'''
feed, doc = self._create_rss()
feed, doc = self._create_rss(extensions=extensions)
with open(filename, 'w') as f:
doc.write(f)
@ -916,6 +937,14 @@ class FeedGenerator(object):
'''
if feedEntry is None:
feedEntry = FeedEntry()
# Try to load extensions:
for extname,ext in self.__extensions.iteritems():
try:
feedEntry.load_extension( extname, ext['atom'], ext['rss'] )
except ImportError:
pass
self.__feed_entries.append( feedEntry )
return feedEntry
@ -942,6 +971,16 @@ class FeedGenerator(object):
entry = [entry]
if replace:
self.__feed_entries = []
# Try to load extensions:
for e in entry:
for extname,ext in self.__extensions.iteritems():
try:
e.load_extension( extname, ext['atom'], ext['rss'] )
except ImportError:
pass
self.__feed_entries += entry
return self.__feed_entries
@ -969,3 +1008,33 @@ class FeedGenerator(object):
remove_entry.
'''
self.remove_entry(item)
def load_extension(self, name, atom=True, rss=True):
'''Load a specific extension by name.
:param name: Name of the extension to load.
:param atom: If the extension should be used for ATOM feeds.
:param rss: If the extension should be used for RSS feeds.
'''
# Check loaded extensions
if not isinstance(self.__extensions, dict):
self.__extensions = {}
if name in self.__extensions.keys():
raise ImportError('Extension already loaded')
# Load extension
extname = name[0].upper() + name[1:] + 'Extension'
supmod = __import__('feedgen.ext.%s' % name)
extmod = getattr(supmod.ext, name)
ext = getattr(extmod, extname)
extinst = ext()
setattr(self, name, extinst)
self.__extensions[name] = {'inst':extinst,'atom':atom,'rss':rss}
# Try to load the extension for already existing entries:
for entry in self.__feed_entries:
try:
entry.load_extension( name, atom, rss )
except ImportError:
pass

0
setup.py Normal file → Executable file
View file