Added extension support and converted PodcastGenerator to extension
This commit is contained in:
parent
e9eb3b5e95
commit
840dc7e5b8
8 changed files with 200 additions and 78 deletions
|
@ -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'):
|
||||
|
|
|
@ -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
6
feedgen/ext/__init__.py
Normal file
|
@ -0,0 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
===========
|
||||
feedgen.ext
|
||||
===========
|
||||
"""
|
39
feedgen/ext/base.py
Normal file
39
feedgen/ext/base.py
Normal 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.
|
||||
'''
|
|
@ -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' ],
|
|
@ -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:
|
|
@ -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
0
setup.py
Normal file → Executable file
Loading…
Reference in a new issue