Flake8 Compatibility
This patch makes the feedgen flake8 compatible, fixing some minor issues along the way. Most noticeable, this switches from tabs to spaces. Signed-off-by: Lars Kiesow <lkiesow@uos.de>
This commit is contained in:
parent
ccf18502bc
commit
444855a248
19 changed files with 3553 additions and 3572 deletions
1
Makefile
1
Makefile
|
@ -53,3 +53,4 @@ test:
|
||||||
python -m unittest tests.test_entry
|
python -m unittest tests.test_entry
|
||||||
python -m unittest tests.test_extension
|
python -m unittest tests.test_extension
|
||||||
@rm -f tmp_Atomfeed.xml tmp_Rssfeed.xml
|
@rm -f tmp_Atomfeed.xml tmp_Rssfeed.xml
|
||||||
|
flake8 $$(find setup.py tests feedgen -name '*.py')
|
||||||
|
|
64
doc/conf.py
64
doc/conf.py
|
@ -3,7 +3,10 @@
|
||||||
# All configuration values have a default; values that are commented out
|
# All configuration values have a default; values that are commented out
|
||||||
# serve to show the default.
|
# serve to show the default.
|
||||||
|
|
||||||
import sys, os, time, codecs, re
|
import sys
|
||||||
|
import os
|
||||||
|
import codecs
|
||||||
|
import re
|
||||||
|
|
||||||
# If extensions (or modules to document with autodoc) are in another directory,
|
# If extensions (or modules to document with autodoc) are in another directory,
|
||||||
# add these directories to sys.path here. If the directory is relative to the
|
# add these directories to sys.path here. If the directory is relative to the
|
||||||
|
@ -13,13 +16,13 @@ sys.path.insert(0, os.path.abspath('.'))
|
||||||
|
|
||||||
import feedgen.version
|
import feedgen.version
|
||||||
|
|
||||||
# -- General configuration -----------------------------------------------------
|
# -- General configuration ----------------------------------------------------
|
||||||
|
|
||||||
# If your documentation needs a minimal Sphinx version, state it here.
|
# If your documentation needs a minimal Sphinx version, state it here.
|
||||||
#needs_sphinx = '1.0'
|
#needs_sphinx = '1.0'
|
||||||
|
|
||||||
# Add any Sphinx extension module names here, as strings. They can be extensions
|
# Add any Sphinx extension module names here, as strings. They can be
|
||||||
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||||
extensions = [
|
extensions = [
|
||||||
'sphinx.ext.intersphinx',
|
'sphinx.ext.intersphinx',
|
||||||
'sphinx.ext.coverage',
|
'sphinx.ext.coverage',
|
||||||
|
@ -65,7 +68,8 @@ release = feedgen.version.version_full_str
|
||||||
# directories to ignore when looking for source files.
|
# directories to ignore when looking for source files.
|
||||||
exclude_patterns = ['_build']
|
exclude_patterns = ['_build']
|
||||||
|
|
||||||
# The reST default role (used for this markup: `text`) to use for all documents.
|
# The reST default role (used for this markup: `text`) to use for all
|
||||||
|
# documents.
|
||||||
#default_role = None
|
#default_role = None
|
||||||
|
|
||||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||||
|
@ -86,7 +90,7 @@ pygments_style = 'sphinx'
|
||||||
#modindex_common_prefix = []
|
#modindex_common_prefix = []
|
||||||
|
|
||||||
|
|
||||||
# -- Options for HTML output ---------------------------------------------------
|
# -- Options for HTML output --------------------------------------------------
|
||||||
|
|
||||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||||
# a list of builtin themes.
|
# a list of builtin themes.
|
||||||
|
@ -169,24 +173,24 @@ html_static_path = ['_static']
|
||||||
htmlhelp_basename = 'pyFeedGen'
|
htmlhelp_basename = 'pyFeedGen'
|
||||||
|
|
||||||
|
|
||||||
# -- Options for LaTeX output --------------------------------------------------
|
# -- Options for LaTeX output -------------------------------------------------
|
||||||
|
|
||||||
latex_elements = {
|
latex_elements = {
|
||||||
# The paper size ('letterpaper' or 'a4paper').
|
# The paper size ('letterpaper' or 'a4paper').
|
||||||
#'papersize': 'letterpaper',
|
#'papersize': 'letterpaper',
|
||||||
|
|
||||||
# The font size ('10pt', '11pt' or '12pt').
|
# The font size ('10pt', '11pt' or '12pt').
|
||||||
#'pointsize': '10pt',
|
#'pointsize': '10pt',
|
||||||
|
|
||||||
# Additional stuff for the LaTeX preamble.
|
# Additional stuff for the LaTeX preamble.
|
||||||
#'preamble': '',
|
#'preamble': '',
|
||||||
}
|
}
|
||||||
|
|
||||||
# Grouping the document tree into LaTeX files. List of tuples
|
# Grouping the document tree into LaTeX files. List of tuples (source start
|
||||||
# (source start file, target name, title, author, documentclass [howto/manual]).
|
# file, target name, title, author, documentclass [howto/manual]).
|
||||||
latex_documents = [
|
latex_documents = [
|
||||||
('index', 'pyFeedGen.tex', u'pyFeedGen Documentation',
|
('index', 'pyFeedGen.tex', u'pyFeedGen Documentation', u'Lars Kiesow',
|
||||||
u'Lars Kiesow', 'manual'),
|
'manual'),
|
||||||
]
|
]
|
||||||
|
|
||||||
# The name of an image file (relative to this directory) to place at the top of
|
# The name of an image file (relative to this directory) to place at the top of
|
||||||
|
@ -210,7 +214,7 @@ latex_documents = [
|
||||||
#latex_domain_indices = True
|
#latex_domain_indices = True
|
||||||
|
|
||||||
|
|
||||||
# -- Options for manual page output --------------------------------------------
|
# -- Options for manual page output -------------------------------------------
|
||||||
|
|
||||||
# One entry per manual page. List of tuples
|
# One entry per manual page. List of tuples
|
||||||
# (source start file, name, description, authors, manual section).
|
# (source start file, name, description, authors, manual section).
|
||||||
|
@ -223,7 +227,7 @@ man_pages = [
|
||||||
#man_show_urls = False
|
#man_show_urls = False
|
||||||
|
|
||||||
|
|
||||||
# -- Options for Texinfo output ------------------------------------------------
|
# -- Options for Texinfo output -----------------------------------------------
|
||||||
|
|
||||||
# Grouping the document tree into Texinfo files. List of tuples
|
# Grouping the document tree into Texinfo files. List of tuples
|
||||||
# (source start file, target name, title, author,
|
# (source start file, target name, title, author,
|
||||||
|
@ -248,28 +252,11 @@ texinfo_documents = [
|
||||||
intersphinx_mapping = {'http://docs.python.org/': None}
|
intersphinx_mapping = {'http://docs.python.org/': None}
|
||||||
|
|
||||||
|
|
||||||
# Ugly way of setting tabsize
|
|
||||||
import re
|
|
||||||
|
|
||||||
def process_docstring(app, what, name, obj, options, lines):
|
|
||||||
'''
|
|
||||||
spaces_pat = re.compile(r"(?<= )( {8})")
|
|
||||||
ll = []
|
|
||||||
for l in lines:
|
|
||||||
ll.append(spaces_pat.sub(" ",l))
|
|
||||||
'''
|
|
||||||
spaces_pat = re.compile(r"^ *")
|
|
||||||
ll = []
|
|
||||||
for l in lines:
|
|
||||||
spacelen = len(spaces_pat.search(l).group(0))
|
|
||||||
newlen = (spacelen % 8) + (spacelen / 8 * 4)
|
|
||||||
ll.append( (' '*newlen) + l.lstrip(' ') )
|
|
||||||
lines[:] = ll
|
|
||||||
|
|
||||||
|
|
||||||
# Include the GitHub readme file in index.rst
|
# Include the GitHub readme file in index.rst
|
||||||
r = re.compile(r'\[`*([^\]`]+)`*\]\(([^\)]+)\)')
|
r = re.compile(r'\[`*([^\]`]+)`*\]\(([^\)]+)\)')
|
||||||
r2 = re.compile(r'.. include-github-readme')
|
r2 = re.compile(r'.. include-github-readme')
|
||||||
|
|
||||||
|
|
||||||
def substitute_link(app, docname, text):
|
def substitute_link(app, docname, text):
|
||||||
if docname == 'index':
|
if docname == 'index':
|
||||||
readme_text = ''
|
readme_text = ''
|
||||||
|
@ -279,5 +266,4 @@ def substitute_link(app, docname, text):
|
||||||
|
|
||||||
|
|
||||||
def setup(app):
|
def setup(app):
|
||||||
app.connect('autodoc-process-docstring', process_docstring)
|
|
||||||
app.connect('source-read', substitute_link)
|
app.connect('source-read', substitute_link)
|
||||||
|
|
|
@ -39,15 +39,16 @@
|
||||||
|
|
||||||
Example::
|
Example::
|
||||||
|
|
||||||
>>> fg.contributor( name='John Doe', email='jdoe@example.com' )
|
>>> fg.contributor(name='John Doe', email='jdoe@example.com' )
|
||||||
>>> fg.contributor({'name':'John Doe', 'email':'jdoe@example.com'})
|
>>> fg.contributor({'name':'John Doe', 'email':'jdoe@example.com'})
|
||||||
>>> fg.contributor([{'name':'John Doe', 'email':'jdoe@example.com'}, ...])
|
>>> fg.contributor([{'name':'John', 'email':'jdoe@example.com'}, …])
|
||||||
|
|
||||||
-----------------
|
-----------------
|
||||||
Generate the Feed
|
Generate the Feed
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
After that you can generate both RSS or ATOM by calling the respective method::
|
After that you can generate both RSS or ATOM by calling the respective
|
||||||
|
method::
|
||||||
|
|
||||||
>>> atomfeed = fg.atom_str(pretty=True) # Get the ATOM feed as string
|
>>> atomfeed = fg.atom_str(pretty=True) # Get the ATOM feed as string
|
||||||
>>> rssfeed = fg.rss_str(pretty=True) # Get the RSS feed as string
|
>>> rssfeed = fg.rss_str(pretty=True) # Get the RSS feed as string
|
||||||
|
@ -77,8 +78,8 @@
|
||||||
Extensions
|
Extensions
|
||||||
----------
|
----------
|
||||||
|
|
||||||
The FeedGenerator supports extension to include additional data into the XML
|
The FeedGenerator supports extension to include additional data into the
|
||||||
structure of the feeds. Extensions can be loaded like this::
|
XML structure of the feeds. Extensions can be loaded like this::
|
||||||
|
|
||||||
>>> fg.load_extension('someext', atom=True, rss=True)
|
>>> fg.load_extension('someext', atom=True, rss=True)
|
||||||
|
|
||||||
|
@ -94,14 +95,14 @@
|
||||||
`ext/someext_entry.py` which is suggested especially for large extensions.
|
`ext/someext_entry.py` which is suggested especially for large extensions.
|
||||||
|
|
||||||
The parameters `atom` and `rss` tell the FeedGenerator if the extensions
|
The parameters `atom` and `rss` tell the FeedGenerator if the extensions
|
||||||
should only be used for either ATOM or RSS feeds. The default value for both
|
should only be used for either ATOM or RSS feeds. The default value for
|
||||||
parameters is true which means that the extension would be used for both
|
both parameters is true which means that the extension would be used for
|
||||||
kinds of feeds.
|
both kinds of feeds.
|
||||||
|
|
||||||
**Example: Produceing a Podcast**
|
**Example: Produceing a Podcast**
|
||||||
|
|
||||||
One extension already provided is the podcast extension. A podcast is an RSS
|
One extension already provided is the podcast extension. A podcast is an
|
||||||
feed with some additional elements for ITunes.
|
RSS feed with some additional elements for ITunes.
|
||||||
|
|
||||||
To produce a podcast simply load the `podcast` extension::
|
To produce a podcast simply load the `podcast` extension::
|
||||||
|
|
||||||
|
|
|
@ -11,37 +11,46 @@
|
||||||
from feedgen.feed import FeedGenerator
|
from feedgen.feed import FeedGenerator
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
USAGE = '''
|
||||||
|
Usage: python -m feedgen [OPTION]
|
||||||
|
|
||||||
|
Use one of the following options:
|
||||||
|
|
||||||
|
File options:
|
||||||
|
<file>.atom -- Generate ATOM test feed
|
||||||
|
<file>.rss -- Generate RSS test teed
|
||||||
|
|
||||||
|
Stdout options:
|
||||||
|
atom -- Generate ATOM test output
|
||||||
|
rss -- Generate RSS test output
|
||||||
|
podcast -- Generate Podcast test output
|
||||||
|
dc.atom -- Generate DC extension test output (atom format)
|
||||||
|
dc.rss -- Generate DC extension test output (rss format)
|
||||||
|
syndication.atom -- Generate syndication extension test output (atom format)
|
||||||
|
syndication.rss -- Generate syndication extension test output (rss format)
|
||||||
|
torrent -- Generate Torrent test output
|
||||||
|
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
def print_enc(s):
|
def print_enc(s):
|
||||||
'''Print function compatible with both python2 and python3 accepting strings
|
'''Print function compatible with both python2 and python3 accepting strings
|
||||||
and byte arrays.
|
and byte arrays.
|
||||||
'''
|
'''
|
||||||
if sys.version_info[0] >= 3:
|
if sys.version_info[0] >= 3:
|
||||||
print(s.decode('utf-8') if type(s) == type(b'') else s)
|
print(s.decode('utf-8') if isinstance(s, bytes) else s)
|
||||||
else:
|
else:
|
||||||
print(s)
|
print(s)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
if len(sys.argv) != 2 or not (
|
if len(sys.argv) != 2 or not (
|
||||||
sys.argv[1].endswith('rss') \
|
sys.argv[1].endswith('rss') or
|
||||||
or sys.argv[1].endswith('atom') \
|
sys.argv[1].endswith('atom') or
|
||||||
or sys.argv[1].endswith('torrent') \
|
sys.argv[1] == 'torrent' or
|
||||||
or sys.argv[1].endswith('podcast') ):
|
sys.argv[1] == 'podcast'):
|
||||||
print_enc ('Usage: %s ( <file>.atom | atom | <file>.rss | rss | podcast | torrent )' % \
|
print(USAGE)
|
||||||
'python -m feedgen')
|
|
||||||
print_enc ('')
|
|
||||||
print_enc (' atom -- Generate ATOM test output and print it to stdout.')
|
|
||||||
print_enc (' rss -- Generate RSS test output and print it to stdout.')
|
|
||||||
print_enc (' <file>.atom -- Generate ATOM test feed and write it to file.atom.')
|
|
||||||
print_enc (' <file>.rss -- Generate RSS test teed and write it to file.rss.')
|
|
||||||
print_enc (' podcast -- Generate Podcast test output and print it to stdout.')
|
|
||||||
print_enc (' dc.atom -- Generate DC extension test output (atom format) and print it to stdout.')
|
|
||||||
print_enc (' dc.rss -- Generate DC extension test output (rss format) and print it to stdout.')
|
|
||||||
print_enc (' syndication.atom -- Generate syndication extension test output (atom format) and print it to stdout.')
|
|
||||||
print_enc (' syndication.rss -- Generate syndication extension test output (rss format) and print it to stdout.')
|
|
||||||
print_enc (' torrent -- Generate Torrent test output and print it to stdout.')
|
|
||||||
print_enc ('')
|
|
||||||
exit()
|
exit()
|
||||||
|
|
||||||
arg = sys.argv[1]
|
arg = sys.argv[1]
|
||||||
|
@ -49,34 +58,34 @@ if __name__ == '__main__':
|
||||||
fg = FeedGenerator()
|
fg = FeedGenerator()
|
||||||
fg.id('http://lernfunk.de/_MEDIAID_123')
|
fg.id('http://lernfunk.de/_MEDIAID_123')
|
||||||
fg.title('Testfeed')
|
fg.title('Testfeed')
|
||||||
fg.author( {'name':'Lars Kiesow','email':'lkiesow@uos.de'} )
|
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.category(term='test')
|
||||||
fg.contributor( name='Lars Kiesow', email='lkiesow@uos.de' )
|
fg.contributor(name='Lars Kiesow', email='lkiesow@uos.de')
|
||||||
fg.contributor( name='John Doe', email='jdoe@example.com' )
|
fg.contributor(name='John Doe', email='jdoe@example.com')
|
||||||
fg.icon('http://ex.com/icon.jpg')
|
fg.icon('http://ex.com/icon.jpg')
|
||||||
fg.logo('http://ex.com/logo.jpg')
|
fg.logo('http://ex.com/logo.jpg')
|
||||||
fg.rights('cc-by')
|
fg.rights('cc-by')
|
||||||
fg.subtitle('This is a cool feed!')
|
fg.subtitle('This is a cool feed!')
|
||||||
fg.link( href='http://larskiesow.de/test.atom', rel='self' )
|
fg.link(href='http://larskiesow.de/test.atom', rel='self')
|
||||||
fg.language('de')
|
fg.language('de')
|
||||||
fe = fg.add_entry()
|
fe = fg.add_entry()
|
||||||
fe.id('http://lernfunk.de/_MEDIAID_123#1')
|
fe.id('http://lernfunk.de/_MEDIAID_123#1')
|
||||||
fe.title('First Element')
|
fe.title('First Element')
|
||||||
fe.content('''Lorem ipsum dolor sit amet, consectetur adipiscing elit. Tamen
|
fe.content('''Lorem ipsum dolor sit amet, consectetur adipiscing elit. Tamen
|
||||||
aberramus a proposito, et, ne longius, prorsus, inquam, Piso, si ista
|
aberramus a proposito, et, ne longius, prorsus, inquam, Piso, si
|
||||||
mala sunt, placet. Aut etiam, ut vestitum, sic sententiam habeas aliam
|
ista mala sunt, placet. Aut etiam, ut vestitum, sic sententiam
|
||||||
domesticam, aliam forensem, ut in fronte ostentatio sit, intus veritas
|
habeas aliam domesticam, aliam forensem, ut in fronte ostentatio
|
||||||
occultetur? Cum id fugiunt, re eadem defendunt, quae Peripatetici,
|
sit, intus veritas occultetur? Cum id fugiunt, re eadem defendunt,
|
||||||
verba.''')
|
quae Peripatetici, verba.''')
|
||||||
fe.summary(u'Lorem ipsum dolor sit amet, consectetur adipiscing elit…')
|
fe.summary(u'Lorem ipsum dolor sit amet, consectetur adipiscing elit…')
|
||||||
fe.link( href='http://example.com', rel='alternate' )
|
fe.link(href='http://example.com', rel='alternate')
|
||||||
fe.author( name='Lars Kiesow', email='lkiesow@uos.de' )
|
fe.author(name='Lars Kiesow', email='lkiesow@uos.de')
|
||||||
|
|
||||||
if arg == 'atom':
|
if arg == 'atom':
|
||||||
print_enc (fg.atom_str(pretty=True))
|
print_enc(fg.atom_str(pretty=True))
|
||||||
elif arg == 'rss':
|
elif arg == 'rss':
|
||||||
print_enc (fg.rss_str(pretty=True))
|
print_enc(fg.rss_str(pretty=True))
|
||||||
elif arg == 'podcast':
|
elif arg == 'podcast':
|
||||||
# Load the podcast extension. It will automatically be loaded for all
|
# Load the podcast extension. It will automatically be loaded for all
|
||||||
# entries in the feed, too. Thus also for our “fe”.
|
# entries in the feed, too. Thus also for our “fe”.
|
||||||
|
@ -87,30 +96,32 @@ if __name__ == '__main__':
|
||||||
fg.podcast.itunes_complete('no')
|
fg.podcast.itunes_complete('no')
|
||||||
fg.podcast.itunes_new_feed_url('http://example.com/new-feed.rss')
|
fg.podcast.itunes_new_feed_url('http://example.com/new-feed.rss')
|
||||||
fg.podcast.itunes_owner('John Doe', 'john@example.com')
|
fg.podcast.itunes_owner('John Doe', 'john@example.com')
|
||||||
fg.podcast.itunes_summary('Lorem ipsum dolor sit amet, ' + \
|
fg.podcast.itunes_summary('Lorem ipsum dolor sit amet, consectetur ' +
|
||||||
'consectetur adipiscing elit. ' + \
|
'adipiscing elit. Verba tu fingas et ea ' +
|
||||||
'Verba tu fingas et ea dicas, quae non sentias?')
|
'dicas, quae non sentias?')
|
||||||
fe.podcast.itunes_author('Lars Kiesow')
|
fe.podcast.itunes_author('Lars Kiesow')
|
||||||
print_enc (fg.rss_str(pretty=True))
|
print_enc(fg.rss_str(pretty=True))
|
||||||
|
|
||||||
elif arg == 'torrent':
|
elif arg == 'torrent':
|
||||||
fg.load_extension('torrent')
|
fg.load_extension('torrent')
|
||||||
fe.link( href='http://somewhere.behind.the.sea/torrent/debian-8.4.0-i386-netint.iso.torrent', rel='alternate', type='application/x-bittorrent, length=1000' )
|
fe.link(href='http://example.com/torrent/debian-8-netint.iso.torrent',
|
||||||
|
rel='alternate',
|
||||||
|
type='application/x-bittorrent, length=1000')
|
||||||
fe.torrent.filename('debian-8.4.0-i386-netint.iso.torrent')
|
fe.torrent.filename('debian-8.4.0-i386-netint.iso.torrent')
|
||||||
fe.torrent.infohash('7661229811ef32014879ceedcdf4a48f256c88ba')
|
fe.torrent.infohash('7661229811ef32014879ceedcdf4a48f256c88ba')
|
||||||
fe.torrent.contentlength('331350016')
|
fe.torrent.contentlength('331350016')
|
||||||
fe.torrent.seeds('789')
|
fe.torrent.seeds('789')
|
||||||
fe.torrent.peers('456')
|
fe.torrent.peers('456')
|
||||||
fe.torrent.verified('123')
|
fe.torrent.verified('123')
|
||||||
print_enc (fg.rss_str(pretty=True))
|
print_enc(fg.rss_str(pretty=True))
|
||||||
|
|
||||||
elif arg.startswith('dc.'):
|
elif arg.startswith('dc.'):
|
||||||
fg.load_extension('dc')
|
fg.load_extension('dc')
|
||||||
fg.dc.dc_contributor('Lars Kiesow')
|
fg.dc.dc_contributor('Lars Kiesow')
|
||||||
if arg.endswith('.atom'):
|
if arg.endswith('.atom'):
|
||||||
print_enc (fg.atom_str(pretty=True))
|
print_enc(fg.atom_str(pretty=True))
|
||||||
else:
|
else:
|
||||||
print_enc (fg.rss_str(pretty=True))
|
print_enc(fg.rss_str(pretty=True))
|
||||||
|
|
||||||
elif arg.startswith('syndication'):
|
elif arg.startswith('syndication'):
|
||||||
fg.load_extension('syndication')
|
fg.load_extension('syndication')
|
||||||
|
@ -118,9 +129,9 @@ if __name__ == '__main__':
|
||||||
fg.syndication.update_frequency(2)
|
fg.syndication.update_frequency(2)
|
||||||
fg.syndication.update_base('2000-01-01T12:00+00:00')
|
fg.syndication.update_base('2000-01-01T12:00+00:00')
|
||||||
if arg.endswith('.rss'):
|
if arg.endswith('.rss'):
|
||||||
print_enc (fg.rss_str(pretty=True))
|
print_enc(fg.rss_str(pretty=True))
|
||||||
else:
|
else:
|
||||||
print_enc (fg.atom_str(pretty=True))
|
print_enc(fg.atom_str(pretty=True))
|
||||||
|
|
||||||
elif arg.endswith('atom'):
|
elif arg.endswith('atom'):
|
||||||
fg.atom_file(arg)
|
fg.atom_file(arg)
|
||||||
|
|
|
@ -4,4 +4,4 @@ import sys
|
||||||
if sys.version_info[0] >= 3:
|
if sys.version_info[0] >= 3:
|
||||||
string_types = str
|
string_types = str
|
||||||
else:
|
else:
|
||||||
string_types = basestring
|
string_types = basestring # noqa: F821
|
||||||
|
|
253
feedgen/entry.py
253
feedgen/entry.py
|
@ -58,11 +58,10 @@ class FeedEntry(object):
|
||||||
self.__extensions = {}
|
self.__extensions = {}
|
||||||
self.__extensions_register = {}
|
self.__extensions_register = {}
|
||||||
|
|
||||||
|
|
||||||
def atom_entry(self, extensions=True):
|
def atom_entry(self, extensions=True):
|
||||||
'''Create an ATOM entry and return it.'''
|
'''Create an ATOM entry and return it.'''
|
||||||
entry = etree.Element('entry')
|
entry = etree.Element('entry')
|
||||||
if not ( self.__atom_id and self.__atom_title and self.__atom_updated ):
|
if not (self.__atom_id and self.__atom_title and self.__atom_updated):
|
||||||
raise ValueError('Required fields not set')
|
raise ValueError('Required fields not set')
|
||||||
id = etree.SubElement(entry, 'id')
|
id = etree.SubElement(entry, 'id')
|
||||||
id.text = self.__atom_id
|
id.text = self.__atom_id
|
||||||
|
@ -71,12 +70,13 @@ class FeedEntry(object):
|
||||||
updated = etree.SubElement(entry, 'updated')
|
updated = etree.SubElement(entry, 'updated')
|
||||||
updated.text = self.__atom_updated.isoformat()
|
updated.text = self.__atom_updated.isoformat()
|
||||||
|
|
||||||
# An entry must contain an alternate link if there is no content element.
|
# An entry must contain an alternate link if there is no content
|
||||||
|
# element.
|
||||||
if not self.__atom_content:
|
if not self.__atom_content:
|
||||||
if not True in [ l.get('rel') == 'alternate' \
|
links = self.__atom_link or []
|
||||||
for l in self.__atom_link or [] ]:
|
if not [l for l in links if l.get('rel') == 'alternate']:
|
||||||
raise ValueError('Entry must contain an alternate link or '
|
raise ValueError('Entry must contain an alternate link or ' +
|
||||||
+ 'a content element.')
|
'a content element.')
|
||||||
|
|
||||||
# Add author elements
|
# Add author elements
|
||||||
for a in self.__atom_author or []:
|
for a in self.__atom_author or []:
|
||||||
|
@ -101,21 +101,24 @@ class FeedEntry(object):
|
||||||
elif self.__atom_content.get('content'):
|
elif self.__atom_content.get('content'):
|
||||||
# Surround xhtml with a div tag, parse it and embed it
|
# Surround xhtml with a div tag, parse it and embed it
|
||||||
if type == 'xhtml':
|
if type == 'xhtml':
|
||||||
content.append(etree.fromstring('''<div
|
content.append(etree.fromstring(
|
||||||
xmlns="http://www.w3.org/1999/xhtml">%s</div>''' % \
|
'<div xmlns="http://www.w3.org/1999/xhtml">' +
|
||||||
self.__atom_content.get('content')))
|
self.__atom_content.get('content') + '</div>'))
|
||||||
elif type == 'CDATA':
|
elif type == 'CDATA':
|
||||||
content.text = etree.CDATA(self.__atom_content.get('content'))
|
content.text = etree.CDATA(
|
||||||
|
self.__atom_content.get('content'))
|
||||||
# Emed the text in escaped form
|
# Emed the text in escaped form
|
||||||
elif not type or type.startswith('text') or type == 'html':
|
elif not type or type.startswith('text') or type == 'html':
|
||||||
content.text = self.__atom_content.get('content')
|
content.text = self.__atom_content.get('content')
|
||||||
# Parse XML and embed it
|
# Parse XML and embed it
|
||||||
elif type.endswith('/xml') or type.endswith('+xml'):
|
elif type.endswith('/xml') or type.endswith('+xml'):
|
||||||
content.append(etree.fromstring(self.__atom_content['content']))
|
content.append(etree.fromstring(
|
||||||
|
self.__atom_content['content']))
|
||||||
# Everything else should be included base64 encoded
|
# Everything else should be included base64 encoded
|
||||||
else:
|
else:
|
||||||
raise ValueError('base64 encoded content is not supported at the moment.'
|
raise ValueError('base64 encoded content is not ' +
|
||||||
+ 'If you are interested , please file a bug report.')
|
'supported at the moment. Pull requests' +
|
||||||
|
' adding support are welcome.')
|
||||||
# Add type description of the content
|
# Add type description of the content
|
||||||
if type:
|
if type:
|
||||||
content.attrib['type'] = type
|
content.attrib['type'] = type
|
||||||
|
@ -149,7 +152,7 @@ class FeedEntry(object):
|
||||||
# Atom requires a name. Skip elements without.
|
# Atom requires a name. Skip elements without.
|
||||||
if not c.get('name'):
|
if not c.get('name'):
|
||||||
continue
|
continue
|
||||||
contrib = etree.SubElement(feed, 'contributor')
|
contrib = etree.SubElement(entry, 'contributor')
|
||||||
name = etree.SubElement(contrib, 'name')
|
name = etree.SubElement(contrib, 'name')
|
||||||
name.text = c.get('name')
|
name.text = c.get('name')
|
||||||
if c.get('email'):
|
if c.get('email'):
|
||||||
|
@ -164,7 +167,7 @@ class FeedEntry(object):
|
||||||
published.text = self.__atom_published.isoformat()
|
published.text = self.__atom_published.isoformat()
|
||||||
|
|
||||||
if self.__atom_rights:
|
if self.__atom_rights:
|
||||||
rights = etree.SubElement(feed, 'rights')
|
rights = etree.SubElement(entry, 'rights')
|
||||||
rights.text = self.__atom_rights
|
rights.text = self.__atom_rights
|
||||||
|
|
||||||
if extensions:
|
if extensions:
|
||||||
|
@ -174,11 +177,12 @@ class FeedEntry(object):
|
||||||
|
|
||||||
return entry
|
return entry
|
||||||
|
|
||||||
|
|
||||||
def rss_entry(self, extensions=True):
|
def rss_entry(self, extensions=True):
|
||||||
'''Create a RSS item and return it.'''
|
'''Create a RSS item and return it.'''
|
||||||
entry = etree.Element('item')
|
entry = etree.Element('item')
|
||||||
if not ( self.__rss_title or self.__rss_description or self.__rss_content):
|
if not (self.__rss_title or
|
||||||
|
self.__rss_description or
|
||||||
|
self.__rss_content):
|
||||||
raise ValueError('Required fields not set')
|
raise ValueError('Required fields not set')
|
||||||
if self.__rss_title:
|
if self.__rss_title:
|
||||||
title = etree.SubElement(entry, 'title')
|
title = etree.SubElement(entry, 'title')
|
||||||
|
@ -189,10 +193,11 @@ class FeedEntry(object):
|
||||||
if self.__rss_description and self.__rss_content:
|
if self.__rss_description and self.__rss_content:
|
||||||
description = etree.SubElement(entry, 'description')
|
description = etree.SubElement(entry, 'description')
|
||||||
description.text = self.__rss_description
|
description.text = self.__rss_description
|
||||||
content = etree.SubElement(entry, '{%s}encoded' %
|
XMLNS_CONTENT = 'http://purl.org/rss/1.0/modules/content/'
|
||||||
'http://purl.org/rss/1.0/modules/content/')
|
content = etree.SubElement(entry, '{%s}encoded' % XMLNS_CONTENT)
|
||||||
content.text = etree.CDATA(self.__rss_content['content']) \
|
content.text = etree.CDATA(self.__rss_content['content']) \
|
||||||
if self.__rss_content.get('type', '') == 'CDATA' else self.__rss_content['content']
|
if self.__rss_content.get('type', '') == 'CDATA' \
|
||||||
|
else self.__rss_content['content']
|
||||||
elif self.__rss_description:
|
elif self.__rss_description:
|
||||||
description = etree.SubElement(entry, 'description')
|
description = etree.SubElement(entry, 'description')
|
||||||
description.text = self.__rss_description
|
description.text = self.__rss_description
|
||||||
|
@ -230,8 +235,6 @@ class FeedEntry(object):
|
||||||
|
|
||||||
return entry
|
return entry
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def title(self, title=None):
|
def title(self, title=None):
|
||||||
'''Get or set the title value of the entry. It should contain a human
|
'''Get or set the title value of the entry. It should contain a human
|
||||||
readable title for the entry. Title is mandatory for both ATOM and RSS
|
readable title for the entry. Title is mandatory for both ATOM and RSS
|
||||||
|
@ -240,49 +243,47 @@ class FeedEntry(object):
|
||||||
:param title: The new title of the entry.
|
:param title: The new title of the entry.
|
||||||
:returns: The entriess title.
|
:returns: The entriess title.
|
||||||
'''
|
'''
|
||||||
if not title is None:
|
if title is not None:
|
||||||
self.__atom_title = title
|
self.__atom_title = title
|
||||||
self.__rss_title = title
|
self.__rss_title = title
|
||||||
return self.__atom_title
|
return self.__atom_title
|
||||||
|
|
||||||
|
|
||||||
def id(self, id=None):
|
def id(self, id=None):
|
||||||
'''Get or set the entry id which identifies the entry using a universally
|
'''Get or set the entry id which identifies the entry using a
|
||||||
unique and permanent URI. Two entries in a feed can have the same value
|
universally unique and permanent URI. Two entries in a feed can have
|
||||||
for id if they represent the same entry at different points in time. This
|
the same value for id if they represent the same entry at different
|
||||||
method will also set rss:guid. Id is mandatory for an ATOM entry.
|
points in time. This method will also set rss:guid. Id is mandatory
|
||||||
|
for an ATOM entry.
|
||||||
|
|
||||||
:param id: New Id of the entry.
|
:param id: New Id of the entry.
|
||||||
:returns: Id of the entry.
|
:returns: Id of the entry.
|
||||||
'''
|
'''
|
||||||
if not id is None:
|
if id is not None:
|
||||||
self.__atom_id = id
|
self.__atom_id = id
|
||||||
self.__rss_guid = id
|
self.__rss_guid = id
|
||||||
return self.__atom_id
|
return self.__atom_id
|
||||||
|
|
||||||
|
|
||||||
def guid(self, guid=None):
|
def guid(self, guid=None):
|
||||||
'''Get or set the entries guid which is a string that uniquely identifies
|
'''Get or set the entries guid which is a string that uniquely
|
||||||
the item. This will also set atom:id.
|
identifies the item. This will also set atom:id.
|
||||||
|
|
||||||
:param guid: Id of the entry.
|
:param guid: Id of the entry.
|
||||||
:returns: Id of the entry.
|
:returns: Id of the entry.
|
||||||
'''
|
'''
|
||||||
return self.id(guid)
|
return self.id(guid)
|
||||||
|
|
||||||
|
|
||||||
def updated(self, updated=None):
|
def updated(self, updated=None):
|
||||||
'''Set or get the updated value which indicates the last time the entry
|
'''Set or get the updated value which indicates the last time the entry
|
||||||
was modified in a significant way.
|
was modified in a significant way.
|
||||||
|
|
||||||
The value can either be a string which will automatically be parsed or a
|
The value can either be a string which will automatically be parsed or
|
||||||
datetime.datetime object. In any case it is necessary that the value
|
a datetime.datetime object. In any case it is necessary that the value
|
||||||
include timezone information.
|
include timezone information.
|
||||||
|
|
||||||
:param updated: The modification date.
|
:param updated: The modification date.
|
||||||
:returns: Modification date as datetime.datetime
|
:returns: Modification date as datetime.datetime
|
||||||
'''
|
'''
|
||||||
if not updated is None:
|
if updated is not None:
|
||||||
if isinstance(updated, string_types):
|
if isinstance(updated, string_types):
|
||||||
updated = dateutil.parser.parse(updated)
|
updated = dateutil.parser.parse(updated)
|
||||||
if not isinstance(updated, datetime):
|
if not isinstance(updated, datetime):
|
||||||
|
@ -294,11 +295,10 @@ class FeedEntry(object):
|
||||||
|
|
||||||
return self.__atom_updated
|
return self.__atom_updated
|
||||||
|
|
||||||
|
|
||||||
def author(self, author=None, replace=False, **kwargs):
|
def author(self, author=None, replace=False, **kwargs):
|
||||||
'''Get or set autor data. An author element is a dict containing a name,
|
'''Get or set autor data. An author element is a dict containing a
|
||||||
an email adress and a uri. Name is mandatory for ATOM, email is mandatory
|
name, an email adress and a uri. Name is mandatory for ATOM, email is
|
||||||
for RSS.
|
mandatory for RSS.
|
||||||
|
|
||||||
This method can be called with:
|
This method can be called with:
|
||||||
- the fields of an author as keyword arguments
|
- the fields of an author as keyword arguments
|
||||||
|
@ -315,36 +315,36 @@ class FeedEntry(object):
|
||||||
|
|
||||||
Example::
|
Example::
|
||||||
|
|
||||||
>>> author( { 'name':'John Doe', 'email':'jdoe@example.com' } )
|
>>> author({'name':'John Doe', 'email':'jdoe@example.com'})
|
||||||
[{'name':'John Doe','email':'jdoe@example.com'}]
|
[{'name':'John Doe','email':'jdoe@example.com'}]
|
||||||
|
|
||||||
>>> author([{'name':'Mr. X'},{'name':'Max'}])
|
>>> author([{'name': 'Mr. X'}, {'name': 'Max'}])
|
||||||
[{'name':'John Doe','email':'jdoe@example.com'},
|
[{'name':'John Doe','email':'jdoe@example.com'},
|
||||||
{'name':'John Doe'}, {'name':'Max'}]
|
{'name':'John Doe'}, {'name':'Max'}]
|
||||||
|
|
||||||
>>> author( name='John Doe', email='jdoe@example.com', replace=True )
|
>>> author(name='John Doe', email='jdoe@example.com', replace=True)
|
||||||
[{'name':'John Doe','email':'jdoe@example.com'}]
|
[{'name':'John Doe','email':'jdoe@example.com'}]
|
||||||
|
|
||||||
'''
|
'''
|
||||||
if author is None and kwargs:
|
if author is None and kwargs:
|
||||||
author = kwargs
|
author = kwargs
|
||||||
if not author is None:
|
if author is not None:
|
||||||
if replace or self.__atom_author is None:
|
if replace or self.__atom_author is None:
|
||||||
self.__atom_author = []
|
self.__atom_author = []
|
||||||
self.__atom_author += ensure_format( author,
|
self.__atom_author += ensure_format(author,
|
||||||
set(['name', 'email', 'uri']), set(['name']))
|
set(['name', 'email', 'uri']),
|
||||||
|
set(['name']))
|
||||||
self.__rss_author = []
|
self.__rss_author = []
|
||||||
for a in self.__atom_author:
|
for a in self.__atom_author:
|
||||||
if a.get('email'):
|
if a.get('email'):
|
||||||
self.__rss_author.append('%s (%s)' % ( a['email'], a['name'] ))
|
self.__rss_author.append('%(email)s (%(name)s)' % a)
|
||||||
return self.__atom_author
|
return self.__atom_author
|
||||||
|
|
||||||
|
|
||||||
def content(self, content=None, src=None, type=None):
|
def content(self, content=None, src=None, type=None):
|
||||||
'''Get or set the cntent of the entry which contains or links to the
|
'''Get or set the cntent of the entry which contains or links to the
|
||||||
complete content of the entry. Content must be provided for ATOM entries
|
complete content of the entry. Content must be provided for ATOM
|
||||||
if there is no alternate link, and should be provided if there is no
|
entries if there is no alternate link, and should be provided if there
|
||||||
summary. If the content is set (not linked) it will also set
|
is no summary. If the content is set (not linked) it will also set
|
||||||
rss:description.
|
rss:description.
|
||||||
|
|
||||||
:param content: The content of the feed entry.
|
:param content: The content of the feed entry.
|
||||||
|
@ -352,20 +352,20 @@ class FeedEntry(object):
|
||||||
:param type: If type is CDATA content would not be escaped.
|
:param type: If type is CDATA content would not be escaped.
|
||||||
:returns: Content element of the entry.
|
:returns: Content element of the entry.
|
||||||
'''
|
'''
|
||||||
if not src is None:
|
if src is not None:
|
||||||
self.__atom_content = {'src':src}
|
self.__atom_content = {'src': src}
|
||||||
elif not content is None:
|
elif content is not None:
|
||||||
self.__atom_content = {'content':content}
|
self.__atom_content = {'content': content}
|
||||||
self.__rss_content = {'content':content}
|
self.__rss_content = {'content': content}
|
||||||
if not type is None:
|
if type is not None:
|
||||||
self.__atom_content['type'] = type
|
self.__atom_content['type'] = type
|
||||||
self.__rss_content['type'] = type
|
self.__rss_content['type'] = type
|
||||||
return self.__atom_content
|
return self.__atom_content
|
||||||
|
|
||||||
|
|
||||||
def link(self, link=None, replace=False, **kwargs):
|
def link(self, link=None, replace=False, **kwargs):
|
||||||
'''Get or set link data. An link element is a dict with the fields href,
|
'''Get or set link data. An link element is a dict with the fields
|
||||||
rel, type, hreflang, title, and length. Href is mandatory for ATOM.
|
href, rel, type, hreflang, title, and length. Href is mandatory for
|
||||||
|
ATOM.
|
||||||
|
|
||||||
This method can be called with:
|
This method can be called with:
|
||||||
- the fields of a link as keyword arguments
|
- the fields of a link as keyword arguments
|
||||||
|
@ -379,8 +379,8 @@ class FeedEntry(object):
|
||||||
or one of the following predefined values (default=alternate):
|
or one of the following predefined values (default=alternate):
|
||||||
|
|
||||||
- *alternate* an alternate representation of the entry or feed, for
|
- *alternate* an alternate representation of the entry or feed, for
|
||||||
example a permalink to the html version of the entry, or the front
|
example a permalink to the html version of the entry, or the
|
||||||
page of the weblog.
|
front page of the weblog.
|
||||||
- *enclosure* a related resource which is potentially large in size
|
- *enclosure* a related resource which is potentially large in size
|
||||||
and might require special handling, for example an audio or video
|
and might require special handling, for example an audio or video
|
||||||
recording.
|
recording.
|
||||||
|
@ -397,9 +397,9 @@ class FeedEntry(object):
|
||||||
RSS only supports one link with nothing but a URL. So for the RSS link
|
RSS only supports one link with nothing but a URL. So for the RSS link
|
||||||
element the last link with rel=alternate is used.
|
element the last link with rel=alternate is used.
|
||||||
|
|
||||||
RSS also supports one enclusure element per entry which is covered by the
|
RSS also supports one enclusure element per entry which is covered by
|
||||||
link element in ATOM feed entries. So for the RSS enclusure element the
|
the link element in ATOM feed entries. So for the RSS enclusure element
|
||||||
last link with rel=enclosure is used.
|
the last link with rel=enclosure is used.
|
||||||
|
|
||||||
:param link: Dict or list of dicts with data.
|
:param link: Dict or list of dicts with data.
|
||||||
:param replace: Add or replace old data.
|
:param replace: Add or replace old data.
|
||||||
|
@ -407,59 +407,59 @@ class FeedEntry(object):
|
||||||
'''
|
'''
|
||||||
if link is None and kwargs:
|
if link is None and kwargs:
|
||||||
link = kwargs
|
link = kwargs
|
||||||
if not link is None:
|
if link is not None:
|
||||||
if replace or self.__atom_link is None:
|
if replace or self.__atom_link is None:
|
||||||
self.__atom_link = []
|
self.__atom_link = []
|
||||||
self.__atom_link += ensure_format( link,
|
self.__atom_link += ensure_format(
|
||||||
|
link,
|
||||||
set(['href', 'rel', 'type', 'hreflang', 'title', 'length']),
|
set(['href', 'rel', 'type', 'hreflang', 'title', 'length']),
|
||||||
set(['href']),
|
set(['href']),
|
||||||
{'rel':['alternate', 'enclosure', 'related', 'self', 'via']},
|
{'rel': ['alternate', 'enclosure', 'related', 'self', 'via']},
|
||||||
{'rel': 'alternate'} )
|
{'rel': 'alternate'})
|
||||||
# RSS only needs one URL. We use the first link for RSS:
|
# RSS only needs one URL. We use the first link for RSS:
|
||||||
for l in self.__atom_link:
|
for l in self.__atom_link:
|
||||||
if l.get('rel') == 'alternate':
|
if l.get('rel') == 'alternate':
|
||||||
self.__rss_link = l['href']
|
self.__rss_link = l['href']
|
||||||
elif l.get('rel') == 'enclosure':
|
elif l.get('rel') == 'enclosure':
|
||||||
self.__rss_enclosure = {'url':l['href']}
|
self.__rss_enclosure = {'url': l['href']}
|
||||||
self.__rss_enclosure['type'] = l.get('type')
|
self.__rss_enclosure['type'] = l.get('type')
|
||||||
self.__rss_enclosure['length'] = l.get('length') or '0'
|
self.__rss_enclosure['length'] = l.get('length') or '0'
|
||||||
# return the set with more information (atom)
|
# return the set with more information (atom)
|
||||||
return self.__atom_link
|
return self.__atom_link
|
||||||
|
|
||||||
|
|
||||||
def summary(self, summary=None):
|
def summary(self, summary=None):
|
||||||
'''Get or set the summary element of an entry which conveys a short
|
'''Get or set the summary element of an entry which conveys a short
|
||||||
summary, abstract, or excerpt of the entry. Summary is an ATOM only
|
summary, abstract, or excerpt of the entry. Summary is an ATOM only
|
||||||
element and should be provided if there either is no content provided for
|
element and should be provided if there either is no content provided
|
||||||
the entry, or that content is not inline (i.e., contains a src
|
for the entry, or that content is not inline (i.e., contains a src
|
||||||
attribute), or if the content is encoded in base64.
|
attribute), or if the content is encoded in base64. This method will
|
||||||
This method will also set the rss:description field if it wasn't
|
also set the rss:description field if it wasn't previously set or
|
||||||
previously set or contains the old value of summary.
|
contains the old value of summary.
|
||||||
|
|
||||||
:param summary: Summary of the entries contents.
|
:param summary: Summary of the entries contents.
|
||||||
:returns: Summary of the entries contents.
|
:returns: Summary of the entries contents.
|
||||||
'''
|
'''
|
||||||
if not summary is None:
|
if summary is not None:
|
||||||
# Replace the RSS description with the summary if it was the summary
|
# Replace the RSS description with the summary if it was the
|
||||||
# before. Not if is the description.
|
# summary before. Not if is the description.
|
||||||
if not self.__rss_description or \
|
if not self.__rss_description or \
|
||||||
self.__rss_description == self.__atom_summary:
|
self.__rss_description == self.__atom_summary:
|
||||||
self.__rss_description = summary
|
self.__rss_description = summary
|
||||||
self.__atom_summary = summary
|
self.__atom_summary = summary
|
||||||
return self.__atom_summary
|
return self.__atom_summary
|
||||||
|
|
||||||
|
|
||||||
def description(self, description=None, isSummary=False):
|
def description(self, description=None, isSummary=False):
|
||||||
'''Get or set the description value which is the item synopsis.
|
'''Get or set the description value which is the item synopsis.
|
||||||
Description is an RSS only element. For ATOM feeds it is split in summary
|
Description is an RSS only element. For ATOM feeds it is split in
|
||||||
and content. The isSummary parameter can be used to control which ATOM
|
summary and content. The isSummary parameter can be used to control
|
||||||
value is set when setting description.
|
which ATOM value is set when setting description.
|
||||||
|
|
||||||
:param description: Description of the entry.
|
:param description: Description of the entry.
|
||||||
:param isSummary: If the description should be used as content or summary.
|
:param isSummary: If the description should be used as content or
|
||||||
|
summary.
|
||||||
:returns: The entries description.
|
:returns: The entries description.
|
||||||
'''
|
'''
|
||||||
if not description is None:
|
if description is not None:
|
||||||
self.__rss_description = description
|
self.__rss_description = description
|
||||||
if isSummary:
|
if isSummary:
|
||||||
self.__atom_summary = description
|
self.__atom_summary = description
|
||||||
|
@ -467,7 +467,6 @@ class FeedEntry(object):
|
||||||
self.__atom_content = description
|
self.__atom_content = description
|
||||||
return self.__rss_description
|
return self.__rss_description
|
||||||
|
|
||||||
|
|
||||||
def category(self, category=None, replace=False, **kwargs):
|
def category(self, category=None, replace=False, **kwargs):
|
||||||
'''Get or set categories that the entry belongs to.
|
'''Get or set categories that the entry belongs to.
|
||||||
|
|
||||||
|
@ -481,8 +480,8 @@ class FeedEntry(object):
|
||||||
- *scheme* identifies the categorization scheme via a URI.
|
- *scheme* identifies the categorization scheme via a URI.
|
||||||
- *label* provides a human-readable label for display
|
- *label* provides a human-readable label for display
|
||||||
|
|
||||||
If a label is present it is used for the RSS feeds. Otherwise the term is
|
If a label is present it is used for the RSS feeds. Otherwise the term
|
||||||
used. The scheme is used for the domain attribute in RSS.
|
is used. The scheme is used for the domain attribute in RSS.
|
||||||
|
|
||||||
:param category: Dict or list of dicts with data.
|
:param category: Dict or list of dicts with data.
|
||||||
:param replace: Add or replace old data.
|
:param replace: Add or replace old data.
|
||||||
|
@ -490,26 +489,25 @@ class FeedEntry(object):
|
||||||
'''
|
'''
|
||||||
if category is None and kwargs:
|
if category is None and kwargs:
|
||||||
category = kwargs
|
category = kwargs
|
||||||
if not category is None:
|
if category is not None:
|
||||||
if replace or self.__atom_category is None:
|
if replace or self.__atom_category is None:
|
||||||
self.__atom_category = []
|
self.__atom_category = []
|
||||||
self.__atom_category += ensure_format(
|
self.__atom_category += ensure_format(
|
||||||
category,
|
category,
|
||||||
set(['term', 'scheme', 'label']),
|
set(['term', 'scheme', 'label']),
|
||||||
set(['term']) )
|
set(['term']))
|
||||||
# Map the ATOM categories to RSS categories. Use the atom:label as
|
# Map the ATOM categories to RSS categories. Use the atom:label as
|
||||||
# name or if not present the atom:term. The atom:scheme is the
|
# name or if not present the atom:term. The atom:scheme is the
|
||||||
# rss:domain.
|
# rss:domain.
|
||||||
self.__rss_category = []
|
self.__rss_category = []
|
||||||
for cat in self.__atom_category:
|
for cat in self.__atom_category:
|
||||||
rss_cat = {}
|
rss_cat = {}
|
||||||
rss_cat['value'] = cat['label'] if cat.get('label') else cat['term']
|
rss_cat['value'] = cat.get('label', cat['term'])
|
||||||
if cat.get('scheme'):
|
if cat.get('scheme'):
|
||||||
rss_cat['domain'] = cat['scheme']
|
rss_cat['domain'] = cat['scheme']
|
||||||
self.__rss_category.append( rss_cat )
|
self.__rss_category.append(rss_cat)
|
||||||
return self.__atom_category
|
return self.__atom_category
|
||||||
|
|
||||||
|
|
||||||
def contributor(self, contributor=None, replace=False, **kwargs):
|
def contributor(self, contributor=None, replace=False, **kwargs):
|
||||||
'''Get or set the contributor data of the feed. This is an ATOM only
|
'''Get or set the contributor data of the feed. This is an ATOM only
|
||||||
value.
|
value.
|
||||||
|
@ -524,32 +522,32 @@ class FeedEntry(object):
|
||||||
- *uri* contains a home page for the person.
|
- *uri* contains a home page for the person.
|
||||||
- *email* contains an email address for the person.
|
- *email* contains an email address for the person.
|
||||||
|
|
||||||
:param contributor: Dictionary or list of dictionaries with contributor data.
|
:param contributor: Dictionary or list of dictionaries with contributor
|
||||||
|
data.
|
||||||
:param replace: Add or replace old data.
|
:param replace: Add or replace old data.
|
||||||
:returns: List of contributors as dictionaries.
|
:returns: List of contributors as dictionaries.
|
||||||
'''
|
'''
|
||||||
if contributor is None and kwargs:
|
if contributor is None and kwargs:
|
||||||
contributor = kwargs
|
contributor = kwargs
|
||||||
if not contributor is None:
|
if contributor is not None:
|
||||||
if replace or self.__atom_contributor is None:
|
if replace or self.__atom_contributor is None:
|
||||||
self.__atom_contributor = []
|
self.__atom_contributor = []
|
||||||
self.__atom_contributor += ensure_format( contributor,
|
self.__atom_contributor += ensure_format(
|
||||||
set(['name', 'email', 'uri']), set(['name']))
|
contributor, set(['name', 'email', 'uri']), set(['name']))
|
||||||
return self.__atom_contributor
|
return self.__atom_contributor
|
||||||
|
|
||||||
|
|
||||||
def published(self, published=None):
|
def published(self, published=None):
|
||||||
'''Set or get the published value which contains the time of the initial
|
'''Set or get the published value which contains the time of the initial
|
||||||
creation or first availability of the entry.
|
creation or first availability of the entry.
|
||||||
|
|
||||||
The value can either be a string which will automatically be parsed or a
|
The value can either be a string which will automatically be parsed or
|
||||||
datetime.datetime object. In any case it is necessary that the value
|
a datetime.datetime object. In any case it is necessary that the value
|
||||||
include timezone information.
|
include timezone information.
|
||||||
|
|
||||||
:param published: The creation date.
|
:param published: The creation date.
|
||||||
:returns: Creation date as datetime.datetime
|
:returns: Creation date as datetime.datetime
|
||||||
'''
|
'''
|
||||||
if not published is None:
|
if published is not None:
|
||||||
if isinstance(published, string_types):
|
if isinstance(published, string_types):
|
||||||
published = dateutil.parser.parse(published)
|
published = dateutil.parser.parse(published)
|
||||||
if not isinstance(published, datetime):
|
if not isinstance(published, datetime):
|
||||||
|
@ -561,71 +559,65 @@ class FeedEntry(object):
|
||||||
|
|
||||||
return self.__atom_published
|
return self.__atom_published
|
||||||
|
|
||||||
|
|
||||||
def pubdate(self, pubDate=None):
|
def pubdate(self, pubDate=None):
|
||||||
'''Get or set the pubDate of the entry which indicates when the entry was
|
'''Get or set the pubDate of the entry which indicates when the entry
|
||||||
published. This method is just another name for the published(...)
|
was published. This method is just another name for the published(...)
|
||||||
method.
|
method.
|
||||||
'''
|
'''
|
||||||
return self.published(pubDate)
|
return self.published(pubDate)
|
||||||
|
|
||||||
|
|
||||||
def rights(self, rights=None):
|
def rights(self, rights=None):
|
||||||
'''Get or set the rights value of the entry which conveys information
|
'''Get or set the rights value of the entry which conveys information
|
||||||
about rights, e.g. copyrights, held in and over the entry. This ATOM value
|
about rights, e.g. copyrights, held in and over the entry. This ATOM
|
||||||
will also set rss:copyright.
|
value will also set rss:copyright.
|
||||||
|
|
||||||
:param rights: Rights information of the feed.
|
:param rights: Rights information of the feed.
|
||||||
:returns: Rights information of the feed.
|
:returns: Rights information of the feed.
|
||||||
'''
|
'''
|
||||||
if not rights is None:
|
if rights is not None:
|
||||||
self.__atom_rights = rights
|
self.__atom_rights = rights
|
||||||
return self.__atom_rights
|
return self.__atom_rights
|
||||||
|
|
||||||
|
|
||||||
def comments(self, comments=None):
|
def comments(self, comments=None):
|
||||||
'''Get or set the the value of comments which is the url of the comments
|
'''Get or set the the value of comments which is the url of the
|
||||||
page for the item. This is a RSS only value.
|
comments page for the item. This is a RSS only value.
|
||||||
|
|
||||||
:param comments: URL to the comments page.
|
:param comments: URL to the comments page.
|
||||||
:returns: URL to the comments page.
|
:returns: URL to the comments page.
|
||||||
'''
|
'''
|
||||||
if not comments is None:
|
if comments is not None:
|
||||||
self.__rss_comments = comments
|
self.__rss_comments = comments
|
||||||
return self.__rss_comments
|
return self.__rss_comments
|
||||||
|
|
||||||
|
|
||||||
def enclosure(self, url=None, length=None, type=None):
|
def enclosure(self, url=None, length=None, type=None):
|
||||||
'''Get or set the value of enclosure which describes a media object that
|
'''Get or set the value of enclosure which describes a media object
|
||||||
is attached to the item. This is a RSS only value which is represented by
|
that is attached to the item. This is a RSS only value which is
|
||||||
link(rel=enclosure) in ATOM. ATOM feeds can furthermore contain several
|
represented by link(rel=enclosure) in ATOM. ATOM feeds can furthermore
|
||||||
enclosures while RSS may contain only one. That is why this method, if
|
contain several enclosures while RSS may contain only one. That is why
|
||||||
repeatedly called, will add more than one enclosures to the feed.
|
this method, if repeatedly called, will add more than one enclosures to
|
||||||
However, only the last one is used for RSS.
|
the feed. However, only the last one is used for RSS.
|
||||||
|
|
||||||
:param url: URL of the media object.
|
:param url: URL of the media object.
|
||||||
:param length: Size of the media in bytes.
|
:param length: Size of the media in bytes.
|
||||||
:param type: Mimetype of the linked media.
|
:param type: Mimetype of the linked media.
|
||||||
:returns: Data of the enclosure element.
|
:returns: Data of the enclosure element.
|
||||||
'''
|
'''
|
||||||
if not url is None:
|
if url is not None:
|
||||||
self.link( href=url, rel='enclosure', type=type, length=length )
|
self.link(href=url, rel='enclosure', type=type, length=length)
|
||||||
return self.__rss_enclosure
|
return self.__rss_enclosure
|
||||||
|
|
||||||
|
|
||||||
def ttl(self, ttl=None):
|
def ttl(self, ttl=None):
|
||||||
'''Get or set the ttl value. It is an RSS only element. ttl stands for
|
'''Get or set the ttl value. It is an RSS only element. ttl stands for
|
||||||
time to live. It's a number of minutes that indicates how long a channel
|
time to live. It's a number of minutes that indicates how long a
|
||||||
can be cached before refreshing from the source.
|
channel can be cached before refreshing from the source.
|
||||||
|
|
||||||
:param ttl: Integer value representing the time to live.
|
:param ttl: Integer value representing the time to live.
|
||||||
:returns: Time to live of of the entry.
|
:returns: Time to live of of the entry.
|
||||||
'''
|
'''
|
||||||
if not ttl is None:
|
if ttl is not None:
|
||||||
self.__rss_ttl = int(ttl)
|
self.__rss_ttl = int(ttl)
|
||||||
return self.__rss_ttl
|
return self.__rss_ttl
|
||||||
|
|
||||||
|
|
||||||
def load_extension(self, name, atom=True, rss=True):
|
def load_extension(self, name, atom=True, rss=True):
|
||||||
'''Load a specific extension by name.
|
'''Load a specific extension by name.
|
||||||
|
|
||||||
|
@ -651,7 +643,6 @@ class FeedEntry(object):
|
||||||
ext = getattr(extmod, extname)
|
ext = getattr(extmod, extname)
|
||||||
self.register_extension(name, ext, atom, rss)
|
self.register_extension(name, ext, atom, rss)
|
||||||
|
|
||||||
|
|
||||||
def register_extension(self, namespace, extension_class_entry=None,
|
def register_extension(self, namespace, extension_class_entry=None,
|
||||||
atom=True, rss=True):
|
atom=True, rss=True):
|
||||||
'''Register a specific extension by classes to a namespace.
|
'''Register a specific extension by classes to a namespace.
|
||||||
|
@ -675,8 +666,8 @@ class FeedEntry(object):
|
||||||
|
|
||||||
# `load_extension` registry
|
# `load_extension` registry
|
||||||
self.__extensions[namespace] = {
|
self.__extensions[namespace] = {
|
||||||
'inst':extinst,
|
'inst': extinst,
|
||||||
'extension_class_entry': extension_class_entry,
|
'extension_class_entry': extension_class_entry,
|
||||||
'atom':atom,
|
'atom': atom,
|
||||||
'rss':rss
|
'rss': rss
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,8 @@ class BaseExtension(object):
|
||||||
'''Basic FeedGenerator extension.
|
'''Basic FeedGenerator extension.
|
||||||
'''
|
'''
|
||||||
def extend_ns(self):
|
def extend_ns(self):
|
||||||
'''Returns a dict that will be used in the namespace map for the feed.'''
|
'''Returns a dict that will be used in the namespace map for the feed.
|
||||||
|
'''
|
||||||
return dict()
|
return dict()
|
||||||
|
|
||||||
def extend_rss(self, feed):
|
def extend_rss(self, feed):
|
||||||
|
@ -27,7 +28,6 @@ class BaseExtension(object):
|
||||||
'''
|
'''
|
||||||
return feed
|
return feed
|
||||||
|
|
||||||
|
|
||||||
def extend_atom(self, feed):
|
def extend_atom(self, feed):
|
||||||
'''Extend an ATOM feed xml structure containing all previously set
|
'''Extend an ATOM feed xml structure containing all previously set
|
||||||
fields.
|
fields.
|
||||||
|
|
|
@ -8,20 +8,19 @@
|
||||||
Descriptions partly taken from
|
Descriptions partly taken from
|
||||||
http://dublincore.org/documents/dcmi-terms/#elements-coverage
|
http://dublincore.org/documents/dcmi-terms/#elements-coverage
|
||||||
|
|
||||||
:copyright: 2013, Lars Kiesow <lkiesow@uos.de>
|
:copyright: 2013-2016, Lars Kiesow <lkiesow@uos.de>
|
||||||
|
|
||||||
:license: FreeBSD and LGPL, see license.* for more details.
|
:license: FreeBSD and LGPL, see license.* for more details.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
from feedgen.ext.base import BaseExtension, BaseEntryExtension
|
from feedgen.ext.base import BaseExtension
|
||||||
|
|
||||||
|
|
||||||
class DcBaseExtension(BaseExtension):
|
class DcBaseExtension(BaseExtension):
|
||||||
'''Dublin Core Elements extension for podcasts.
|
'''Dublin Core Elements extension for podcasts.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
# http://dublincore.org/documents/usageguide/elements.shtml
|
# http://dublincore.org/documents/usageguide/elements.shtml
|
||||||
# http://dublincore.org/documents/dces/
|
# http://dublincore.org/documents/dces/
|
||||||
|
@ -43,7 +42,7 @@ class DcBaseExtension(BaseExtension):
|
||||||
self._dcelem_type = None
|
self._dcelem_type = None
|
||||||
|
|
||||||
def extend_ns(self):
|
def extend_ns(self):
|
||||||
return {'dc' : 'http://purl.org/dc/elements/1.1/'}
|
return {'dc': 'http://purl.org/dc/elements/1.1/'}
|
||||||
|
|
||||||
def _extend_xml(self, xml_elem):
|
def _extend_xml(self, xml_elem):
|
||||||
'''Extend xml_elem with set DC fields.
|
'''Extend xml_elem with set DC fields.
|
||||||
|
@ -52,15 +51,16 @@ class DcBaseExtension(BaseExtension):
|
||||||
'''
|
'''
|
||||||
DCELEMENTS_NS = 'http://purl.org/dc/elements/1.1/'
|
DCELEMENTS_NS = 'http://purl.org/dc/elements/1.1/'
|
||||||
|
|
||||||
for elem in ['contributor', 'coverage', 'creator', 'date', 'description',
|
for elem in ['contributor', 'coverage', 'creator', 'date',
|
||||||
'language', 'publisher', 'relation', 'rights', 'source', 'subject',
|
'description', 'language', 'publisher', 'relation',
|
||||||
'title', 'type', 'format', 'identifier']:
|
'rights', 'source', 'subject', 'title', 'type', 'format',
|
||||||
|
'identifier']:
|
||||||
if hasattr(self, '_dcelem_%s' % elem):
|
if hasattr(self, '_dcelem_%s' % elem):
|
||||||
for val in getattr(self, '_dcelem_%s' % elem) or []:
|
for val in getattr(self, '_dcelem_%s' % elem) or []:
|
||||||
node = etree.SubElement(xml_elem, '{%s}%s' % (DCELEMENTS_NS, elem))
|
node = etree.SubElement(xml_elem,
|
||||||
|
'{%s}%s' % (DCELEMENTS_NS, elem))
|
||||||
node.text = val
|
node.text = val
|
||||||
|
|
||||||
|
|
||||||
def extend_atom(self, atom_feed):
|
def extend_atom(self, atom_feed):
|
||||||
'''Extend an Atom feed with the set DC fields.
|
'''Extend an Atom feed with the set DC fields.
|
||||||
|
|
||||||
|
@ -72,8 +72,6 @@ class DcBaseExtension(BaseExtension):
|
||||||
|
|
||||||
return atom_feed
|
return atom_feed
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def extend_rss(self, rss_feed):
|
def extend_rss(self, rss_feed):
|
||||||
'''Extend a RSS feed with the set DC fields.
|
'''Extend a RSS feed with the set DC fields.
|
||||||
|
|
||||||
|
@ -85,7 +83,6 @@ class DcBaseExtension(BaseExtension):
|
||||||
|
|
||||||
return rss_feed
|
return rss_feed
|
||||||
|
|
||||||
|
|
||||||
def dc_contributor(self, contributor=None, replace=False):
|
def dc_contributor(self, contributor=None, replace=False):
|
||||||
'''Get or set the dc:contributor which is an entity responsible for
|
'''Get or set the dc:contributor which is an entity responsible for
|
||||||
making contributions to the resource.
|
making contributions to the resource.
|
||||||
|
@ -97,7 +94,7 @@ class DcBaseExtension(BaseExtension):
|
||||||
:param replace: Replace alredy set contributors (deault: False).
|
:param replace: Replace alredy set contributors (deault: False).
|
||||||
:returns: List of contributors.
|
:returns: List of contributors.
|
||||||
'''
|
'''
|
||||||
if not contributor is None:
|
if contributor is not None:
|
||||||
if not isinstance(contributor, list):
|
if not isinstance(contributor, list):
|
||||||
contributor = [contributor]
|
contributor = [contributor]
|
||||||
if replace or not self._dcelem_contributor:
|
if replace or not self._dcelem_contributor:
|
||||||
|
@ -105,28 +102,28 @@ class DcBaseExtension(BaseExtension):
|
||||||
self._dcelem_contributor += contributor
|
self._dcelem_contributor += contributor
|
||||||
return self._dcelem_contributor
|
return self._dcelem_contributor
|
||||||
|
|
||||||
|
|
||||||
def dc_coverage(self, coverage=None, replace=True):
|
def dc_coverage(self, coverage=None, replace=True):
|
||||||
'''Get or set the dc:coverage which indicated the spatial or temporal
|
'''Get or set the dc:coverage which indicated the spatial or temporal
|
||||||
topic of the resource, the spatial applicability of the resource, or the
|
topic of the resource, the spatial applicability of the resource, or
|
||||||
jurisdiction under which the resource is relevant.
|
the jurisdiction under which the resource is relevant.
|
||||||
|
|
||||||
Spatial topic and spatial applicability may be a named place or a
|
Spatial topic and spatial applicability may be a named place or a
|
||||||
location specified by its geographic coordinates. Temporal topic may be a
|
location specified by its geographic coordinates. Temporal topic may be
|
||||||
named period, date, or date range. A jurisdiction may be a named
|
a named period, date, or date range. A jurisdiction may be a named
|
||||||
administrative entity or a geographic place to which the resource
|
administrative entity or a geographic place to which the resource
|
||||||
applies. Recommended best practice is to use a controlled vocabulary such
|
applies. Recommended best practice is to use a controlled vocabulary
|
||||||
as the Thesaurus of Geographic Names [TGN]. Where appropriate, named
|
such as the Thesaurus of Geographic Names [TGN]. Where appropriate,
|
||||||
places or time periods can be used in preference to numeric identifiers
|
named places or time periods can be used in preference to numeric
|
||||||
such as sets of coordinates or date ranges.
|
identifiers such as sets of coordinates or date ranges.
|
||||||
|
|
||||||
References: [TGN] http://www.getty.edu/research/tools/vocabulary/tgn/index.html
|
References:
|
||||||
|
[TGN] http://www.getty.edu/research/tools/vocabulary/tgn/index.html
|
||||||
|
|
||||||
:param coverage: Coverage of the feed.
|
:param coverage: Coverage of the feed.
|
||||||
:param replace: Replace already set coverage (default: True).
|
:param replace: Replace already set coverage (default: True).
|
||||||
:returns: Coverage of the feed.
|
:returns: Coverage of the feed.
|
||||||
'''
|
'''
|
||||||
if not coverage is None:
|
if coverage is not None:
|
||||||
if not isinstance(coverage, list):
|
if not isinstance(coverage, list):
|
||||||
coverage = [coverage]
|
coverage = [coverage]
|
||||||
if replace or not self._dcelem_coverage:
|
if replace or not self._dcelem_coverage:
|
||||||
|
@ -134,10 +131,9 @@ class DcBaseExtension(BaseExtension):
|
||||||
self._dcelem_coverage = coverage
|
self._dcelem_coverage = coverage
|
||||||
return self._dcelem_coverage
|
return self._dcelem_coverage
|
||||||
|
|
||||||
|
|
||||||
def dc_creator(self, creator=None, replace=False):
|
def dc_creator(self, creator=None, replace=False):
|
||||||
'''Get or set the dc:creator which is an entity primarily responsible for
|
'''Get or set the dc:creator which is an entity primarily responsible
|
||||||
making the resource.
|
for making the resource.
|
||||||
|
|
||||||
For more information see:
|
For more information see:
|
||||||
http://dublincore.org/documents/dcmi-terms/#elements-creator
|
http://dublincore.org/documents/dcmi-terms/#elements-creator
|
||||||
|
@ -146,7 +142,7 @@ class DcBaseExtension(BaseExtension):
|
||||||
:param replace: Replace alredy set creators (deault: False).
|
:param replace: Replace alredy set creators (deault: False).
|
||||||
:returns: List of creators.
|
:returns: List of creators.
|
||||||
'''
|
'''
|
||||||
if not creator is None:
|
if creator is not None:
|
||||||
if not isinstance(creator, list):
|
if not isinstance(creator, list):
|
||||||
creator = [creator]
|
creator = [creator]
|
||||||
if replace or not self._dcelem_creator:
|
if replace or not self._dcelem_creator:
|
||||||
|
@ -154,7 +150,6 @@ class DcBaseExtension(BaseExtension):
|
||||||
self._dcelem_creator += creator
|
self._dcelem_creator += creator
|
||||||
return self._dcelem_creator
|
return self._dcelem_creator
|
||||||
|
|
||||||
|
|
||||||
def dc_date(self, date=None, replace=True):
|
def dc_date(self, date=None, replace=True):
|
||||||
'''Get or set the dc:date which describes a point or period of time
|
'''Get or set the dc:date which describes a point or period of time
|
||||||
associated with an event in the lifecycle of the resource.
|
associated with an event in the lifecycle of the resource.
|
||||||
|
@ -166,7 +161,7 @@ class DcBaseExtension(BaseExtension):
|
||||||
:param replace: Replace alredy set dates (deault: True).
|
:param replace: Replace alredy set dates (deault: True).
|
||||||
:returns: List of dates.
|
:returns: List of dates.
|
||||||
'''
|
'''
|
||||||
if not date is None:
|
if date is not None:
|
||||||
if not isinstance(date, list):
|
if not isinstance(date, list):
|
||||||
date = [date]
|
date = [date]
|
||||||
if replace or not self._dcelem_date:
|
if replace or not self._dcelem_date:
|
||||||
|
@ -174,7 +169,6 @@ class DcBaseExtension(BaseExtension):
|
||||||
self._dcelem_date += date
|
self._dcelem_date += date
|
||||||
return self._dcelem_date
|
return self._dcelem_date
|
||||||
|
|
||||||
|
|
||||||
def dc_description(self, description=None, replace=True):
|
def dc_description(self, description=None, replace=True):
|
||||||
'''Get or set the dc:description which is an account of the resource.
|
'''Get or set the dc:description which is an account of the resource.
|
||||||
|
|
||||||
|
@ -185,7 +179,7 @@ class DcBaseExtension(BaseExtension):
|
||||||
:param replace: Replace alredy set descriptions (deault: True).
|
:param replace: Replace alredy set descriptions (deault: True).
|
||||||
:returns: List of descriptions.
|
:returns: List of descriptions.
|
||||||
'''
|
'''
|
||||||
if not description is None:
|
if description is not None:
|
||||||
if not isinstance(description, list):
|
if not isinstance(description, list):
|
||||||
description = [description]
|
description = [description]
|
||||||
if replace or not self._dcelem_description:
|
if replace or not self._dcelem_description:
|
||||||
|
@ -193,7 +187,6 @@ class DcBaseExtension(BaseExtension):
|
||||||
self._dcelem_description += description
|
self._dcelem_description += description
|
||||||
return self._dcelem_description
|
return self._dcelem_description
|
||||||
|
|
||||||
|
|
||||||
def dc_format(self, format=None, replace=True):
|
def dc_format(self, format=None, replace=True):
|
||||||
'''Get or set the dc:format which describes the file format, physical
|
'''Get or set the dc:format which describes the file format, physical
|
||||||
medium, or dimensions of the resource.
|
medium, or dimensions of the resource.
|
||||||
|
@ -205,7 +198,7 @@ class DcBaseExtension(BaseExtension):
|
||||||
:param replace: Replace alredy set format (deault: True).
|
:param replace: Replace alredy set format (deault: True).
|
||||||
:returns: Format of the resource.
|
:returns: Format of the resource.
|
||||||
'''
|
'''
|
||||||
if not format is None:
|
if format is not None:
|
||||||
if not isinstance(format, list):
|
if not isinstance(format, list):
|
||||||
format = [format]
|
format = [format]
|
||||||
if replace or not self._dcelem_format:
|
if replace or not self._dcelem_format:
|
||||||
|
@ -213,10 +206,9 @@ class DcBaseExtension(BaseExtension):
|
||||||
self._dcelem_format += format
|
self._dcelem_format += format
|
||||||
return self._dcelem_format
|
return self._dcelem_format
|
||||||
|
|
||||||
|
|
||||||
def dc_identifier(self, identifier=None, replace=True):
|
def dc_identifier(self, identifier=None, replace=True):
|
||||||
'''Get or set the dc:identifier which should be an unambiguous reference
|
'''Get or set the dc:identifier which should be an unambiguous
|
||||||
to the resource within a given context.
|
reference to the resource within a given context.
|
||||||
|
|
||||||
For more inidentifierion see:
|
For more inidentifierion see:
|
||||||
http://dublincore.org/documents/dcmi-terms/#elements-identifier
|
http://dublincore.org/documents/dcmi-terms/#elements-identifier
|
||||||
|
@ -225,16 +217,16 @@ class DcBaseExtension(BaseExtension):
|
||||||
:param replace: Replace alredy set identifier (deault: True).
|
:param replace: Replace alredy set identifier (deault: True).
|
||||||
:returns: Identifiers of the resource.
|
:returns: Identifiers of the resource.
|
||||||
'''
|
'''
|
||||||
if not identifier is None:
|
if identifier is not None:
|
||||||
if not isinstance(identifier, list):
|
if not isinstance(identifier, list):
|
||||||
identifier = [identifier]
|
identifier = [identifier]
|
||||||
if replace or not self._dcelem_identifier:
|
if replace or not self._dcelem_identifier:
|
||||||
self._dcelem_identifier = []
|
self._dcelem_identifier = []
|
||||||
self._dcelem_identifier += identifier
|
self._dcelem_identifier += identifier
|
||||||
|
|
||||||
|
|
||||||
def dc_language(self, language=None, replace=True):
|
def dc_language(self, language=None, replace=True):
|
||||||
'''Get or set the dc:language which describes a language of the resource.
|
'''Get or set the dc:language which describes a language of the
|
||||||
|
resource.
|
||||||
|
|
||||||
For more information see:
|
For more information see:
|
||||||
http://dublincore.org/documents/dcmi-terms/#elements-language
|
http://dublincore.org/documents/dcmi-terms/#elements-language
|
||||||
|
@ -243,7 +235,7 @@ class DcBaseExtension(BaseExtension):
|
||||||
:param replace: Replace alredy set languages (deault: True).
|
:param replace: Replace alredy set languages (deault: True).
|
||||||
:returns: List of languages.
|
:returns: List of languages.
|
||||||
'''
|
'''
|
||||||
if not language is None:
|
if language is not None:
|
||||||
if not isinstance(language, list):
|
if not isinstance(language, list):
|
||||||
language = [language]
|
language = [language]
|
||||||
if replace or not self._dcelem_language:
|
if replace or not self._dcelem_language:
|
||||||
|
@ -251,10 +243,9 @@ class DcBaseExtension(BaseExtension):
|
||||||
self._dcelem_language += language
|
self._dcelem_language += language
|
||||||
return self._dcelem_language
|
return self._dcelem_language
|
||||||
|
|
||||||
|
|
||||||
def dc_publisher(self, publisher=None, replace=False):
|
def dc_publisher(self, publisher=None, replace=False):
|
||||||
'''Get or set the dc:publisher which is an entity responsible for making
|
'''Get or set the dc:publisher which is an entity responsible for
|
||||||
the resource available.
|
making the resource available.
|
||||||
|
|
||||||
For more information see:
|
For more information see:
|
||||||
http://dublincore.org/documents/dcmi-terms/#elements-publisher
|
http://dublincore.org/documents/dcmi-terms/#elements-publisher
|
||||||
|
@ -263,7 +254,7 @@ class DcBaseExtension(BaseExtension):
|
||||||
:param replace: Replace alredy set publishers (deault: False).
|
:param replace: Replace alredy set publishers (deault: False).
|
||||||
:returns: List of publishers.
|
:returns: List of publishers.
|
||||||
'''
|
'''
|
||||||
if not publisher is None:
|
if publisher is not None:
|
||||||
if not isinstance(publisher, list):
|
if not isinstance(publisher, list):
|
||||||
publisher = [publisher]
|
publisher = [publisher]
|
||||||
if replace or not self._dcelem_publisher:
|
if replace or not self._dcelem_publisher:
|
||||||
|
@ -271,7 +262,6 @@ class DcBaseExtension(BaseExtension):
|
||||||
self._dcelem_publisher += publisher
|
self._dcelem_publisher += publisher
|
||||||
return self._dcelem_publisher
|
return self._dcelem_publisher
|
||||||
|
|
||||||
|
|
||||||
def dc_relation(self, relation=None, replace=False):
|
def dc_relation(self, relation=None, replace=False):
|
||||||
'''Get or set the dc:relation which describes a related ressource.
|
'''Get or set the dc:relation which describes a related ressource.
|
||||||
|
|
||||||
|
@ -282,7 +272,7 @@ class DcBaseExtension(BaseExtension):
|
||||||
:param replace: Replace alredy set relations (deault: False).
|
:param replace: Replace alredy set relations (deault: False).
|
||||||
:returns: List of relations.
|
:returns: List of relations.
|
||||||
'''
|
'''
|
||||||
if not relation is None:
|
if relation is not None:
|
||||||
if not isinstance(relation, list):
|
if not isinstance(relation, list):
|
||||||
relation = [relation]
|
relation = [relation]
|
||||||
if replace or not self._dcelem_relation:
|
if replace or not self._dcelem_relation:
|
||||||
|
@ -290,7 +280,6 @@ class DcBaseExtension(BaseExtension):
|
||||||
self._dcelem_relation += relation
|
self._dcelem_relation += relation
|
||||||
return self._dcelem_relation
|
return self._dcelem_relation
|
||||||
|
|
||||||
|
|
||||||
def dc_rights(self, rights=None, replace=False):
|
def dc_rights(self, rights=None, replace=False):
|
||||||
'''Get or set the dc:rights which may contain information about rights
|
'''Get or set the dc:rights which may contain information about rights
|
||||||
held in and over the resource.
|
held in and over the resource.
|
||||||
|
@ -302,7 +291,7 @@ class DcBaseExtension(BaseExtension):
|
||||||
:param replace: Replace alredy set rightss (deault: False).
|
:param replace: Replace alredy set rightss (deault: False).
|
||||||
:returns: List of rights information.
|
:returns: List of rights information.
|
||||||
'''
|
'''
|
||||||
if not rights is None:
|
if rights is not None:
|
||||||
if not isinstance(rights, list):
|
if not isinstance(rights, list):
|
||||||
rights = [rights]
|
rights = [rights]
|
||||||
if replace or not self._dcelem_rights:
|
if replace or not self._dcelem_rights:
|
||||||
|
@ -310,15 +299,14 @@ class DcBaseExtension(BaseExtension):
|
||||||
self._dcelem_rights += rights
|
self._dcelem_rights += rights
|
||||||
return self._dcelem_rights
|
return self._dcelem_rights
|
||||||
|
|
||||||
|
|
||||||
def dc_source(self, source=None, replace=False):
|
def dc_source(self, source=None, replace=False):
|
||||||
'''Get or set the dc:source which is a related resource from which the
|
'''Get or set the dc:source which is a related resource from which the
|
||||||
described resource is derived.
|
described resource is derived.
|
||||||
|
|
||||||
The described resource may be derived from the related resource in whole
|
The described resource may be derived from the related resource in
|
||||||
or in part. Recommended best practice is to identify the related resource
|
whole or in part. Recommended best practice is to identify the related
|
||||||
by means of a string conforming to a formal identification system.
|
resource by means of a string conforming to a formal identification
|
||||||
|
system.
|
||||||
|
|
||||||
For more information see:
|
For more information see:
|
||||||
http://dublincore.org/documents/dcmi-terms/#elements-source
|
http://dublincore.org/documents/dcmi-terms/#elements-source
|
||||||
|
@ -327,7 +315,7 @@ class DcBaseExtension(BaseExtension):
|
||||||
:param replace: Replace alredy set sources (deault: False).
|
:param replace: Replace alredy set sources (deault: False).
|
||||||
:returns: List of sources.
|
:returns: List of sources.
|
||||||
'''
|
'''
|
||||||
if not source is None:
|
if source is not None:
|
||||||
if not isinstance(source, list):
|
if not isinstance(source, list):
|
||||||
source = [source]
|
source = [source]
|
||||||
if replace or not self._dcelem_source:
|
if replace or not self._dcelem_source:
|
||||||
|
@ -335,7 +323,6 @@ class DcBaseExtension(BaseExtension):
|
||||||
self._dcelem_source += source
|
self._dcelem_source += source
|
||||||
return self._dcelem_source
|
return self._dcelem_source
|
||||||
|
|
||||||
|
|
||||||
def dc_subject(self, subject=None, replace=False):
|
def dc_subject(self, subject=None, replace=False):
|
||||||
'''Get or set the dc:subject which describes the topic of the resource.
|
'''Get or set the dc:subject which describes the topic of the resource.
|
||||||
|
|
||||||
|
@ -346,7 +333,7 @@ class DcBaseExtension(BaseExtension):
|
||||||
:param replace: Replace alredy set subjects (deault: False).
|
:param replace: Replace alredy set subjects (deault: False).
|
||||||
:returns: List of subjects.
|
:returns: List of subjects.
|
||||||
'''
|
'''
|
||||||
if not subject is None:
|
if subject is not None:
|
||||||
if not isinstance(subject, list):
|
if not isinstance(subject, list):
|
||||||
subject = [subject]
|
subject = [subject]
|
||||||
if replace or not self._dcelem_subject:
|
if replace or not self._dcelem_subject:
|
||||||
|
@ -354,7 +341,6 @@ class DcBaseExtension(BaseExtension):
|
||||||
self._dcelem_subject += subject
|
self._dcelem_subject += subject
|
||||||
return self._dcelem_subject
|
return self._dcelem_subject
|
||||||
|
|
||||||
|
|
||||||
def dc_title(self, title=None, replace=True):
|
def dc_title(self, title=None, replace=True):
|
||||||
'''Get or set the dc:title which is a name given to the resource.
|
'''Get or set the dc:title which is a name given to the resource.
|
||||||
|
|
||||||
|
@ -365,7 +351,7 @@ class DcBaseExtension(BaseExtension):
|
||||||
:param replace: Replace alredy set titles (deault: False).
|
:param replace: Replace alredy set titles (deault: False).
|
||||||
:returns: List of titles.
|
:returns: List of titles.
|
||||||
'''
|
'''
|
||||||
if not title is None:
|
if title is not None:
|
||||||
if not isinstance(title, list):
|
if not isinstance(title, list):
|
||||||
title = [title]
|
title = [title]
|
||||||
if replace or not self._dcelem_title:
|
if replace or not self._dcelem_title:
|
||||||
|
@ -373,7 +359,6 @@ class DcBaseExtension(BaseExtension):
|
||||||
self._dcelem_title += title
|
self._dcelem_title += title
|
||||||
return self._dcelem_title
|
return self._dcelem_title
|
||||||
|
|
||||||
|
|
||||||
def dc_type(self, type=None, replace=False):
|
def dc_type(self, type=None, replace=False):
|
||||||
'''Get or set the dc:type which describes the nature or genre of the
|
'''Get or set the dc:type which describes the nature or genre of the
|
||||||
resource.
|
resource.
|
||||||
|
@ -385,7 +370,7 @@ class DcBaseExtension(BaseExtension):
|
||||||
:param replace: Replace alredy set types (deault: False).
|
:param replace: Replace alredy set types (deault: False).
|
||||||
:returns: List of types.
|
:returns: List of types.
|
||||||
'''
|
'''
|
||||||
if not type is None:
|
if type is not None:
|
||||||
if not isinstance(type, list):
|
if not isinstance(type, list):
|
||||||
type = [type]
|
type = [type]
|
||||||
if replace or not self._dcelem_type:
|
if replace or not self._dcelem_type:
|
||||||
|
@ -393,10 +378,12 @@ class DcBaseExtension(BaseExtension):
|
||||||
self._dcelem_type += type
|
self._dcelem_type += type
|
||||||
return self._dcelem_type
|
return self._dcelem_type
|
||||||
|
|
||||||
|
|
||||||
class DcExtension(DcBaseExtension):
|
class DcExtension(DcBaseExtension):
|
||||||
'''Dublin Core Elements extension for podcasts.
|
'''Dublin Core Elements extension for podcasts.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
|
||||||
class DcEntryExtension(DcBaseExtension):
|
class DcEntryExtension(DcBaseExtension):
|
||||||
'''Dublin Core Elements extension for podcasts.
|
'''Dublin Core Elements extension for podcasts.
|
||||||
'''
|
'''
|
||||||
|
|
|
@ -20,9 +20,8 @@ class PodcastExtension(BaseExtension):
|
||||||
'''FeedGenerator extension for podcasts.
|
'''FeedGenerator extension for podcasts.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
## ITunes tags
|
# ITunes tags
|
||||||
# http://www.apple.com/itunes/podcasts/specs.html#rss
|
# http://www.apple.com/itunes/podcasts/specs.html#rss
|
||||||
self.__itunes_author = None
|
self.__itunes_author = None
|
||||||
self.__itunes_block = None
|
self.__itunes_block = None
|
||||||
|
@ -35,10 +34,8 @@ class PodcastExtension(BaseExtension):
|
||||||
self.__itunes_subtitle = None
|
self.__itunes_subtitle = None
|
||||||
self.__itunes_summary = None
|
self.__itunes_summary = None
|
||||||
|
|
||||||
|
|
||||||
def extend_ns(self):
|
def extend_ns(self):
|
||||||
return {'itunes' : 'http://www.itunes.com/dtds/podcast-1.0.dtd'}
|
return {'itunes': 'http://www.itunes.com/dtds/podcast-1.0.dtd'}
|
||||||
|
|
||||||
|
|
||||||
def extend_rss(self, rss_feed):
|
def extend_rss(self, rss_feed):
|
||||||
'''Extend an RSS feed root with set itunes fields.
|
'''Extend an RSS feed root with set itunes fields.
|
||||||
|
@ -52,20 +49,23 @@ class PodcastExtension(BaseExtension):
|
||||||
author = etree.SubElement(channel, '{%s}author' % ITUNES_NS)
|
author = etree.SubElement(channel, '{%s}author' % ITUNES_NS)
|
||||||
author.text = self.__itunes_author
|
author.text = self.__itunes_author
|
||||||
|
|
||||||
if not self.__itunes_block is None:
|
if self.__itunes_block is not None:
|
||||||
block = etree.SubElement(channel, '{%s}block' % ITUNES_NS)
|
block = etree.SubElement(channel, '{%s}block' % ITUNES_NS)
|
||||||
block.text = 'yes' if self.__itunes_block else 'no'
|
block.text = 'yes' if self.__itunes_block else 'no'
|
||||||
|
|
||||||
for c in self.__itunes_category or []:
|
for c in self.__itunes_category or []:
|
||||||
if not c.get('cat'):
|
if not c.get('cat'):
|
||||||
continue
|
continue
|
||||||
category = channel.find('{%s}category[@text="%s"]' % (ITUNES_NS,c.get('cat')))
|
category = channel.find(
|
||||||
if category == None:
|
'{%s}category[@text="%s"]' % (ITUNES_NS, c.get('cat')))
|
||||||
category = etree.SubElement(channel, '{%s}category' % ITUNES_NS)
|
if category is None:
|
||||||
|
category = etree.SubElement(channel,
|
||||||
|
'{%s}category' % ITUNES_NS)
|
||||||
category.attrib['text'] = c.get('cat')
|
category.attrib['text'] = c.get('cat')
|
||||||
|
|
||||||
if c.get('sub'):
|
if c.get('sub'):
|
||||||
subcategory = etree.SubElement(category, '{%s}category' % ITUNES_NS)
|
subcategory = etree.SubElement(category,
|
||||||
|
'{%s}category' % ITUNES_NS)
|
||||||
subcategory.attrib['text'] = c.get('sub')
|
subcategory.attrib['text'] = c.get('sub')
|
||||||
|
|
||||||
if self.__itunes_image:
|
if self.__itunes_image:
|
||||||
|
@ -81,7 +81,8 @@ class PodcastExtension(BaseExtension):
|
||||||
complete.text = self.__itunes_complete
|
complete.text = self.__itunes_complete
|
||||||
|
|
||||||
if self.__itunes_new_feed_url:
|
if self.__itunes_new_feed_url:
|
||||||
new_feed_url = etree.SubElement(channel, '{%s}new-feed-url' % ITUNES_NS)
|
new_feed_url = etree.SubElement(channel,
|
||||||
|
'{%s}new-feed-url' % ITUNES_NS)
|
||||||
new_feed_url.text = self.__itunes_new_feed_url
|
new_feed_url.text = self.__itunes_new_feed_url
|
||||||
|
|
||||||
if self.__itunes_owner:
|
if self.__itunes_owner:
|
||||||
|
@ -101,29 +102,27 @@ class PodcastExtension(BaseExtension):
|
||||||
|
|
||||||
return rss_feed
|
return rss_feed
|
||||||
|
|
||||||
|
|
||||||
def itunes_author(self, itunes_author=None):
|
def itunes_author(self, itunes_author=None):
|
||||||
'''Get or set the itunes:author. The content of this tag is shown in the
|
'''Get or set the itunes:author. The content of this tag is shown in
|
||||||
Artist column in iTunes. If the tag is not present, iTunes uses the
|
the Artist column in iTunes. If the tag is not present, iTunes uses the
|
||||||
contents of the <author> tag. If <itunes:author> is not present at the
|
contents of the <author> tag. If <itunes:author> is not present at the
|
||||||
feed level, iTunes will use the contents of <managingEditor>.
|
feed level, iTunes will use the contents of <managingEditor>.
|
||||||
|
|
||||||
:param itunes_author: The author of the podcast.
|
:param itunes_author: The author of the podcast.
|
||||||
:returns: The author of the podcast.
|
:returns: The author of the podcast.
|
||||||
'''
|
'''
|
||||||
if not itunes_author is None:
|
if itunes_author is not None:
|
||||||
self.__itunes_author = itunes_author
|
self.__itunes_author = itunes_author
|
||||||
return self.__itunes_author
|
return self.__itunes_author
|
||||||
|
|
||||||
|
|
||||||
def itunes_block(self, itunes_block=None):
|
def itunes_block(self, itunes_block=None):
|
||||||
'''Get or set the ITunes block attribute. Use this to prevent the entire
|
'''Get or set the ITunes block attribute. Use this to prevent the
|
||||||
podcast from appearing in the iTunes podcast directory.
|
entire podcast from appearing in the iTunes podcast directory.
|
||||||
|
|
||||||
:param itunes_block: Block the podcast.
|
:param itunes_block: Block the podcast.
|
||||||
:returns: If the podcast is blocked.
|
:returns: If the podcast is blocked.
|
||||||
'''
|
'''
|
||||||
if not itunes_block is None:
|
if itunes_block is not None:
|
||||||
self.__itunes_block = itunes_block
|
self.__itunes_block = itunes_block
|
||||||
return self.__itunes_block
|
return self.__itunes_block
|
||||||
|
|
||||||
|
@ -167,36 +166,36 @@ class PodcastExtension(BaseExtension):
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**Important note about deprecated parameter syntax:** Old version of the
|
**Important note about deprecated parameter syntax:** Old version of
|
||||||
feedgen did only support one category plus one subcategory which would be
|
the feedgen did only support one category plus one subcategory which
|
||||||
passed to this ducntion as first two parameters. For compatibility
|
would be passed to this ducntion as first two parameters. For
|
||||||
reasons, this still works but should not be used any may be removed at
|
compatibility reasons, this still works but should not be used any may
|
||||||
any time.
|
be removed at any time.
|
||||||
'''
|
'''
|
||||||
# Ensure old API still works for now. Note that the API is deprecated and
|
# Ensure old API still works for now. Note that the API is deprecated
|
||||||
# this fallback may be removed at any time.
|
# and this fallback may be removed at any time.
|
||||||
if isinstance(itunes_category, string_types):
|
if isinstance(itunes_category, string_types):
|
||||||
itunes_category = {'cat':itunes_category}
|
itunes_category = {'cat': itunes_category}
|
||||||
if replace:
|
if replace:
|
||||||
itunes_category['sub'] = replace
|
itunes_category['sub'] = replace
|
||||||
replace=True
|
replace = True
|
||||||
if itunes_category is None and kwargs:
|
if itunes_category is None and kwargs:
|
||||||
itunes_category = kwargs
|
itunes_category = kwargs
|
||||||
if not itunes_category is None:
|
if itunes_category is not None:
|
||||||
if replace or self.__itunes_category is None:
|
if replace or self.__itunes_category is None:
|
||||||
self.__itunes_category = []
|
self.__itunes_category = []
|
||||||
self.__itunes_category += ensure_format( itunes_category,
|
self.__itunes_category += ensure_format(itunes_category,
|
||||||
set(['cat', 'sub']), set(['cat']))
|
set(['cat', 'sub']),
|
||||||
|
set(['cat']))
|
||||||
return self.__itunes_category
|
return self.__itunes_category
|
||||||
|
|
||||||
|
|
||||||
def itunes_image(self, itunes_image=None):
|
def itunes_image(self, itunes_image=None):
|
||||||
'''Get or set the image for the podcast. This tag specifies the artwork
|
'''Get or set the image for the podcast. This tag specifies the artwork
|
||||||
for your podcast. Put the URL to the image in the href attribute. iTunes
|
for your podcast. Put the URL to the image in the href attribute.
|
||||||
prefers square .jpg images that are at least 1400x1400 pixels, which is
|
iTunes prefers square .jpg images that are at least 1400x1400 pixels,
|
||||||
different from what is specified for the standard RSS image tag. In order
|
which is different from what is specified for the standard RSS image
|
||||||
for a podcast to be eligible for an iTunes Store feature, the
|
tag. In order for a podcast to be eligible for an iTunes Store feature,
|
||||||
accompanying image must be at least 1400x1400 pixels.
|
the accompanying image must be at least 1400x1400 pixels.
|
||||||
|
|
||||||
iTunes supports images in JPEG and PNG formats with an RGB color space
|
iTunes supports images in JPEG and PNG formats with an RGB color space
|
||||||
(CMYK is not supported). The URL must end in ".jpg" or ".png". If the
|
(CMYK is not supported). The URL must end in ".jpg" or ".png". If the
|
||||||
|
@ -204,43 +203,43 @@ class PodcastExtension(BaseExtension):
|
||||||
RSS image tag.
|
RSS image tag.
|
||||||
|
|
||||||
If you change your podcast’s image, also change the file’s name. iTunes
|
If you change your podcast’s image, also change the file’s name. iTunes
|
||||||
may not change the image if it checks your feed and the image URL is the
|
may not change the image if it checks your feed and the image URL is
|
||||||
same. The server hosting your cover art image must allow HTTP head
|
the same. The server hosting your cover art image must allow HTTP head
|
||||||
requests for iTS to be able to automatically update your cover art.
|
requests for iTS to be able to automatically update your cover art.
|
||||||
|
|
||||||
:param itunes_image: Image of the podcast.
|
:param itunes_image: Image of the podcast.
|
||||||
:returns: Image of the podcast.
|
:returns: Image of the podcast.
|
||||||
'''
|
'''
|
||||||
if not itunes_image is None:
|
if itunes_image is not None:
|
||||||
if not ( itunes_image.endswith('.jpg') or itunes_image.endswith('.png') ):
|
if itunes_image.endswith('.jpg') or itunes_image.endswith('.png'):
|
||||||
ValueError('Image file must be png or jpg')
|
|
||||||
self.__itunes_image = itunes_image
|
self.__itunes_image = itunes_image
|
||||||
|
else:
|
||||||
|
ValueError('Image file must be png or jpg')
|
||||||
return self.__itunes_image
|
return self.__itunes_image
|
||||||
|
|
||||||
|
|
||||||
def itunes_explicit(self, itunes_explicit=None):
|
def itunes_explicit(self, itunes_explicit=None):
|
||||||
'''Get or the the itunes:explicit value of the podcast. This tag should
|
'''Get or the the itunes:explicit value of the podcast. This tag should
|
||||||
be used to indicate whether your podcast contains explicit material. The
|
be used to indicate whether your podcast contains explicit material.
|
||||||
three values for this tag are "yes", "no", and "clean".
|
The three values for this tag are "yes", "no", and "clean".
|
||||||
|
|
||||||
If you populate this tag with "yes", an "explicit" parental advisory
|
If you populate this tag with "yes", an "explicit" parental advisory
|
||||||
graphic will appear next to your podcast artwork on the iTunes Store and
|
graphic will appear next to your podcast artwork on the iTunes Store
|
||||||
in the Name column in iTunes. If the value is "clean", the parental
|
and in the Name column in iTunes. If the value is "clean", the parental
|
||||||
advisory type is considered Clean, meaning that no explicit language or
|
advisory type is considered Clean, meaning that no explicit language or
|
||||||
adult content is included anywhere in the episodes, and a "clean" graphic
|
adult content is included anywhere in the episodes, and a "clean"
|
||||||
will appear. If the explicit tag is present and has any other value
|
graphic will appear. If the explicit tag is present and has any other
|
||||||
(e.g., "no"), you see no indicator — blank is the default advisory type.
|
value (e.g., "no"), you see no indicator — blank is the default
|
||||||
|
advisory type.
|
||||||
|
|
||||||
:param itunes_explicit: If the podcast contains explicit material.
|
:param itunes_explicit: If the podcast contains explicit material.
|
||||||
:returns: If the podcast contains explicit material.
|
:returns: If the podcast contains explicit material.
|
||||||
'''
|
'''
|
||||||
if not itunes_explicit is None:
|
if itunes_explicit is not None:
|
||||||
if not itunes_explicit in ('', 'yes', 'no', 'clean'):
|
if itunes_explicit not in ('', 'yes', 'no', 'clean'):
|
||||||
raise ValueError('Invalid value for explicit tag')
|
raise ValueError('Invalid value for explicit tag')
|
||||||
self.__itunes_explicit = itunes_explicit
|
self.__itunes_explicit = itunes_explicit
|
||||||
return self.__itunes_explicit
|
return self.__itunes_explicit
|
||||||
|
|
||||||
|
|
||||||
def itunes_complete(self, itunes_complete=None):
|
def itunes_complete(self, itunes_complete=None):
|
||||||
'''Get or set the itunes:complete value of the podcast. This tag can be
|
'''Get or set the itunes:complete value of the podcast. This tag can be
|
||||||
used to indicate the completion of a podcast.
|
used to indicate the completion of a podcast.
|
||||||
|
@ -253,33 +252,31 @@ class PodcastExtension(BaseExtension):
|
||||||
:param itunes_complete: If the podcast is complete.
|
:param itunes_complete: If the podcast is complete.
|
||||||
:returns: If the podcast is complete.
|
:returns: If the podcast is complete.
|
||||||
'''
|
'''
|
||||||
if not itunes_complete is None:
|
if itunes_complete is not None:
|
||||||
if not itunes_complete in ('yes', 'no', '', True, False):
|
if itunes_complete not in ('yes', 'no', '', True, False):
|
||||||
raise ValueError('Invalid value for complete tag')
|
raise ValueError('Invalid value for complete tag')
|
||||||
if itunes_complete == True:
|
if itunes_complete is True:
|
||||||
itunes_complete = 'yes'
|
itunes_complete = 'yes'
|
||||||
if itunes_complete == False:
|
if itunes_complete is False:
|
||||||
itunes_complete = 'no'
|
itunes_complete = 'no'
|
||||||
self.__itunes_complete = itunes_complete
|
self.__itunes_complete = itunes_complete
|
||||||
return self.__itunes_complete
|
return self.__itunes_complete
|
||||||
|
|
||||||
|
|
||||||
def itunes_new_feed_url(self, itunes_new_feed_url=None):
|
def itunes_new_feed_url(self, itunes_new_feed_url=None):
|
||||||
'''Get or set the new-feed-url property of the podcast. This tag allows
|
'''Get or set the new-feed-url property of the podcast. This tag allows
|
||||||
you to change the URL where the podcast feed is located
|
you to change the URL where the podcast feed is located
|
||||||
|
|
||||||
After adding the tag to your old feed, you should maintain the old feed
|
After adding the tag to your old feed, you should maintain the old feed
|
||||||
for 48 hours before retiring it. At that point, iTunes will have updated
|
for 48 hours before retiring it. At that point, iTunes will have
|
||||||
the directory with the new feed URL.
|
updated the directory with the new feed URL.
|
||||||
|
|
||||||
:param itunes_new_feed_url: New feed URL.
|
:param itunes_new_feed_url: New feed URL.
|
||||||
:returns: New feed URL.
|
:returns: New feed URL.
|
||||||
'''
|
'''
|
||||||
if not itunes_new_feed_url is None:
|
if itunes_new_feed_url is not None:
|
||||||
self.__itunes_new_feed_url = itunes_new_feed_url
|
self.__itunes_new_feed_url = itunes_new_feed_url
|
||||||
return self.__itunes_new_feed_url
|
return self.__itunes_new_feed_url
|
||||||
|
|
||||||
|
|
||||||
def itunes_owner(self, name=None, email=None):
|
def itunes_owner(self, name=None, email=None):
|
||||||
'''Get or set the itunes:owner of the podcast. This tag contains
|
'''Get or set the itunes:owner of the podcast. This tag contains
|
||||||
information that will be used to contact the owner of the podcast for
|
information that will be used to contact the owner of the podcast for
|
||||||
|
@ -289,16 +286,15 @@ class PodcastExtension(BaseExtension):
|
||||||
:param itunes_owner: The owner of the feed.
|
:param itunes_owner: The owner of the feed.
|
||||||
:returns: Data of the owner of the feed.
|
:returns: Data of the owner of the feed.
|
||||||
'''
|
'''
|
||||||
if not name is None:
|
if name is not None:
|
||||||
if name and email:
|
if name and email:
|
||||||
self.__itunes_owner = {'name':name, 'email':email}
|
self.__itunes_owner = {'name': name, 'email': email}
|
||||||
elif not name and not email:
|
elif not name and not email:
|
||||||
self.__itunes_owner = None
|
self.__itunes_owner = None
|
||||||
else:
|
else:
|
||||||
raise ValueError('Both name and email have to be set.')
|
raise ValueError('Both name and email have to be set.')
|
||||||
return self.__itunes_owner
|
return self.__itunes_owner
|
||||||
|
|
||||||
|
|
||||||
def itunes_subtitle(self, itunes_subtitle=None):
|
def itunes_subtitle(self, itunes_subtitle=None):
|
||||||
'''Get or set the itunes:subtitle value for the podcast. The contents of
|
'''Get or set the itunes:subtitle value for the podcast. The contents of
|
||||||
this tag are shown in the Description column in iTunes. The subtitle
|
this tag are shown in the Description column in iTunes. The subtitle
|
||||||
|
@ -307,53 +303,57 @@ class PodcastExtension(BaseExtension):
|
||||||
:param itunes_subtitle: Subtitle of the podcast.
|
:param itunes_subtitle: Subtitle of the podcast.
|
||||||
:returns: Subtitle of the podcast.
|
:returns: Subtitle of the podcast.
|
||||||
'''
|
'''
|
||||||
if not itunes_subtitle is None:
|
if itunes_subtitle is not None:
|
||||||
self.__itunes_subtitle = itunes_subtitle
|
self.__itunes_subtitle = itunes_subtitle
|
||||||
return self.__itunes_subtitle
|
return self.__itunes_subtitle
|
||||||
|
|
||||||
|
|
||||||
def itunes_summary(self, itunes_summary=None):
|
def itunes_summary(self, itunes_summary=None):
|
||||||
'''Get or set the itunes:summary value for the podcast. The contents of
|
'''Get or set the itunes:summary value for the podcast. The contents of
|
||||||
this tag are shown in a separate window that appears when the "circled i"
|
this tag are shown in a separate window that appears when the "circled
|
||||||
in the Description column is clicked. It also appears on the iTunes page
|
i" in the Description column is clicked. It also appears on the iTunes
|
||||||
for your podcast. This field can be up to 4000 characters. If
|
page for your podcast. This field can be up to 4000 characters. If
|
||||||
<itunes:summary> is not included, the contents of the <description> tag
|
`<itunes:summary>` is not included, the contents of the <description>
|
||||||
are used.
|
tag are used.
|
||||||
|
|
||||||
:param itunes_summary: Summary of the podcast.
|
:param itunes_summary: Summary of the podcast.
|
||||||
:returns: Summary of the podcast.
|
:returns: Summary of the podcast.
|
||||||
'''
|
'''
|
||||||
if not itunes_summary is None:
|
if itunes_summary is not None:
|
||||||
self.__itunes_summary = itunes_summary
|
self.__itunes_summary = itunes_summary
|
||||||
return self.__itunes_summary
|
return self.__itunes_summary
|
||||||
|
|
||||||
|
|
||||||
_itunes_categories = {
|
_itunes_categories = {
|
||||||
'Arts': [ 'Design', 'Fashion & Beauty', 'Food', 'Literature',
|
'Arts': [
|
||||||
'Performing Arts', 'Visual Arts' ],
|
'Design', 'Fashion & Beauty', 'Food', 'Literature',
|
||||||
'Business' : [ 'Business News', 'Careers', 'Investing',
|
'Performing Arts', 'Visual Arts'],
|
||||||
'Management & Marketing', 'Shopping' ],
|
'Business': [
|
||||||
'Comedy' : [],
|
'Business News', 'Careers', 'Investing',
|
||||||
'Education' : [ 'Education', 'Education Technology',
|
'Management & Marketing', 'Shopping'],
|
||||||
'Higher Education', 'K-12', 'Language Courses', 'Training' ],
|
'Comedy': [],
|
||||||
'Games & Hobbies' : [ 'Automotive', 'Aviation', 'Hobbies',
|
'Education': [
|
||||||
'Other Games', 'Video Games' ],
|
'Education', 'Education Technology', 'Higher Education',
|
||||||
'Government & Organizations' : [ 'Local', 'National', 'Non-Profit',
|
'K-12', 'Language Courses', 'Training'],
|
||||||
'Regional' ],
|
'Games & Hobbies': [
|
||||||
'Health' : [ 'Alternative Health', 'Fitness & Nutrition', 'Self-Help',
|
'Automotive', 'Aviation', 'Hobbies', 'Other Games',
|
||||||
'Sexuality' ],
|
'Video Games'],
|
||||||
'Kids & Family' : [],
|
'Government & Organizations': [
|
||||||
'Music' : [],
|
'Local', 'National', 'Non-Profit', 'Regional'],
|
||||||
'News & Politics' : [],
|
'Health': [
|
||||||
'Religion & Spirituality' : [ 'Buddhism', 'Christianity', 'Hinduism',
|
'Alternative Health', 'Fitness & Nutrition', 'Self-Help',
|
||||||
'Islam', 'Judaism', 'Other', 'Spirituality' ],
|
'Sexuality'],
|
||||||
'Science & Medicine' : [ 'Medicine', 'Natural Sciences',
|
'Kids & Family': [],
|
||||||
'Social Sciences' ],
|
'Music': [],
|
||||||
'Society & Culture' : [ 'History', 'Personal Journals', 'Philosophy',
|
'News & Politics': [],
|
||||||
'Places & Travel' ],
|
'Religion & Spirituality': [
|
||||||
'Sports & Recreation' : [ 'Amateur', 'College & High School',
|
'Buddhism', 'Christianity', 'Hinduism', 'Islam', 'Judaism',
|
||||||
'Outdoor', 'Professional' ],
|
'Other', 'Spirituality'],
|
||||||
'Technology' : [ 'Gadgets', 'Tech News', 'Podcasting',
|
'Science & Medicine': [
|
||||||
'Software How-To' ],
|
'Medicine', 'Natural Sciences', 'Social Sciences'],
|
||||||
'TV & Film' : []
|
'Society & Culture': [
|
||||||
}
|
'History', 'Personal Journals', 'Philosophy',
|
||||||
|
'Places & Travel'],
|
||||||
|
'Sports & Recreation': [
|
||||||
|
'Amateur', 'College & High School', 'Outdoor', 'Professional'],
|
||||||
|
'Technology': [
|
||||||
|
'Gadgets', 'Tech News', 'Podcasting', 'Software How-To'],
|
||||||
|
'TV & Film': []}
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
Extends the feedgen to produce podcasts.
|
Extends the feedgen to produce podcasts.
|
||||||
|
|
||||||
:copyright: 2013, Lars Kiesow <lkiesow@uos.de>
|
:copyright: 2013-2016, Lars Kiesow <lkiesow@uos.de>
|
||||||
|
|
||||||
:license: FreeBSD and LGPL, see license.* for more details.
|
:license: FreeBSD and LGPL, see license.* for more details.
|
||||||
'''
|
'''
|
||||||
|
@ -18,9 +18,8 @@ class PodcastEntryExtension(BaseEntryExtension):
|
||||||
'''FeedEntry extension for podcasts.
|
'''FeedEntry extension for podcasts.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
## ITunes tags
|
# ITunes tags
|
||||||
# http://www.apple.com/itunes/podcasts/specs.html#rss
|
# http://www.apple.com/itunes/podcasts/specs.html#rss
|
||||||
self.__itunes_author = None
|
self.__itunes_author = None
|
||||||
self.__itunes_block = None
|
self.__itunes_block = None
|
||||||
|
@ -32,7 +31,6 @@ class PodcastEntryExtension(BaseEntryExtension):
|
||||||
self.__itunes_subtitle = None
|
self.__itunes_subtitle = None
|
||||||
self.__itunes_summary = None
|
self.__itunes_summary = None
|
||||||
|
|
||||||
|
|
||||||
def extend_rss(self, entry):
|
def extend_rss(self, entry):
|
||||||
'''Add additional fields to an RSS item.
|
'''Add additional fields to an RSS item.
|
||||||
|
|
||||||
|
@ -44,7 +42,7 @@ class PodcastEntryExtension(BaseEntryExtension):
|
||||||
author = etree.SubElement(entry, '{%s}author' % ITUNES_NS)
|
author = etree.SubElement(entry, '{%s}author' % ITUNES_NS)
|
||||||
author.text = self.__itunes_author
|
author.text = self.__itunes_author
|
||||||
|
|
||||||
if not self.__itunes_block is None:
|
if self.__itunes_block is not None:
|
||||||
block = etree.SubElement(entry, '{%s}block' % ITUNES_NS)
|
block = etree.SubElement(entry, '{%s}block' % ITUNES_NS)
|
||||||
block.text = 'yes' if self.__itunes_block else 'no'
|
block.text = 'yes' if self.__itunes_block else 'no'
|
||||||
|
|
||||||
|
@ -60,11 +58,15 @@ class PodcastEntryExtension(BaseEntryExtension):
|
||||||
explicit = etree.SubElement(entry, '{%s}explicit' % ITUNES_NS)
|
explicit = etree.SubElement(entry, '{%s}explicit' % ITUNES_NS)
|
||||||
explicit.text = self.__itunes_explicit
|
explicit.text = self.__itunes_explicit
|
||||||
|
|
||||||
if not self.__itunes_is_closed_captioned is None:
|
if self.__itunes_is_closed_captioned is not None:
|
||||||
is_closed_captioned = etree.SubElement(entry, '{%s}isClosedCaptioned' % ITUNES_NS)
|
is_closed_captioned = etree.SubElement(
|
||||||
is_closed_captioned.text = 'yes' if self.__itunes_is_closed_captioned else 'no'
|
entry, '{%s}isClosedCaptioned' % ITUNES_NS)
|
||||||
|
if self.__itunes_is_closed_captioned:
|
||||||
|
is_closed_captioned.text = 'yes'
|
||||||
|
else:
|
||||||
|
is_closed_captioned.text = 'no'
|
||||||
|
|
||||||
if not self.__itunes_order is None and self.__itunes_order >= 0:
|
if self.__itunes_order is not None and self.__itunes_order >= 0:
|
||||||
order = etree.SubElement(entry, '{%s}order' % ITUNES_NS)
|
order = etree.SubElement(entry, '{%s}order' % ITUNES_NS)
|
||||||
order.text = str(self.__itunes_order)
|
order.text = str(self.__itunes_order)
|
||||||
|
|
||||||
|
@ -77,22 +79,20 @@ class PodcastEntryExtension(BaseEntryExtension):
|
||||||
summary.text = self.__itunes_summary
|
summary.text = self.__itunes_summary
|
||||||
return entry
|
return entry
|
||||||
|
|
||||||
|
|
||||||
def itunes_author(self, itunes_author=None):
|
def itunes_author(self, itunes_author=None):
|
||||||
'''Get or set the itunes:author of the podcast episode. The content of
|
'''Get or set the itunes:author of the podcast episode. The content of
|
||||||
this tag is shown in the Artist column in iTunes. If the tag is not
|
this tag is shown in the Artist column in iTunes. If the tag is not
|
||||||
present, iTunes uses the contents of the <author> tag. If <itunes:author>
|
present, iTunes uses the contents of the <author> tag. If
|
||||||
is not present at the feed level, iTunes will use the contents of
|
<itunes:author> is not present at the feed level, iTunes will use the
|
||||||
<managingEditor>.
|
contents of <managingEditor>.
|
||||||
|
|
||||||
:param itunes_author: The author of the podcast.
|
:param itunes_author: The author of the podcast.
|
||||||
:returns: The author of the podcast.
|
:returns: The author of the podcast.
|
||||||
'''
|
'''
|
||||||
if not itunes_author is None:
|
if itunes_author is not None:
|
||||||
self.__itunes_author = itunes_author
|
self.__itunes_author = itunes_author
|
||||||
return self.__itunes_author
|
return self.__itunes_author
|
||||||
|
|
||||||
|
|
||||||
def itunes_block(self, itunes_block=None):
|
def itunes_block(self, itunes_block=None):
|
||||||
'''Get or set the ITunes block attribute. Use this to prevent episodes
|
'''Get or set the ITunes block attribute. Use this to prevent episodes
|
||||||
from appearing in the iTunes podcast directory.
|
from appearing in the iTunes podcast directory.
|
||||||
|
@ -100,18 +100,18 @@ class PodcastEntryExtension(BaseEntryExtension):
|
||||||
:param itunes_block: Block podcast episodes.
|
:param itunes_block: Block podcast episodes.
|
||||||
:returns: If the podcast episode is blocked.
|
:returns: If the podcast episode is blocked.
|
||||||
'''
|
'''
|
||||||
if not itunes_block is None:
|
if itunes_block is not None:
|
||||||
self.__itunes_block = itunes_block
|
self.__itunes_block = itunes_block
|
||||||
return self.__itunes_block
|
return self.__itunes_block
|
||||||
|
|
||||||
|
|
||||||
def itunes_image(self, itunes_image=None):
|
def itunes_image(self, itunes_image=None):
|
||||||
'''Get or set the image for the podcast episode. This tag specifies the
|
'''Get or set the image for the podcast episode. This tag specifies the
|
||||||
artwork for your podcast. Put the URL to the image in the href attribute.
|
artwork for your podcast. Put the URL to the image in the href
|
||||||
iTunes prefers square .jpg images that are at least 1400x1400 pixels,
|
attribute. iTunes prefers square .jpg images that are at least
|
||||||
which is different from what is specified for the standard RSS image tag.
|
1400x1400 pixels, which is different from what is specified for the
|
||||||
In order for a podcast to be eligible for an iTunes Store feature, the
|
standard RSS image tag. In order for a podcast to be eligible for an
|
||||||
accompanying image must be at least 1400x1400 pixels.
|
iTunes Store feature, the accompanying image must be at least 1400x1400
|
||||||
|
pixels.
|
||||||
|
|
||||||
iTunes supports images in JPEG and PNG formats with an RGB color space
|
iTunes supports images in JPEG and PNG formats with an RGB color space
|
||||||
(CMYK is not supported). The URL must end in ".jpg" or ".png". If the
|
(CMYK is not supported). The URL must end in ".jpg" or ".png". If the
|
||||||
|
@ -119,35 +119,35 @@ class PodcastEntryExtension(BaseEntryExtension):
|
||||||
RSS image tag.
|
RSS image tag.
|
||||||
|
|
||||||
If you change your podcast’s image, also change the file’s name. iTunes
|
If you change your podcast’s image, also change the file’s name. iTunes
|
||||||
may not change the image if it checks your feed and the image URL is the
|
may not change the image if it checks your feed and the image URL is
|
||||||
same. The server hosting your cover art image must allow HTTP head
|
the same. The server hosting your cover art image must allow HTTP head
|
||||||
requests for iTS to be able to automatically update your cover art.
|
requests for iTS to be able to automatically update your cover art.
|
||||||
|
|
||||||
:param itunes_image: Image of the podcast.
|
:param itunes_image: Image of the podcast.
|
||||||
:returns: Image of the podcast.
|
:returns: Image of the podcast.
|
||||||
'''
|
'''
|
||||||
if not itunes_image is None:
|
if itunes_image is not None:
|
||||||
if not ( itunes_image.endswith('.jpg') or itunes_image.endswith('.png') ):
|
if itunes_image.endswith('.jpg') or itunes_image.endswith('.png'):
|
||||||
ValueError('Image file must be png or jpg')
|
|
||||||
self.__itunes_image = itunes_image
|
self.__itunes_image = itunes_image
|
||||||
|
else:
|
||||||
|
ValueError('Image file must be png or jpg')
|
||||||
return self.__itunes_image
|
return self.__itunes_image
|
||||||
|
|
||||||
|
|
||||||
def itunes_duration(self, itunes_duration=None):
|
def itunes_duration(self, itunes_duration=None):
|
||||||
'''Get or set the duration of the podcast episode. The content of this
|
'''Get or set the duration of the podcast episode. The content of this
|
||||||
tag is shown in the Time column in iTunes.
|
tag is shown in the Time column in iTunes.
|
||||||
|
|
||||||
The tag can be formatted HH:MM:SS, H:MM:SS, MM:SS, or M:SS (H = hours,
|
The tag can be formatted HH:MM:SS, H:MM:SS, MM:SS, or M:SS (H = hours,
|
||||||
M = minutes, S = seconds). If an integer is provided (no colon present),
|
M = minutes, S = seconds). If an integer is provided (no colon
|
||||||
the value is assumed to be in seconds. If one colon is present, the
|
present), the value is assumed to be in seconds. If one colon is
|
||||||
number to the left is assumed to be minutes, and the number to the right
|
present, the number to the left is assumed to be minutes, and the
|
||||||
is assumed to be seconds. If more than two colons are present, the
|
number to the right is assumed to be seconds. If more than two colons
|
||||||
numbers farthest to the right are ignored.
|
are present, the numbers farthest to the right are ignored.
|
||||||
|
|
||||||
:param itunes_duration: Duration of the podcast episode.
|
:param itunes_duration: Duration of the podcast episode.
|
||||||
:returns: Duration of the podcast episode.
|
:returns: Duration of the podcast episode.
|
||||||
'''
|
'''
|
||||||
if not itunes_duration is None:
|
if itunes_duration is not None:
|
||||||
itunes_duration = str(itunes_duration)
|
itunes_duration = str(itunes_duration)
|
||||||
if len(itunes_duration.split(':')) > 3 or \
|
if len(itunes_duration.split(':')) > 3 or \
|
||||||
itunes_duration.lstrip('0123456789:') != '':
|
itunes_duration.lstrip('0123456789:') != '':
|
||||||
|
@ -155,65 +155,67 @@ class PodcastEntryExtension(BaseEntryExtension):
|
||||||
self.__itunes_duration = itunes_duration
|
self.__itunes_duration = itunes_duration
|
||||||
return self.itunes_duration
|
return self.itunes_duration
|
||||||
|
|
||||||
|
|
||||||
def itunes_explicit(self, itunes_explicit=None):
|
def itunes_explicit(self, itunes_explicit=None):
|
||||||
'''Get or the the itunes:explicit value of the podcast episode. This tag
|
'''Get or the the itunes:explicit value of the podcast episode. This
|
||||||
should be used to indicate whether your podcast episode contains explicit
|
tag should be used to indicate whether your podcast episode contains
|
||||||
material. The three values for this tag are "yes", "no", and "clean".
|
explicit material. The three values for this tag are "yes", "no", and
|
||||||
|
"clean".
|
||||||
|
|
||||||
If you populate this tag with "yes", an "explicit" parental advisory
|
If you populate this tag with "yes", an "explicit" parental advisory
|
||||||
graphic will appear next to your podcast artwork on the iTunes Store and
|
graphic will appear next to your podcast artwork on the iTunes Store
|
||||||
in the Name column in iTunes. If the value is "clean", the parental
|
and in the Name column in iTunes. If the value is "clean", the parental
|
||||||
advisory type is considered Clean, meaning that no explicit language or
|
advisory type is considered Clean, meaning that no explicit language or
|
||||||
adult content is included anywhere in the episodes, and a "clean" graphic
|
adult content is included anywhere in the episodes, and a "clean"
|
||||||
will appear. If the explicit tag is present and has any other value
|
graphic will appear. If the explicit tag is present and has any other
|
||||||
(e.g., "no"), you see no indicator — blank is the default advisory type.
|
value (e.g., "no"), you see no indicator — blank is the default
|
||||||
|
advisory type.
|
||||||
|
|
||||||
:param itunes_explicit: If the podcast episode contains explicit material.
|
:param itunes_explicit: If the podcast episode contains explicit
|
||||||
|
material.
|
||||||
:returns: If the podcast episode contains explicit material.
|
:returns: If the podcast episode contains explicit material.
|
||||||
'''
|
'''
|
||||||
if not itunes_explicit is None:
|
if itunes_explicit is not None:
|
||||||
if not itunes_explicit in ('', 'yes', 'no', 'clean'):
|
if itunes_explicit not in ('', 'yes', 'no', 'clean'):
|
||||||
raise ValueError('Invalid value for explicit tag')
|
raise ValueError('Invalid value for explicit tag')
|
||||||
self.__itunes_explicit = itunes_explicit
|
self.__itunes_explicit = itunes_explicit
|
||||||
return self.__itunes_explicit
|
return self.__itunes_explicit
|
||||||
|
|
||||||
|
|
||||||
def itunes_is_closed_captioned(self, itunes_is_closed_captioned=None):
|
def itunes_is_closed_captioned(self, itunes_is_closed_captioned=None):
|
||||||
'''Get or set the is_closed_captioned value of the podcast episode. This
|
'''Get or set the is_closed_captioned value of the podcast episode.
|
||||||
tag should be used if your podcast includes a video episode with embedded
|
This tag should be used if your podcast includes a video episode with
|
||||||
closed captioning support. The two values for this tag are "yes" and
|
embedded closed captioning support. The two values for this tag are
|
||||||
"no”.
|
"yes" and "no”.
|
||||||
|
|
||||||
:param is_closed_captioned: If the episode has closed captioning support.
|
:param is_closed_captioned: If the episode has closed captioning
|
||||||
|
support.
|
||||||
:returns: If the episode has closed captioning support.
|
:returns: If the episode has closed captioning support.
|
||||||
'''
|
'''
|
||||||
if not itunes_is_closed_captioned is None:
|
if itunes_is_closed_captioned is not None:
|
||||||
self.__itunes_is_closed_captioned = itunes_is_closed_captioned in ('yes', True)
|
self.__itunes_is_closed_captioned = \
|
||||||
|
itunes_is_closed_captioned in ('yes', True)
|
||||||
return self.__itunes_is_closed_captioned
|
return self.__itunes_is_closed_captioned
|
||||||
|
|
||||||
|
|
||||||
def itunes_order(self, itunes_order=None):
|
def itunes_order(self, itunes_order=None):
|
||||||
'''Get or set the itunes:order value of the podcast episode. This tag can
|
'''Get or set the itunes:order value of the podcast episode. This tag
|
||||||
be used to override the default ordering of episodes on the store.
|
can be used to override the default ordering of episodes on the store.
|
||||||
|
|
||||||
This tag is used at an <item> level by populating with the number value
|
This tag is used at an <item> level by populating with the number value
|
||||||
in which you would like the episode to appear on the store. For example,
|
in which you would like the episode to appear on the store. For
|
||||||
if you would like an <item> to appear as the first episode in the
|
example, if you would like an <item> to appear as the first episode in
|
||||||
podcast, you would populate the <itunes:order> tag with “1”. If
|
the podcast, you would populate the <itunes:order> tag with “1”. If
|
||||||
conflicting order values are present in multiple episodes, the store will
|
conflicting order values are present in multiple episodes, the store
|
||||||
use default ordering (pubDate).
|
will use default ordering (pubDate).
|
||||||
|
|
||||||
To remove the order from the episode set the order to a value below zero.
|
To remove the order from the episode set the order to a value below
|
||||||
|
zero.
|
||||||
|
|
||||||
:param itunes_order: The order of the episode.
|
:param itunes_order: The order of the episode.
|
||||||
:returns: The order of the episode.
|
:returns: The order of the episode.
|
||||||
'''
|
'''
|
||||||
if not itunes_order is None:
|
if itunes_order is not None:
|
||||||
self.__itunes_order = int(itunes_order)
|
self.__itunes_order = int(itunes_order)
|
||||||
return self.__itunes_order
|
return self.__itunes_order
|
||||||
|
|
||||||
|
|
||||||
def itunes_subtitle(self, itunes_subtitle=None):
|
def itunes_subtitle(self, itunes_subtitle=None):
|
||||||
'''Get or set the itunes:subtitle value for the podcast episode. The
|
'''Get or set the itunes:subtitle value for the podcast episode. The
|
||||||
contents of this tag are shown in the Description column in iTunes. The
|
contents of this tag are shown in the Description column in iTunes. The
|
||||||
|
@ -222,22 +224,21 @@ class PodcastEntryExtension(BaseEntryExtension):
|
||||||
:param itunes_subtitle: Subtitle of the podcast episode.
|
:param itunes_subtitle: Subtitle of the podcast episode.
|
||||||
:returns: Subtitle of the podcast episode.
|
:returns: Subtitle of the podcast episode.
|
||||||
'''
|
'''
|
||||||
if not itunes_subtitle is None:
|
if itunes_subtitle is not None:
|
||||||
self.__itunes_subtitle = itunes_subtitle
|
self.__itunes_subtitle = itunes_subtitle
|
||||||
return self.__itunes_subtitle
|
return self.__itunes_subtitle
|
||||||
|
|
||||||
|
|
||||||
def itunes_summary(self, itunes_summary=None):
|
def itunes_summary(self, itunes_summary=None):
|
||||||
'''Get or set the itunes:summary value for the podcast episode. The
|
'''Get or set the itunes:summary value for the podcast episode. The
|
||||||
contents of this tag are shown in a separate window that appears when the
|
contents of this tag are shown in a separate window that appears when
|
||||||
"circled i" in the Description column is clicked. It also appears on the
|
the "circled i" in the Description column is clicked. It also appears
|
||||||
iTunes page for your podcast. This field can be up to 4000 characters. If
|
on the iTunes page for your podcast. This field can be up to 4000
|
||||||
<itunes:summary> is not included, the contents of the <description> tag
|
characters. If <itunes:summary> is not included, the contents of the
|
||||||
are used.
|
<description> tag are used.
|
||||||
|
|
||||||
:param itunes_summary: Summary of the podcast episode.
|
:param itunes_summary: Summary of the podcast episode.
|
||||||
:returns: Summary of the podcast episode.
|
:returns: Summary of the podcast episode.
|
||||||
'''
|
'''
|
||||||
if not itunes_summary is None:
|
if itunes_summary is not None:
|
||||||
self.__itunes_summary = itunes_summary
|
self.__itunes_summary = itunes_summary
|
||||||
return self.__itunes_summary
|
return self.__itunes_summary
|
||||||
|
|
|
@ -11,15 +11,16 @@
|
||||||
'''
|
'''
|
||||||
|
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
from feedgen.ext.base import BaseExtension,BaseEntryExtension
|
from feedgen.ext.base import BaseExtension, BaseEntryExtension
|
||||||
|
|
||||||
TORRENT_NS = 'http://xmlns.ezrss.it/0.1/dtd/'
|
TORRENT_NS = 'http://xmlns.ezrss.it/0.1/dtd/'
|
||||||
|
|
||||||
|
|
||||||
class TorrentExtension(BaseExtension):
|
class TorrentExtension(BaseExtension):
|
||||||
'''FeedGenerator extension for torrent feeds.
|
'''FeedGenerator extension for torrent feeds.
|
||||||
'''
|
'''
|
||||||
def extend_ns(self):
|
def extend_ns(self):
|
||||||
return {'torrent' : TORRENT_NS}
|
return {'torrent': TORRENT_NS}
|
||||||
|
|
||||||
|
|
||||||
class TorrentEntryExtension(BaseEntryExtension):
|
class TorrentEntryExtension(BaseEntryExtension):
|
||||||
|
@ -31,8 +32,6 @@ class TorrentEntryExtension(BaseEntryExtension):
|
||||||
self.__torrent_contentlength = None
|
self.__torrent_contentlength = None
|
||||||
self.__torrent_seeds = None
|
self.__torrent_seeds = None
|
||||||
self.__torrent_peers = None
|
self.__torrent_peers = None
|
||||||
self.__torrent=verified = None
|
|
||||||
|
|
||||||
|
|
||||||
def extend_rss(self, entry):
|
def extend_rss(self, entry):
|
||||||
'''Add additional fields to an RSS item.
|
'''Add additional fields to an RSS item.
|
||||||
|
@ -44,7 +43,8 @@ class TorrentEntryExtension(BaseEntryExtension):
|
||||||
filename.text = self.__torrent_filename
|
filename.text = self.__torrent_filename
|
||||||
|
|
||||||
if self.__torrent_contentlength:
|
if self.__torrent_contentlength:
|
||||||
contentlength = etree.SubElement(entry, '{%s}contentlength' % TORRENT_NS)
|
contentlength = etree.SubElement(entry,
|
||||||
|
'{%s}contentlength' % TORRENT_NS)
|
||||||
contentlength.text = self.__torrent_contentlength
|
contentlength.text = self.__torrent_contentlength
|
||||||
|
|
||||||
if self.__torrent_infohash:
|
if self.__torrent_infohash:
|
||||||
|
@ -65,63 +65,62 @@ class TorrentEntryExtension(BaseEntryExtension):
|
||||||
verified = etree.SubElement(entry, '{%s}verified' % TORRENT_NS)
|
verified = etree.SubElement(entry, '{%s}verified' % TORRENT_NS)
|
||||||
verified.text = self.__torrent_verified
|
verified.text = self.__torrent_verified
|
||||||
|
|
||||||
|
|
||||||
def filename(self, torrent_filename=None):
|
def filename(self, torrent_filename=None):
|
||||||
'''Get or set the name of the torrent file.
|
'''Get or set the name of the torrent file.
|
||||||
|
|
||||||
:param torrent_filename: The name of the torrent file.
|
:param torrent_filename: The name of the torrent file.
|
||||||
:returns: The name of the torrent file.
|
:returns: The name of the torrent file.
|
||||||
'''
|
'''
|
||||||
if not torrent_filename is None:
|
if torrent_filename is not None:
|
||||||
self.__torrent_filename = torrent_filename
|
self.__torrent_filename = torrent_filename
|
||||||
return self.__torrent_filename
|
return self.__torrent_filename
|
||||||
|
|
||||||
def infohash (self, torrent_infohash=None):
|
def infohash(self, torrent_infohash=None):
|
||||||
'''Get or set the hash of the target file.
|
'''Get or set the hash of the target file.
|
||||||
|
|
||||||
:param torrent_infohash: The target file hash.
|
:param torrent_infohash: The target file hash.
|
||||||
:returns: The target hash file.
|
:returns: The target hash file.
|
||||||
'''
|
'''
|
||||||
if not torrent_infohash is None:
|
if torrent_infohash is not None:
|
||||||
self.__torrent_infohash = torrent_infohash
|
self.__torrent_infohash = torrent_infohash
|
||||||
return self.__torrent_infohash
|
return self.__torrent_infohash
|
||||||
|
|
||||||
def contentlength (self, torrent_contentlength=None):
|
def contentlength(self, torrent_contentlength=None):
|
||||||
'''Get or set the size of the target file.
|
'''Get or set the size of the target file.
|
||||||
|
|
||||||
:param torrent_contentlength: The target file size.
|
:param torrent_contentlength: The target file size.
|
||||||
:returns: The target file size.
|
:returns: The target file size.
|
||||||
'''
|
'''
|
||||||
if not torrent_contentlength is None:
|
if torrent_contentlength is not None:
|
||||||
self.__torrent_contentlength = torrent_contentlength
|
self.__torrent_contentlength = torrent_contentlength
|
||||||
return self.__torrent_contentlength
|
return self.__torrent_contentlength
|
||||||
|
|
||||||
def seeds (self, torrent_seeds=None):
|
def seeds(self, torrent_seeds=None):
|
||||||
'''Get or set the number of seeds.
|
'''Get or set the number of seeds.
|
||||||
|
|
||||||
:param torrent_seeds: The seeds number.
|
:param torrent_seeds: The seeds number.
|
||||||
:returns: The seeds number.
|
:returns: The seeds number.
|
||||||
'''
|
'''
|
||||||
if not torrent_seeds is None:
|
if torrent_seeds is not None:
|
||||||
self.__torrent_seeds = torrent_seeds
|
self.__torrent_seeds = torrent_seeds
|
||||||
return self.__torrent_seeds
|
return self.__torrent_seeds
|
||||||
|
|
||||||
def peers (self, torrent_peers=None):
|
def peers(self, torrent_peers=None):
|
||||||
'''Get or set the number od peers
|
'''Get or set the number od peers
|
||||||
|
|
||||||
:param torrent_infohash: The peers number.
|
:param torrent_infohash: The peers number.
|
||||||
:returns: The peers number.
|
:returns: The peers number.
|
||||||
'''
|
'''
|
||||||
if not torrent_peers is None:
|
if torrent_peers is not None:
|
||||||
self.__torrent_peers = torrent_peers
|
self.__torrent_peers = torrent_peers
|
||||||
return self.__torrent_peers
|
return self.__torrent_peers
|
||||||
|
|
||||||
def verified (self, torrent_verified=None):
|
def verified(self, torrent_verified=None):
|
||||||
'''Get or set the number of verified peers.
|
'''Get or set the number of verified peers.
|
||||||
|
|
||||||
:param torrent_infohash: The verified peers number.
|
:param torrent_infohash: The verified peers number.
|
||||||
:returns: The verified peers number.
|
:returns: The verified peers number.
|
||||||
'''
|
'''
|
||||||
if not torrent_verified is None:
|
if torrent_verified is not None:
|
||||||
self.__torrent_verified = torrent_verified
|
self.__torrent_verified = torrent_verified
|
||||||
return self.__torrent_verified
|
return self.__torrent_verified
|
||||||
|
|
400
feedgen/feed.py
400
feedgen/feed.py
|
@ -3,7 +3,7 @@
|
||||||
feedgen.feed
|
feedgen.feed
|
||||||
~~~~~~~~~~~~
|
~~~~~~~~~~~~
|
||||||
|
|
||||||
:copyright: 2013, Lars Kiesow <lkiesow@uos.de>
|
:copyright: 2013-2016, Lars Kiesow <lkiesow@uos.de>
|
||||||
|
|
||||||
:license: FreeBSD and LGPL, see license.* for more details.
|
:license: FreeBSD and LGPL, see license.* for more details.
|
||||||
|
|
||||||
|
@ -27,11 +27,10 @@ class FeedGenerator(object):
|
||||||
'''FeedGenerator for generating ATOM and RSS feeds.
|
'''FeedGenerator for generating ATOM and RSS feeds.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.__feed_entries = []
|
self.__feed_entries = []
|
||||||
|
|
||||||
## ATOM
|
# ATOM
|
||||||
# http://www.atomenabled.org/developers/syndication/
|
# http://www.atomenabled.org/developers/syndication/
|
||||||
# required
|
# required
|
||||||
self.__atom_id = None
|
self.__atom_id = None
|
||||||
|
@ -46,9 +45,9 @@ class FeedGenerator(object):
|
||||||
self.__atom_category = None # {term*, scheme, label}
|
self.__atom_category = None # {term*, scheme, label}
|
||||||
self.__atom_contributor = None
|
self.__atom_contributor = None
|
||||||
self.__atom_generator = {
|
self.__atom_generator = {
|
||||||
'value' :'python-feedgen',
|
'value': 'python-feedgen',
|
||||||
'uri' :'http://lkiesow.github.io/python-feedgen',
|
'uri': 'http://lkiesow.github.io/python-feedgen',
|
||||||
'version':feedgen.version.version_str } #{value*,uri,version}
|
'version': feedgen.version.version_str} # {value*,uri,version}
|
||||||
self.__atom_icon = None
|
self.__atom_icon = None
|
||||||
self.__atom_logo = None
|
self.__atom_logo = None
|
||||||
self.__atom_rights = None
|
self.__atom_rights = None
|
||||||
|
@ -57,7 +56,7 @@ class FeedGenerator(object):
|
||||||
# other
|
# other
|
||||||
self.__atom_feed_xml_lang = None
|
self.__atom_feed_xml_lang = None
|
||||||
|
|
||||||
## RSS
|
# RSS
|
||||||
# http://www.rssboard.org/rss-specification
|
# http://www.rssboard.org/rss-specification
|
||||||
self.__rss_title = None
|
self.__rss_title = None
|
||||||
self.__rss_link = None
|
self.__rss_link = None
|
||||||
|
@ -83,9 +82,9 @@ class FeedGenerator(object):
|
||||||
# Extension list:
|
# Extension list:
|
||||||
self.__extensions = {}
|
self.__extensions = {}
|
||||||
|
|
||||||
|
|
||||||
def _create_atom(self, extensions=True):
|
def _create_atom(self, extensions=True):
|
||||||
'''Create a ATOM feed xml structure containing all previously set fields.
|
'''Create a ATOM feed xml structure containing all previously set
|
||||||
|
fields.
|
||||||
|
|
||||||
:returns: Tuple containing the feed root element and the element tree.
|
:returns: Tuple containing the feed root element and the element tree.
|
||||||
'''
|
'''
|
||||||
|
@ -93,17 +92,20 @@ class FeedGenerator(object):
|
||||||
if extensions:
|
if extensions:
|
||||||
for ext in self.__extensions.values() or []:
|
for ext in self.__extensions.values() or []:
|
||||||
if ext.get('atom'):
|
if ext.get('atom'):
|
||||||
nsmap.update( ext['inst'].extend_ns() )
|
nsmap.update(ext['inst'].extend_ns())
|
||||||
|
|
||||||
feed = etree.Element('feed', xmlns='http://www.w3.org/2005/Atom', nsmap=nsmap)
|
feed = etree.Element('feed',
|
||||||
|
xmlns='http://www.w3.org/2005/Atom',
|
||||||
|
nsmap=nsmap)
|
||||||
if self.__atom_feed_xml_lang:
|
if self.__atom_feed_xml_lang:
|
||||||
feed.attrib['{http://www.w3.org/XML/1998/namespace}lang'] = \
|
feed.attrib['{http://www.w3.org/XML/1998/namespace}lang'] = \
|
||||||
self.__atom_feed_xml_lang
|
self.__atom_feed_xml_lang
|
||||||
|
|
||||||
if not ( self.__atom_id and self.__atom_title and self.__atom_updated ):
|
if not (self.__atom_id and self.__atom_title and self.__atom_updated):
|
||||||
missing = ', '.join(([] if self.__atom_title else ['title']) + \
|
missing = ([] if self.__atom_title else ['title']) + \
|
||||||
([] if self.__atom_id else ['id']) + \
|
([] if self.__atom_id else ['id']) + \
|
||||||
([] if self.__atom_updated else ['updated']))
|
([] if self.__atom_updated else ['updated'])
|
||||||
|
missing = ', '.join(missing)
|
||||||
raise ValueError('Required fields not set (%s)' % missing)
|
raise ValueError('Required fields not set (%s)' % missing)
|
||||||
id = etree.SubElement(feed, 'id')
|
id = etree.SubElement(feed, 'id')
|
||||||
id.text = self.__atom_id
|
id.text = self.__atom_id
|
||||||
|
@ -198,7 +200,6 @@ class FeedGenerator(object):
|
||||||
doc = etree.ElementTree(feed)
|
doc = etree.ElementTree(feed)
|
||||||
return feed, doc
|
return feed, doc
|
||||||
|
|
||||||
|
|
||||||
def atom_str(self, pretty=False, extensions=True, encoding='UTF-8',
|
def atom_str(self, pretty=False, extensions=True, encoding='UTF-8',
|
||||||
xml_declaration=True):
|
xml_declaration=True):
|
||||||
'''Generates an ATOM feed and returns the feed XML as string.
|
'''Generates an ATOM feed and returns the feed XML as string.
|
||||||
|
@ -213,20 +214,19 @@ class FeedGenerator(object):
|
||||||
:returns: String representation of the ATOM feed.
|
:returns: String representation of the ATOM feed.
|
||||||
|
|
||||||
**Return type:** The return type may vary between different Python
|
**Return type:** The return type may vary between different Python
|
||||||
versions and your encoding parameters passed to this method. For details
|
versions and your encoding parameters passed to this method. For
|
||||||
have a look at the `lxml documentation
|
details have a look at the `lxml documentation
|
||||||
<https://docs.python.org/3/library/xml.etree.elementtree.html#xml.etree.ElementTree.tostring>`_
|
<https://docs.python.org/3/library/xml.etree.elementtree.html#xml.etree.ElementTree.tostring>`_
|
||||||
'''
|
'''
|
||||||
feed, doc = self._create_atom(extensions=extensions)
|
feed, doc = self._create_atom(extensions=extensions)
|
||||||
return etree.tostring(feed, pretty_print=pretty, encoding=encoding,
|
return etree.tostring(feed, pretty_print=pretty, encoding=encoding,
|
||||||
xml_declaration=xml_declaration)
|
xml_declaration=xml_declaration)
|
||||||
|
|
||||||
|
|
||||||
def atom_file(self, filename, extensions=True, pretty=False,
|
def atom_file(self, filename, extensions=True, pretty=False,
|
||||||
encoding='UTF-8', xml_declaration=True):
|
encoding='UTF-8', xml_declaration=True):
|
||||||
'''Generates an ATOM feed and write the resulting XML to a file.
|
'''Generates an ATOM feed and write the resulting XML to a file.
|
||||||
|
|
||||||
:param filename: Name of file to write, or a file-like object, or a URL.
|
:param filename: Name of file to write or a file-like object or a URL.
|
||||||
:param extensions: Enable or disable the loaded extensions for the xml
|
:param extensions: Enable or disable the loaded extensions for the xml
|
||||||
generation (default: enabled).
|
generation (default: enabled).
|
||||||
:param pretty: If the feed should be split into multiple lines and
|
:param pretty: If the feed should be split into multiple lines and
|
||||||
|
@ -239,9 +239,9 @@ class FeedGenerator(object):
|
||||||
doc.write(filename, pretty_print=pretty, encoding=encoding,
|
doc.write(filename, pretty_print=pretty, encoding=encoding,
|
||||||
xml_declaration=xml_declaration)
|
xml_declaration=xml_declaration)
|
||||||
|
|
||||||
|
|
||||||
def _create_rss(self, extensions=True):
|
def _create_rss(self, extensions=True):
|
||||||
'''Create an RSS feed xml structure containing all previously set fields.
|
'''Create an RSS feed xml structure containing all previously set
|
||||||
|
fields.
|
||||||
|
|
||||||
:returns: Tuple containing the feed root element and the element tree.
|
:returns: Tuple containing the feed root element and the element tree.
|
||||||
'''
|
'''
|
||||||
|
@ -249,17 +249,20 @@ class FeedGenerator(object):
|
||||||
if extensions:
|
if extensions:
|
||||||
for ext in self.__extensions.values() or []:
|
for ext in self.__extensions.values() or []:
|
||||||
if ext.get('rss'):
|
if ext.get('rss'):
|
||||||
nsmap.update( ext['inst'].extend_ns() )
|
nsmap.update(ext['inst'].extend_ns())
|
||||||
|
|
||||||
nsmap.update({'atom': 'http://www.w3.org/2005/Atom',
|
nsmap.update({'atom': 'http://www.w3.org/2005/Atom',
|
||||||
'content': 'http://purl.org/rss/1.0/modules/content/'})
|
'content': 'http://purl.org/rss/1.0/modules/content/'})
|
||||||
|
|
||||||
feed = etree.Element('rss', version='2.0', nsmap=nsmap )
|
feed = etree.Element('rss', version='2.0', nsmap=nsmap)
|
||||||
channel = etree.SubElement(feed, 'channel')
|
channel = etree.SubElement(feed, 'channel')
|
||||||
if not ( self.__rss_title and self.__rss_link and self.__rss_description ):
|
if not (self.__rss_title and
|
||||||
missing = ', '.join(([] if self.__rss_title else ['title']) + \
|
self.__rss_link and
|
||||||
|
self.__rss_description):
|
||||||
|
missing = ([] if self.__rss_title else ['title']) + \
|
||||||
([] if self.__rss_link else ['link']) + \
|
([] if self.__rss_link else ['link']) + \
|
||||||
([] if self.__rss_description else ['description']))
|
([] if self.__rss_description else ['description'])
|
||||||
|
missing = ', '.join(missing)
|
||||||
raise ValueError('Required fields not set (%s)' % missing)
|
raise ValueError('Required fields not set (%s)' % missing)
|
||||||
title = etree.SubElement(channel, 'title')
|
title = etree.SubElement(channel, 'title')
|
||||||
title.text = self.__rss_title
|
title.text = self.__rss_title
|
||||||
|
@ -270,8 +273,8 @@ class FeedGenerator(object):
|
||||||
for ln in self.__atom_link or []:
|
for ln in self.__atom_link or []:
|
||||||
# It is recommended to include a atom self link in rss documents…
|
# It is recommended to include a atom self link in rss documents…
|
||||||
if ln.get('rel') == 'self':
|
if ln.get('rel') == 'self':
|
||||||
selflink = etree.SubElement(channel,
|
selflink = etree.SubElement(
|
||||||
'{http://www.w3.org/2005/Atom}link',
|
channel, '{http://www.w3.org/2005/Atom}link',
|
||||||
href=ln['href'], rel='self')
|
href=ln['href'], rel='self')
|
||||||
if ln.get('type'):
|
if ln.get('type'):
|
||||||
selflink.attrib['type'] = ln['type']
|
selflink.attrib['type'] = ln['type']
|
||||||
|
@ -310,11 +313,9 @@ class FeedGenerator(object):
|
||||||
url = etree.SubElement(image, 'url')
|
url = etree.SubElement(image, 'url')
|
||||||
url.text = self.__rss_image.get('url')
|
url.text = self.__rss_image.get('url')
|
||||||
title = etree.SubElement(image, 'title')
|
title = etree.SubElement(image, 'title')
|
||||||
title.text = self.__rss_image['title'] \
|
title.text = self.__rss_image.get('title', self.__rss_title)
|
||||||
if self.__rss_image.get('title') else self.__rss_title
|
|
||||||
link = etree.SubElement(image, 'link')
|
link = etree.SubElement(image, 'link')
|
||||||
link.text = self.__rss_image['link'] \
|
link.text = self.__rss_image.get('link', self.__rss_link)
|
||||||
if self.__rss_image.get('link') else self.__rss_link
|
|
||||||
if self.__rss_image.get('width'):
|
if self.__rss_image.get('width'):
|
||||||
width = etree.SubElement(image, 'width')
|
width = etree.SubElement(image, 'width')
|
||||||
width.text = self.__rss_image.get('width')
|
width.text = self.__rss_image.get('width')
|
||||||
|
@ -353,7 +354,8 @@ class FeedGenerator(object):
|
||||||
if self.__rss_textInput:
|
if self.__rss_textInput:
|
||||||
textInput = etree.SubElement(channel, 'textInput')
|
textInput = etree.SubElement(channel, 'textInput')
|
||||||
textInput.attrib['title'] = self.__rss_textInput.get('title')
|
textInput.attrib['title'] = self.__rss_textInput.get('title')
|
||||||
textInput.attrib['description'] = self.__rss_textInput.get('description')
|
textInput.attrib['description'] = \
|
||||||
|
self.__rss_textInput.get('description')
|
||||||
textInput.attrib['name'] = self.__rss_textInput.get('name')
|
textInput.attrib['name'] = self.__rss_textInput.get('name')
|
||||||
textInput.attrib['link'] = self.__rss_textInput.get('link')
|
textInput.attrib['link'] = self.__rss_textInput.get('link')
|
||||||
if self.__rss_ttl:
|
if self.__rss_ttl:
|
||||||
|
@ -375,7 +377,6 @@ class FeedGenerator(object):
|
||||||
doc = etree.ElementTree(feed)
|
doc = etree.ElementTree(feed)
|
||||||
return feed, doc
|
return feed, doc
|
||||||
|
|
||||||
|
|
||||||
def rss_str(self, pretty=False, extensions=True, encoding='UTF-8',
|
def rss_str(self, pretty=False, extensions=True, encoding='UTF-8',
|
||||||
xml_declaration=True):
|
xml_declaration=True):
|
||||||
'''Generates an RSS feed and returns the feed XML as string.
|
'''Generates an RSS feed and returns the feed XML as string.
|
||||||
|
@ -390,20 +391,19 @@ class FeedGenerator(object):
|
||||||
:returns: String representation of the RSS feed.
|
:returns: String representation of the RSS feed.
|
||||||
|
|
||||||
**Return type:** The return type may vary between different Python
|
**Return type:** The return type may vary between different Python
|
||||||
versions and your encoding parameters passed to this method. For details
|
versions and your encoding parameters passed to this method. For
|
||||||
have a look at the `lxml documentation
|
details have a look at the `lxml documentation
|
||||||
<https://docs.python.org/3/library/xml.etree.elementtree.html#xml.etree.ElementTree.tostring>`_
|
<https://docs.python.org/3/library/xml.etree.elementtree.html#xml.etree.ElementTree.tostring>`_
|
||||||
'''
|
'''
|
||||||
feed, doc = self._create_rss(extensions=extensions)
|
feed, doc = self._create_rss(extensions=extensions)
|
||||||
return etree.tostring(feed, pretty_print=pretty, encoding=encoding,
|
return etree.tostring(feed, pretty_print=pretty, encoding=encoding,
|
||||||
xml_declaration=xml_declaration)
|
xml_declaration=xml_declaration)
|
||||||
|
|
||||||
|
|
||||||
def rss_file(self, filename, extensions=True, pretty=False,
|
def rss_file(self, filename, extensions=True, pretty=False,
|
||||||
encoding='UTF-8', xml_declaration=True):
|
encoding='UTF-8', xml_declaration=True):
|
||||||
'''Generates an RSS feed and write the resulting XML to a file.
|
'''Generates an RSS feed and write the resulting XML to a file.
|
||||||
|
|
||||||
:param filename: Name of file to write, or a file-like object, or a URL.
|
:param filename: Name of file to write or a file-like object or a URL.
|
||||||
:param extensions: Enable or disable the loaded extensions for the xml
|
:param extensions: Enable or disable the loaded extensions for the xml
|
||||||
generation (default: enabled).
|
generation (default: enabled).
|
||||||
:param pretty: If the feed should be split into multiple lines and
|
:param pretty: If the feed should be split into multiple lines and
|
||||||
|
@ -416,7 +416,6 @@ class FeedGenerator(object):
|
||||||
doc.write(filename, pretty_print=pretty, encoding=encoding,
|
doc.write(filename, pretty_print=pretty, encoding=encoding,
|
||||||
xml_declaration=xml_declaration)
|
xml_declaration=xml_declaration)
|
||||||
|
|
||||||
|
|
||||||
def title(self, title=None):
|
def title(self, title=None):
|
||||||
'''Get or set the title value of the feed. It should contain a human
|
'''Get or set the title value of the feed. It should contain a human
|
||||||
readable title for the feed. Often the same as the title of the
|
readable title for the feed. Often the same as the title of the
|
||||||
|
@ -426,12 +425,11 @@ class FeedGenerator(object):
|
||||||
:param title: The new title of the feed.
|
:param title: The new title of the feed.
|
||||||
:returns: The feeds title.
|
:returns: The feeds title.
|
||||||
'''
|
'''
|
||||||
if not title is None:
|
if title is not None:
|
||||||
self.__atom_title = title
|
self.__atom_title = title
|
||||||
self.__rss_title = title
|
self.__rss_title = title
|
||||||
return self.__atom_title
|
return self.__atom_title
|
||||||
|
|
||||||
|
|
||||||
def id(self, id=None):
|
def id(self, id=None):
|
||||||
'''Get or set the feed id which identifies the feed using a universally
|
'''Get or set the feed id which identifies the feed using a universally
|
||||||
unique and permanent URI. If you have a long-term, renewable lease on
|
unique and permanent URI. If you have a long-term, renewable lease on
|
||||||
|
@ -442,17 +440,16 @@ class FeedGenerator(object):
|
||||||
:returns: Id of the feed.
|
:returns: Id of the feed.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
if not id is None:
|
if id is not None:
|
||||||
self.__atom_id = id
|
self.__atom_id = id
|
||||||
return self.__atom_id
|
return self.__atom_id
|
||||||
|
|
||||||
|
|
||||||
def updated(self, updated=None):
|
def updated(self, updated=None):
|
||||||
'''Set or get the updated value which indicates the last time the feed
|
'''Set or get the updated value which indicates the last time the feed
|
||||||
was modified in a significant way.
|
was modified in a significant way.
|
||||||
|
|
||||||
The value can either be a string which will automatically be parsed or a
|
The value can either be a string which will automatically be parsed or
|
||||||
datetime.datetime object. In any case it is necessary that the value
|
a datetime.datetime object. In any case it is necessary that the value
|
||||||
include timezone information.
|
include timezone information.
|
||||||
|
|
||||||
This will set both atom:updated and rss:lastBuildDate.
|
This will set both atom:updated and rss:lastBuildDate.
|
||||||
|
@ -463,7 +460,7 @@ class FeedGenerator(object):
|
||||||
:param updated: The modification date.
|
:param updated: The modification date.
|
||||||
:returns: Modification date as datetime.datetime
|
:returns: Modification date as datetime.datetime
|
||||||
'''
|
'''
|
||||||
if not updated is None:
|
if updated is not None:
|
||||||
if isinstance(updated, string_types):
|
if isinstance(updated, string_types):
|
||||||
updated = dateutil.parser.parse(updated)
|
updated = dateutil.parser.parse(updated)
|
||||||
if not isinstance(updated, datetime):
|
if not isinstance(updated, datetime):
|
||||||
|
@ -475,13 +472,12 @@ class FeedGenerator(object):
|
||||||
|
|
||||||
return self.__atom_updated
|
return self.__atom_updated
|
||||||
|
|
||||||
|
|
||||||
def lastBuildDate(self, lastBuildDate=None):
|
def lastBuildDate(self, lastBuildDate=None):
|
||||||
'''Set or get the lastBuildDate value which indicates the last time the
|
'''Set or get the lastBuildDate value which indicates the last time the
|
||||||
content of the channel changed.
|
content of the channel changed.
|
||||||
|
|
||||||
The value can either be a string which will automatically be parsed or a
|
The value can either be a string which will automatically be parsed or
|
||||||
datetime.datetime object. In any case it is necessary that the value
|
a datetime.datetime object. In any case it is necessary that the value
|
||||||
include timezone information.
|
include timezone information.
|
||||||
|
|
||||||
This will set both atom:updated and rss:lastBuildDate.
|
This will set both atom:updated and rss:lastBuildDate.
|
||||||
|
@ -492,13 +488,12 @@ class FeedGenerator(object):
|
||||||
:param lastBuildDate: The modification date.
|
:param lastBuildDate: The modification date.
|
||||||
:returns: Modification date as datetime.datetime
|
:returns: Modification date as datetime.datetime
|
||||||
'''
|
'''
|
||||||
return self.updated( lastBuildDate )
|
return self.updated(lastBuildDate)
|
||||||
|
|
||||||
|
|
||||||
def author(self, author=None, replace=False, **kwargs):
|
def author(self, author=None, replace=False, **kwargs):
|
||||||
'''Get or set author data. An author element is a dictionary containing a name,
|
'''Get or set author data. An author element is a dictionary containing
|
||||||
an email address and a URI. Name is mandatory for ATOM, email is mandatory
|
a name, an email address and a URI. Name is mandatory for ATOM, email
|
||||||
for RSS.
|
is mandatory for RSS.
|
||||||
|
|
||||||
This method can be called with:
|
This method can be called with:
|
||||||
|
|
||||||
|
@ -518,34 +513,36 @@ class FeedGenerator(object):
|
||||||
|
|
||||||
Example::
|
Example::
|
||||||
|
|
||||||
>>> feedgen.author( { 'name':'John Doe', 'email':'jdoe@example.com' } )
|
>>> feedgen.author({'name':'John Doe', 'email':'jdoe@example.com'})
|
||||||
[{'name':'John Doe','email':'jdoe@example.com'}]
|
[{'name':'John Doe','email':'jdoe@example.com'}]
|
||||||
|
|
||||||
>>> feedgen.author([{'name':'Mr. X'},{'name':'Max'}])
|
>>> feedgen.author([{'name':'Mr. X'},{'name':'Max'}])
|
||||||
[{'name':'John Doe','email':'jdoe@example.com'},
|
[{'name':'John Doe','email':'jdoe@example.com'},
|
||||||
{'name':'John Doe'}, {'name':'Max'}]
|
{'name':'John Doe'}, {'name':'Max'}]
|
||||||
|
|
||||||
>>> feedgen.author( name='John Doe', email='jdoe@example.com', replace=True )
|
>>> feedgen.author(name='John Doe', email='jdoe@example.com',
|
||||||
|
replace=True)
|
||||||
[{'name':'John Doe','email':'jdoe@example.com'}]
|
[{'name':'John Doe','email':'jdoe@example.com'}]
|
||||||
|
|
||||||
'''
|
'''
|
||||||
if author is None and kwargs:
|
if author is None and kwargs:
|
||||||
author = kwargs
|
author = kwargs
|
||||||
if not author is None:
|
if author is not None:
|
||||||
if replace or self.__atom_author is None:
|
if replace or self.__atom_author is None:
|
||||||
self.__atom_author = []
|
self.__atom_author = []
|
||||||
self.__atom_author += ensure_format( author,
|
self.__atom_author += ensure_format(author,
|
||||||
set(['name', 'email', 'uri']), set(['name']))
|
set(['name', 'email', 'uri']),
|
||||||
|
set(['name']))
|
||||||
self.__rss_author = []
|
self.__rss_author = []
|
||||||
for a in self.__atom_author:
|
for a in self.__atom_author:
|
||||||
if a.get('email'):
|
if a.get('email'):
|
||||||
self.__rss_author.append(a['email'])
|
self.__rss_author.append(a['email'])
|
||||||
return self.__atom_author
|
return self.__atom_author
|
||||||
|
|
||||||
|
|
||||||
def link(self, link=None, replace=False, **kwargs):
|
def link(self, link=None, replace=False, **kwargs):
|
||||||
'''Get or set link data. An link element is a dict with the fields href,
|
'''Get or set link data. An link element is a dict with the fields
|
||||||
rel, type, hreflang, title, and length. Href is mandatory for ATOM.
|
href, rel, type, hreflang, title, and length. Href is mandatory for
|
||||||
|
ATOM.
|
||||||
|
|
||||||
This method can be called with:
|
This method can be called with:
|
||||||
|
|
||||||
|
@ -560,8 +557,8 @@ class FeedGenerator(object):
|
||||||
or one of the following predefined values (default=alternate):
|
or one of the following predefined values (default=alternate):
|
||||||
|
|
||||||
- *alternate* an alternate representation of the entry or feed, for
|
- *alternate* an alternate representation of the entry or feed, for
|
||||||
example a permalink to the html version of the entry, or the front
|
example a permalink to the html version of the entry, or the
|
||||||
page of the weblog.
|
front page of the weblog.
|
||||||
- *enclosure* a related resource which is potentially large in size
|
- *enclosure* a related resource which is potentially large in size
|
||||||
and might require special handling, for example an audio or video
|
and might require special handling, for example an audio or video
|
||||||
recording.
|
recording.
|
||||||
|
@ -589,24 +586,30 @@ class FeedGenerator(object):
|
||||||
'''
|
'''
|
||||||
if link is None and kwargs:
|
if link is None and kwargs:
|
||||||
link = kwargs
|
link = kwargs
|
||||||
if not link is None:
|
if link is not None:
|
||||||
if replace or self.__atom_link is None:
|
if replace or self.__atom_link is None:
|
||||||
self.__atom_link = []
|
self.__atom_link = []
|
||||||
self.__atom_link += ensure_format( link,
|
self.__atom_link += ensure_format(
|
||||||
|
link,
|
||||||
set(['href', 'rel', 'type', 'hreflang', 'title', 'length']),
|
set(['href', 'rel', 'type', 'hreflang', 'title', 'length']),
|
||||||
set(['href']),
|
set(['href']),
|
||||||
{'rel': [
|
{'rel': [
|
||||||
'about', 'alternate', 'appendix', 'archives', 'author', 'bookmark',
|
'about', 'alternate', 'appendix', 'archives', 'author',
|
||||||
'canonical', 'chapter', 'collection', 'contents', 'copyright', 'create-form',
|
'bookmark', 'canonical', 'chapter', 'collection',
|
||||||
'current', 'derivedfrom', 'describedby', 'describes', 'disclosure',
|
'contents', 'copyright', 'create-form', 'current',
|
||||||
'duplicate', 'edit', 'edit-form', 'edit-media', 'enclosure', 'first', 'glossary',
|
'derivedfrom', 'describedby', 'describes', 'disclosure',
|
||||||
'help', 'hosts', 'hub', 'icon', 'index', 'item', 'last', 'latest-version', 'license',
|
'duplicate', 'edit', 'edit-form', 'edit-media',
|
||||||
'lrdd', 'memento', 'monitor', 'monitor-group', 'next', 'next-archive', 'nofollow',
|
'enclosure', 'first', 'glossary', 'help', 'hosts', 'hub',
|
||||||
'noreferrer', 'original', 'payment', 'predecessor-version', 'prefetch', 'prev', 'preview',
|
'icon', 'index', 'item', 'last', 'latest-version',
|
||||||
'previous', 'prev-archive', 'privacy-policy', 'profile', 'related', 'replies', 'search',
|
'license', 'lrdd', 'memento', 'monitor', 'monitor-group',
|
||||||
'section', 'self', 'service', 'start', 'stylesheet', 'subsection', 'successor-version',
|
'next', 'next-archive', 'nofollow', 'noreferrer',
|
||||||
'tag', 'terms-of-service', 'timegate', 'timemap', 'type', 'up', 'version-history', 'via',
|
'original', 'payment', 'predecessor-version', 'prefetch',
|
||||||
'working-copy', 'working-copy-of'
|
'prev', 'preview', 'previous', 'prev-archive',
|
||||||
|
'privacy-policy', 'profile', 'related', 'replies',
|
||||||
|
'search', 'section', 'self', 'service', 'start',
|
||||||
|
'stylesheet', 'subsection', 'successor-version', 'tag',
|
||||||
|
'terms-of-service', 'timegate', 'timemap', 'type', 'up',
|
||||||
|
'version-history', 'via', 'working-copy', 'working-copy-of'
|
||||||
]})
|
]})
|
||||||
# RSS only needs one URL. We use the first link for RSS:
|
# RSS only needs one URL. We use the first link for RSS:
|
||||||
if len(self.__atom_link) > 0:
|
if len(self.__atom_link) > 0:
|
||||||
|
@ -614,7 +617,6 @@ class FeedGenerator(object):
|
||||||
# return the set with more information (atom)
|
# return the set with more information (atom)
|
||||||
return self.__atom_link
|
return self.__atom_link
|
||||||
|
|
||||||
|
|
||||||
def category(self, category=None, replace=False, **kwargs):
|
def category(self, category=None, replace=False, **kwargs):
|
||||||
'''Get or set categories that the feed belongs to.
|
'''Get or set categories that the feed belongs to.
|
||||||
|
|
||||||
|
@ -630,8 +632,8 @@ class FeedGenerator(object):
|
||||||
- *scheme* identifies the categorization scheme via a URI.
|
- *scheme* identifies the categorization scheme via a URI.
|
||||||
- *label* provides a human-readable label for display
|
- *label* provides a human-readable label for display
|
||||||
|
|
||||||
If a label is present it is used for the RSS feeds. Otherwise the term is
|
If a label is present it is used for the RSS feeds. Otherwise the term
|
||||||
used. The scheme is used for the domain attribute in RSS.
|
is used. The scheme is used for the domain attribute in RSS.
|
||||||
|
|
||||||
:param link: Dict or list of dicts with data.
|
:param link: Dict or list of dicts with data.
|
||||||
:param replace: Add or replace old data.
|
:param replace: Add or replace old data.
|
||||||
|
@ -639,31 +641,30 @@ class FeedGenerator(object):
|
||||||
'''
|
'''
|
||||||
if category is None and kwargs:
|
if category is None and kwargs:
|
||||||
category = kwargs
|
category = kwargs
|
||||||
if not category is None:
|
if category is not None:
|
||||||
if replace or self.__atom_category is None:
|
if replace or self.__atom_category is None:
|
||||||
self.__atom_category = []
|
self.__atom_category = []
|
||||||
self.__atom_category += ensure_format(
|
self.__atom_category += ensure_format(
|
||||||
category,
|
category,
|
||||||
set(['term', 'scheme', 'label']),
|
set(['term', 'scheme', 'label']),
|
||||||
set(['term']) )
|
set(['term']))
|
||||||
# Map the ATOM categories to RSS categories. Use the atom:label as
|
# Map the ATOM categories to RSS categories. Use the atom:label as
|
||||||
# name or if not present the atom:term. The atom:scheme is the
|
# name or if not present the atom:term. The atom:scheme is the
|
||||||
# rss:domain.
|
# rss:domain.
|
||||||
self.__rss_category = []
|
self.__rss_category = []
|
||||||
for cat in self.__atom_category:
|
for cat in self.__atom_category:
|
||||||
rss_cat = {}
|
rss_cat = {}
|
||||||
rss_cat['value'] = cat['label'] if cat.get('label') else cat['term']
|
rss_cat['value'] = cat.get('label', cat['term'])
|
||||||
if cat.get('scheme'):
|
if cat.get('scheme'):
|
||||||
rss_cat['domain'] = cat['scheme']
|
rss_cat['domain'] = cat['scheme']
|
||||||
self.__rss_category.append( rss_cat )
|
self.__rss_category.append(rss_cat)
|
||||||
return self.__atom_category
|
return self.__atom_category
|
||||||
|
|
||||||
|
|
||||||
def cloud(self, domain=None, port=None, path=None, registerProcedure=None,
|
def cloud(self, domain=None, port=None, path=None, registerProcedure=None,
|
||||||
protocol=None):
|
protocol=None):
|
||||||
'''Set or get the cloud data of the feed. It is an RSS only attribute. It
|
'''Set or get the cloud data of the feed. It is an RSS only attribute.
|
||||||
specifies a web service that supports the rssCloud interface which can be
|
It specifies a web service that supports the rssCloud interface which
|
||||||
implemented in HTTP-POST, XML-RPC or SOAP 1.1.
|
can be implemented in HTTP-POST, XML-RPC or SOAP 1.1.
|
||||||
|
|
||||||
:param domain: The domain where the webservice can be found.
|
:param domain: The domain where the webservice can be found.
|
||||||
:param port: The port the webservice listens to.
|
:param port: The port the webservice listens to.
|
||||||
|
@ -672,12 +673,12 @@ class FeedGenerator(object):
|
||||||
:param protocol: Can be either HTTP-POST, XML-RPC or SOAP 1.1.
|
:param protocol: Can be either HTTP-POST, XML-RPC or SOAP 1.1.
|
||||||
:returns: Dictionary containing the cloud data.
|
:returns: Dictionary containing the cloud data.
|
||||||
'''
|
'''
|
||||||
if not domain is None:
|
if domain is not None:
|
||||||
self.__rss_cloud = {'domain':domain, 'port':port, 'path':path,
|
self.__rss_cloud = {'domain': domain, 'port': port, 'path': path,
|
||||||
'registerProcedure':registerProcedure, 'protocol':protocol}
|
'registerProcedure': registerProcedure,
|
||||||
|
'protocol': protocol}
|
||||||
return self.__rss_cloud
|
return self.__rss_cloud
|
||||||
|
|
||||||
|
|
||||||
def contributor(self, contributor=None, replace=False, **kwargs):
|
def contributor(self, contributor=None, replace=False, **kwargs):
|
||||||
'''Get or set the contributor data of the feed. This is an ATOM only
|
'''Get or set the contributor data of the feed. This is an ATOM only
|
||||||
value.
|
value.
|
||||||
|
@ -692,74 +693,73 @@ class FeedGenerator(object):
|
||||||
- *uri* contains a home page for the person.
|
- *uri* contains a home page for the person.
|
||||||
- *email* contains an email address for the person.
|
- *email* contains an email address for the person.
|
||||||
|
|
||||||
:param contributor: Dictionary or list of dictionaries with contributor data.
|
:param contributor: Dictionary or list of dictionaries with contributor
|
||||||
|
data.
|
||||||
:param replace: Add or replace old data.
|
:param replace: Add or replace old data.
|
||||||
:returns: List of contributors as dictionaries.
|
:returns: List of contributors as dictionaries.
|
||||||
'''
|
'''
|
||||||
if contributor is None and kwargs:
|
if contributor is None and kwargs:
|
||||||
contributor = kwargs
|
contributor = kwargs
|
||||||
if not contributor is None:
|
if contributor is not None:
|
||||||
if replace or self.__atom_contributor is None:
|
if replace or self.__atom_contributor is None:
|
||||||
self.__atom_contributor = []
|
self.__atom_contributor = []
|
||||||
self.__atom_contributor += ensure_format( contributor,
|
self.__atom_contributor += ensure_format(
|
||||||
set(['name', 'email', 'uri']), set(['name']))
|
contributor, set(['name', 'email', 'uri']), set(['name']))
|
||||||
return self.__atom_contributor
|
return self.__atom_contributor
|
||||||
|
|
||||||
|
|
||||||
def generator(self, generator=None, version=None, uri=None):
|
def generator(self, generator=None, version=None, uri=None):
|
||||||
'''Get or the generator of the feed which identifies the software used to
|
'''Get or the generator of the feed which identifies the software used
|
||||||
generate the feed, for debugging and other purposes. Both the uri and
|
to generate the feed, for debugging and other purposes. Both the uri
|
||||||
version attributes are optional and only available in the ATOM feed.
|
and version attributes are optional and only available in the ATOM
|
||||||
|
feed.
|
||||||
|
|
||||||
:param generator: Software used to create the feed.
|
:param generator: Software used to create the feed.
|
||||||
:param version: Version of the software.
|
:param version: Version of the software.
|
||||||
:param uri: URI the software can be found.
|
:param uri: URI the software can be found.
|
||||||
'''
|
'''
|
||||||
if not generator is None:
|
if generator is not None:
|
||||||
self.__atom_generator = {'value':generator}
|
self.__atom_generator = {'value': generator}
|
||||||
if not version is None:
|
if version is not None:
|
||||||
self.__atom_generator['version'] = version
|
self.__atom_generator['version'] = version
|
||||||
if not uri is None:
|
if uri is not None:
|
||||||
self.__atom_generator['uri'] = uri
|
self.__atom_generator['uri'] = uri
|
||||||
self.__rss_generator = generator
|
self.__rss_generator = generator
|
||||||
return self.__atom_generator
|
return self.__atom_generator
|
||||||
|
|
||||||
|
|
||||||
def icon(self, icon=None):
|
def icon(self, icon=None):
|
||||||
'''Get or set the icon of the feed which is a small image which provides
|
'''Get or set the icon of the feed which is a small image which
|
||||||
iconic visual identification for the feed. Icons should be square. This
|
provides iconic visual identification for the feed. Icons should be
|
||||||
is an ATOM only value.
|
square. This is an ATOM only value.
|
||||||
|
|
||||||
:param icon: URI of the feeds icon.
|
:param icon: URI of the feeds icon.
|
||||||
:returns: URI of the feeds icon.
|
:returns: URI of the feeds icon.
|
||||||
'''
|
'''
|
||||||
if not icon is None:
|
if icon is not None:
|
||||||
self.__atom_icon = icon
|
self.__atom_icon = icon
|
||||||
return self.__atom_icon
|
return self.__atom_icon
|
||||||
|
|
||||||
|
|
||||||
def logo(self, logo=None):
|
def logo(self, logo=None):
|
||||||
'''Get or set the logo of the feed which is a larger image which provides
|
'''Get or set the logo of the feed which is a larger image which
|
||||||
visual identification for the feed. Images should be twice as wide as
|
provides visual identification for the feed. Images should be twice as
|
||||||
they are tall. This is an ATOM value but will also set the rss:image
|
wide as they are tall. This is an ATOM value but will also set the
|
||||||
value.
|
rss:image value.
|
||||||
|
|
||||||
:param logo: Logo of the feed.
|
:param logo: Logo of the feed.
|
||||||
:returns: Logo of the feed.
|
:returns: Logo of the feed.
|
||||||
'''
|
'''
|
||||||
if not logo is None:
|
if logo is not None:
|
||||||
self.__atom_logo = logo
|
self.__atom_logo = logo
|
||||||
self.__rss_image = { 'url' : logo }
|
self.__rss_image = {'url': logo}
|
||||||
return self.__atom_logo
|
return self.__atom_logo
|
||||||
|
|
||||||
|
|
||||||
def image(self, url=None, title=None, link=None, width=None, height=None,
|
def image(self, url=None, title=None, link=None, width=None, height=None,
|
||||||
description=None):
|
description=None):
|
||||||
'''Set the image of the feed. This element is roughly equivalent to
|
'''Set the image of the feed. This element is roughly equivalent to
|
||||||
atom:logo.
|
atom:logo.
|
||||||
|
|
||||||
:param url: The URL of a GIF, JPEG or PNG image.
|
:param url: The URL of a GIF, JPEG or PNG image.
|
||||||
:param title: Describes the image. The default value is the feeds title.
|
:param title: Describes the image. The default value is the feeds
|
||||||
|
title.
|
||||||
:param link: URL of the site the image will link to. The default is to
|
:param link: URL of the site the image will link to. The default is to
|
||||||
use the feeds first altertate link.
|
use the feeds first altertate link.
|
||||||
:param width: Width of the image in pixel. The maximum is 144.
|
:param width: Width of the image in pixel. The maximum is 144.
|
||||||
|
@ -767,11 +767,11 @@ class FeedGenerator(object):
|
||||||
:param description: Title of the link.
|
:param description: Title of the link.
|
||||||
:returns: Data of the image as dictionary.
|
:returns: Data of the image as dictionary.
|
||||||
'''
|
'''
|
||||||
if not url is None:
|
if url is not None:
|
||||||
self.__rss_image = { 'url' : url }
|
self.__rss_image = {'url': url}
|
||||||
if not title is None:
|
if title is not None:
|
||||||
self.__rss_image['title'] = title
|
self.__rss_image['title'] = title
|
||||||
if not link is None:
|
if link is not None:
|
||||||
self.__rss_image['link'] = link
|
self.__rss_image['link'] = link
|
||||||
if width:
|
if width:
|
||||||
self.__rss_image['width'] = width
|
self.__rss_image['width'] = width
|
||||||
|
@ -780,20 +780,18 @@ class FeedGenerator(object):
|
||||||
self.__atom_logo = url
|
self.__atom_logo = url
|
||||||
return self.__rss_image
|
return self.__rss_image
|
||||||
|
|
||||||
|
|
||||||
def rights(self, rights=None):
|
def rights(self, rights=None):
|
||||||
'''Get or set the rights value of the feed which conveys information
|
'''Get or set the rights value of the feed which conveys information
|
||||||
about rights, e.g. copyrights, held in and over the feed. This ATOM value
|
about rights, e.g. copyrights, held in and over the feed. This ATOM
|
||||||
will also set rss:copyright.
|
value will also set rss:copyright.
|
||||||
|
|
||||||
:param rights: Rights information of the feed.
|
:param rights: Rights information of the feed.
|
||||||
'''
|
'''
|
||||||
if not rights is None:
|
if rights is not None:
|
||||||
self.__atom_rights = rights
|
self.__atom_rights = rights
|
||||||
self.__rss_copyright = rights
|
self.__rss_copyright = rights
|
||||||
return self.__atom_rights
|
return self.__atom_rights
|
||||||
|
|
||||||
|
|
||||||
def copyright(self, copyright=None):
|
def copyright(self, copyright=None):
|
||||||
'''Get or set the copyright notice for content in the channel. This RSS
|
'''Get or set the copyright notice for content in the channel. This RSS
|
||||||
value will also set the atom:rights value.
|
value will also set the atom:rights value.
|
||||||
|
@ -801,8 +799,7 @@ class FeedGenerator(object):
|
||||||
:param copyright: The copyright notice.
|
:param copyright: The copyright notice.
|
||||||
:returns: The copyright notice.
|
:returns: The copyright notice.
|
||||||
'''
|
'''
|
||||||
return self.rights( copyright )
|
return self.rights(copyright)
|
||||||
|
|
||||||
|
|
||||||
def subtitle(self, subtitle=None):
|
def subtitle(self, subtitle=None):
|
||||||
'''Get or set the subtitle value of the cannel which contains a
|
'''Get or set the subtitle value of the cannel which contains a
|
||||||
|
@ -812,79 +809,75 @@ class FeedGenerator(object):
|
||||||
:param subtitle: The subtitle of the feed.
|
:param subtitle: The subtitle of the feed.
|
||||||
:returns: The subtitle of the feed.
|
:returns: The subtitle of the feed.
|
||||||
'''
|
'''
|
||||||
if not subtitle is None:
|
if subtitle is not None:
|
||||||
self.__atom_subtitle = subtitle
|
self.__atom_subtitle = subtitle
|
||||||
self.__rss_description = subtitle
|
self.__rss_description = subtitle
|
||||||
return self.__atom_subtitle
|
return self.__atom_subtitle
|
||||||
|
|
||||||
|
|
||||||
def description(self, description=None):
|
def description(self, description=None):
|
||||||
'''Set and get the description of the feed. This is an RSS only element
|
'''Set and get the description of the feed. This is an RSS only element
|
||||||
which is a phrase or sentence describing the channel. It is mandatory for
|
which is a phrase or sentence describing the channel. It is mandatory
|
||||||
RSS feeds. It is roughly the same as atom:subtitle. Thus setting this
|
for RSS feeds. It is roughly the same as atom:subtitle. Thus setting
|
||||||
will also set atom:subtitle.
|
this will also set atom:subtitle.
|
||||||
|
|
||||||
:param description: Description of the channel.
|
:param description: Description of the channel.
|
||||||
:returns: Description of the channel.
|
:returns: Description of the channel.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
return self.subtitle( description )
|
return self.subtitle(description)
|
||||||
|
|
||||||
|
|
||||||
def docs(self, docs=None):
|
def docs(self, docs=None):
|
||||||
'''Get or set the docs value of the feed. This is an RSS only value. It
|
'''Get or set the docs value of the feed. This is an RSS only value. It
|
||||||
is a URL that points to the documentation for the format used in the RSS
|
is a URL that points to the documentation for the format used in the
|
||||||
file. It is probably a pointer to [1]. It is for people who might stumble
|
RSS file. It is probably a pointer to [1]. It is for people who might
|
||||||
across an RSS file on a Web server 25 years from now and wonder what it
|
stumble across an RSS file on a Web server 25 years from now and wonder
|
||||||
is.
|
what it is.
|
||||||
|
|
||||||
[1]: http://www.rssboard.org/rss-specification
|
[1]: http://www.rssboard.org/rss-specification
|
||||||
|
|
||||||
:param docs: URL of the format documentation.
|
:param docs: URL of the format documentation.
|
||||||
:returns: URL of the format documentation.
|
:returns: URL of the format documentation.
|
||||||
'''
|
'''
|
||||||
if not docs is None:
|
if docs is not None:
|
||||||
self.__rss_docs = docs
|
self.__rss_docs = docs
|
||||||
return self.__rss_docs
|
return self.__rss_docs
|
||||||
|
|
||||||
|
|
||||||
def language(self, language=None):
|
def language(self, language=None):
|
||||||
'''Get or set the language of the feed. It indicates the language the
|
'''Get or set the language of the feed. It indicates the language the
|
||||||
channel is written in. This allows aggregators to group all Italian
|
channel is written in. This allows aggregators to group all Italian
|
||||||
language sites, for example, on a single page. This is an RSS only field.
|
language sites, for example, on a single page. This is an RSS only
|
||||||
However, this value will also be used to set the xml:lang property of the
|
field. However, this value will also be used to set the xml:lang
|
||||||
ATOM feed node.
|
property of the ATOM feed node.
|
||||||
The value should be an IETF language tag.
|
The value should be an IETF language tag.
|
||||||
|
|
||||||
:param language: Language of the feed.
|
:param language: Language of the feed.
|
||||||
:returns: Language of the feed.
|
:returns: Language of the feed.
|
||||||
'''
|
'''
|
||||||
if not language is None:
|
if language is not None:
|
||||||
self.__rss_language = language
|
self.__rss_language = language
|
||||||
self.__atom_feed_xml_lang = language
|
self.__atom_feed_xml_lang = language
|
||||||
return self.__rss_language
|
return self.__rss_language
|
||||||
|
|
||||||
|
|
||||||
def managingEditor(self, managingEditor=None):
|
def managingEditor(self, managingEditor=None):
|
||||||
'''Set or get the value for managingEditor which is the email address for
|
'''Set or get the value for managingEditor which is the email address
|
||||||
person responsible for editorial content. This is a RSS only value.
|
for person responsible for editorial content. This is a RSS only
|
||||||
|
value.
|
||||||
|
|
||||||
:param managingEditor: Email adress of the managing editor.
|
:param managingEditor: Email adress of the managing editor.
|
||||||
:returns: Email adress of the managing editor.
|
:returns: Email adress of the managing editor.
|
||||||
'''
|
'''
|
||||||
if not managingEditor is None:
|
if managingEditor is not None:
|
||||||
self.__rss_managingEditor = managingEditor
|
self.__rss_managingEditor = managingEditor
|
||||||
return self.__rss_managingEditor
|
return self.__rss_managingEditor
|
||||||
|
|
||||||
|
|
||||||
def pubDate(self, pubDate=None):
|
def pubDate(self, pubDate=None):
|
||||||
'''Set or get the publication date for the content in the channel. For
|
'''Set or get the publication date for the content in the channel. For
|
||||||
example, the New York Times publishes on a daily basis, the publication
|
example, the New York Times publishes on a daily basis, the publication
|
||||||
date flips once every 24 hours. That's when the pubDate of the channel
|
date flips once every 24 hours. That's when the pubDate of the channel
|
||||||
changes.
|
changes.
|
||||||
|
|
||||||
The value can either be a string which will automatically be parsed or a
|
The value can either be a string which will automatically be parsed or
|
||||||
datetime.datetime object. In any case it is necessary that the value
|
a datetime.datetime object. In any case it is necessary that the value
|
||||||
include timezone information.
|
include timezone information.
|
||||||
|
|
||||||
This will set both atom:updated and rss:lastBuildDate.
|
This will set both atom:updated and rss:lastBuildDate.
|
||||||
|
@ -892,7 +885,7 @@ class FeedGenerator(object):
|
||||||
:param pubDate: The publication date.
|
:param pubDate: The publication date.
|
||||||
:returns: Publication date as datetime.datetime
|
:returns: Publication date as datetime.datetime
|
||||||
'''
|
'''
|
||||||
if not pubDate is None:
|
if pubDate is not None:
|
||||||
if isinstance(pubDate, string_types):
|
if isinstance(pubDate, string_types):
|
||||||
pubDate = dateutil.parser.parse(pubDate)
|
pubDate = dateutil.parser.parse(pubDate)
|
||||||
if not isinstance(pubDate, datetime):
|
if not isinstance(pubDate, datetime):
|
||||||
|
@ -903,76 +896,73 @@ class FeedGenerator(object):
|
||||||
|
|
||||||
return self.__rss_pubDate
|
return self.__rss_pubDate
|
||||||
|
|
||||||
|
|
||||||
def rating(self, rating=None):
|
def rating(self, rating=None):
|
||||||
'''Set and get the PICS rating for the channel. It is an RSS only
|
'''Set and get the PICS rating for the channel. It is an RSS only
|
||||||
value.
|
value.
|
||||||
'''
|
'''
|
||||||
if not rating is None:
|
if rating is not None:
|
||||||
self.__rss_rating = rating
|
self.__rss_rating = rating
|
||||||
return self.__rss_rating
|
return self.__rss_rating
|
||||||
|
|
||||||
|
|
||||||
def skipHours(self, hours=None, replace=False):
|
def skipHours(self, hours=None, replace=False):
|
||||||
'''Set or get the value of skipHours, a hint for aggregators telling them
|
'''Set or get the value of skipHours, a hint for aggregators telling
|
||||||
which hours they can skip. This is an RSS only value.
|
them which hours they can skip. This is an RSS only value.
|
||||||
|
|
||||||
This method can be called with an hour or a list of hours. The hours are
|
This method can be called with an hour or a list of hours. The hours
|
||||||
represented as integer values from 0 to 23.
|
are represented as integer values from 0 to 23.
|
||||||
|
|
||||||
:param hours: List of hours the feedreaders should not check the feed.
|
:param hours: List of hours the feedreaders should not check the feed.
|
||||||
:param replace: Add or replace old data.
|
:param replace: Add or replace old data.
|
||||||
:returns: List of hours the feedreaders should not check the feed.
|
:returns: List of hours the feedreaders should not check the feed.
|
||||||
'''
|
'''
|
||||||
if not hours is None:
|
if hours is not None:
|
||||||
if not (isinstance(hours, list) or isinstance(hours, set)):
|
if not (isinstance(hours, list) or isinstance(hours, set)):
|
||||||
hours = [hours]
|
hours = [hours]
|
||||||
for h in hours:
|
for h in hours:
|
||||||
if not h in range(24):
|
if h not in range(24):
|
||||||
raise ValueError('Invalid hour %s' % h)
|
raise ValueError('Invalid hour %s' % h)
|
||||||
if replace or not self.__rss_skipHours:
|
if replace or not self.__rss_skipHours:
|
||||||
self.__rss_skipHours = set()
|
self.__rss_skipHours = set()
|
||||||
self.__rss_skipHours |= set(hours)
|
self.__rss_skipHours |= set(hours)
|
||||||
return self.__rss_skipHours
|
return self.__rss_skipHours
|
||||||
|
|
||||||
|
|
||||||
def skipDays(self, days=None, replace=False):
|
def skipDays(self, days=None, replace=False):
|
||||||
'''Set or get the value of skipDays, a hint for aggregators telling them
|
'''Set or get the value of skipDays, a hint for aggregators telling
|
||||||
which days they can skip This is an RSS only value.
|
them which days they can skip This is an RSS only value.
|
||||||
|
|
||||||
This method can be called with a day name or a list of day names. The days are
|
This method can be called with a day name or a list of day names. The
|
||||||
represented as strings from 'Monday' to 'Sunday'.
|
days are represented as strings from 'Monday' to 'Sunday'.
|
||||||
|
|
||||||
:param hours: List of days the feedreaders should not check the feed.
|
:param hours: List of days the feedreaders should not check the feed.
|
||||||
:param replace: Add or replace old data.
|
:param replace: Add or replace old data.
|
||||||
:returns: List of days the feedreaders should not check the feed.
|
:returns: List of days the feedreaders should not check the feed.
|
||||||
'''
|
'''
|
||||||
if not days is None:
|
if days is not None:
|
||||||
if not (isinstance(days, list) or isinstance(days, set)):
|
if not (isinstance(days, list) or isinstance(days, set)):
|
||||||
days = [days]
|
days = [days]
|
||||||
for d in days:
|
for d in days:
|
||||||
if not d in ['Monday', 'Tuesday', 'Wednesday', 'Thursday',
|
if d not in ['Monday', 'Tuesday', 'Wednesday', 'Thursday',
|
||||||
'Friday', 'Saturday', 'Sunday']:
|
'Friday', 'Saturday', 'Sunday']:
|
||||||
raise ValueError('Invalid day %s' % h)
|
raise ValueError('Invalid day %s' % d)
|
||||||
if replace or not self.__rss_skipDays:
|
if replace or not self.__rss_skipDays:
|
||||||
self.__rss_skipDays = set()
|
self.__rss_skipDays = set()
|
||||||
self.__rss_skipDays |= set(days)
|
self.__rss_skipDays |= set(days)
|
||||||
return self.__rss_skipDays
|
return self.__rss_skipDays
|
||||||
|
|
||||||
|
|
||||||
def textInput(self, title=None, description=None, name=None, link=None):
|
def textInput(self, title=None, description=None, name=None, link=None):
|
||||||
'''Get or set the value of textInput. This is an RSS only field. The
|
'''Get or set the value of textInput. This is an RSS only field. The
|
||||||
purpose of the <textInput> element is something of a mystery. You can use
|
purpose of the <textInput> element is something of a mystery. You can
|
||||||
it to specify a search engine box. Or to allow a reader to provide
|
use it to specify a search engine box. Or to allow a reader to provide
|
||||||
feedback. Most aggregators ignore it.
|
feedback. Most aggregators ignore it.
|
||||||
|
|
||||||
:param title: The label of the Submit button in the text input area.
|
:param title: The label of the Submit button in the text input area.
|
||||||
:param description: Explains the text input area.
|
:param description: Explains the text input area.
|
||||||
:param name: The name of the text object in the text input area.
|
:param name: The name of the text object in the text input area.
|
||||||
:param link: The URL of the CGI script that processes text input requests.
|
:param link: The URL of the CGI script that processes text input
|
||||||
|
requests.
|
||||||
:returns: Dictionary containing textInput values.
|
:returns: Dictionary containing textInput values.
|
||||||
'''
|
'''
|
||||||
if not title is None:
|
if title is not None:
|
||||||
self.__rss_textInput = {}
|
self.__rss_textInput = {}
|
||||||
self.__rss_textInput['title'] = title
|
self.__rss_textInput['title'] = title
|
||||||
self.__rss_textInput['description'] = description
|
self.__rss_textInput['description'] = description
|
||||||
|
@ -980,37 +970,35 @@ class FeedGenerator(object):
|
||||||
self.__rss_textInput['link'] = link
|
self.__rss_textInput['link'] = link
|
||||||
return self.__rss_textInput
|
return self.__rss_textInput
|
||||||
|
|
||||||
|
|
||||||
def ttl(self, ttl=None):
|
def ttl(self, ttl=None):
|
||||||
'''Get or set the ttl value. It is an RSS only element. ttl stands for
|
'''Get or set the ttl value. It is an RSS only element. ttl stands for
|
||||||
time to live. It's a number of minutes that indicates how long a channel
|
time to live. It's a number of minutes that indicates how long a
|
||||||
can be cached before refreshing from the source.
|
channel can be cached before refreshing from the source.
|
||||||
|
|
||||||
:param ttl: Integer value indicating how long the channel may be cached.
|
:param ttl: Integer value indicating how long the channel may be
|
||||||
|
cached.
|
||||||
:returns: Time to live.
|
:returns: Time to live.
|
||||||
'''
|
'''
|
||||||
if not ttl is None:
|
if ttl is not None:
|
||||||
self.__rss_ttl = int(ttl)
|
self.__rss_ttl = int(ttl)
|
||||||
return self.__rss_ttl
|
return self.__rss_ttl
|
||||||
|
|
||||||
|
|
||||||
def webMaster(self, webMaster=None):
|
def webMaster(self, webMaster=None):
|
||||||
'''Get and set the value of webMaster, which represents the email address
|
'''Get and set the value of webMaster, which represents the email
|
||||||
for the person responsible for technical issues relating to the feed.
|
address for the person responsible for technical issues relating to the
|
||||||
This is an RSS only value.
|
feed. This is an RSS only value.
|
||||||
|
|
||||||
:param webMaster: Email address of the webmaster.
|
:param webMaster: Email address of the webmaster.
|
||||||
:returns: Email address of the webmaster.
|
:returns: Email address of the webmaster.
|
||||||
'''
|
'''
|
||||||
if not webMaster is None:
|
if webMaster is not None:
|
||||||
self.__rss_webMaster = webMaster
|
self.__rss_webMaster = webMaster
|
||||||
return self.__rss_webMaster
|
return self.__rss_webMaster
|
||||||
|
|
||||||
|
|
||||||
def add_entry(self, feedEntry=None):
|
def add_entry(self, feedEntry=None):
|
||||||
'''This method will add a new entry to the feed. If the feedEntry
|
'''This method will add a new entry to the feed. If the feedEntry
|
||||||
argument is omittet a new Entry object is created automatically. This is
|
argument is omittet a new Entry object is created automatically. This
|
||||||
the prefered way to add new entries to a feed.
|
is the prefered way to add new entries to a feed.
|
||||||
|
|
||||||
:param feedEntry: FeedEntry object to add.
|
:param feedEntry: FeedEntry object to add.
|
||||||
:returns: FeedEntry object created or passed to this function.
|
:returns: FeedEntry object created or passed to this function.
|
||||||
|
@ -1033,18 +1021,18 @@ class FeedGenerator(object):
|
||||||
items = self.__extensions.items()
|
items = self.__extensions.items()
|
||||||
|
|
||||||
# Try to load extensions:
|
# Try to load extensions:
|
||||||
for extname,ext in items:
|
for extname, ext in items:
|
||||||
try:
|
try:
|
||||||
feedEntry.register_extension(extname,
|
feedEntry.register_extension(extname,
|
||||||
ext['extension_class_entry'],
|
ext['extension_class_entry'],
|
||||||
ext['atom'], ext['rss'] )
|
ext['atom'],
|
||||||
|
ext['rss'])
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
self.__feed_entries.append( feedEntry )
|
self.__feed_entries.append(feedEntry)
|
||||||
return feedEntry
|
return feedEntry
|
||||||
|
|
||||||
|
|
||||||
def add_item(self, item=None):
|
def add_item(self, item=None):
|
||||||
'''This method will add a new item to the feed. If the item argument is
|
'''This method will add a new item to the feed. If the item argument is
|
||||||
omittet a new FeedEntry object is created automatically. This is just
|
omittet a new FeedEntry object is created automatically. This is just
|
||||||
|
@ -1052,7 +1040,6 @@ class FeedGenerator(object):
|
||||||
'''
|
'''
|
||||||
return self.add_entry(item)
|
return self.add_entry(item)
|
||||||
|
|
||||||
|
|
||||||
def entry(self, entry=None, replace=False):
|
def entry(self, entry=None, replace=False):
|
||||||
'''Get or set feed entries. Use the add_entry() method instead to
|
'''Get or set feed entries. Use the add_entry() method instead to
|
||||||
automatically create the FeedEntry objects.
|
automatically create the FeedEntry objects.
|
||||||
|
@ -1062,7 +1049,7 @@ class FeedGenerator(object):
|
||||||
:param entry: FeedEntry object or list of FeedEntry objects.
|
:param entry: FeedEntry object or list of FeedEntry objects.
|
||||||
:returns: List ob all feed entries.
|
:returns: List ob all feed entries.
|
||||||
'''
|
'''
|
||||||
if not entry is None:
|
if entry is not None:
|
||||||
if not isinstance(entry, list):
|
if not isinstance(entry, list):
|
||||||
entry = [entry]
|
entry = [entry]
|
||||||
if replace:
|
if replace:
|
||||||
|
@ -1077,24 +1064,22 @@ class FeedGenerator(object):
|
||||||
|
|
||||||
# Try to load extensions:
|
# Try to load extensions:
|
||||||
for e in entry:
|
for e in entry:
|
||||||
for extname,ext in items:
|
for extname, ext in items:
|
||||||
try:
|
try:
|
||||||
e.register_extension(extname,
|
e.register_extension(extname,
|
||||||
ext['extension_class_entry'],
|
ext['extension_class_entry'],
|
||||||
ext['atom'], ext['rss'] )
|
ext['atom'], ext['rss'])
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
self.__feed_entries += entry
|
self.__feed_entries += entry
|
||||||
return self.__feed_entries
|
return self.__feed_entries
|
||||||
|
|
||||||
|
|
||||||
def item(self, item=None, replace=False):
|
def item(self, item=None, replace=False):
|
||||||
'''Get or set feed items. This is just another name for entry(...)
|
'''Get or set feed items. This is just another name for entry(...)
|
||||||
'''
|
'''
|
||||||
return self.entry(item, replace)
|
return self.entry(item, replace)
|
||||||
|
|
||||||
|
|
||||||
def remove_entry(self, entry):
|
def remove_entry(self, entry):
|
||||||
'''Remove a single entry from the feed. This method accepts both the
|
'''Remove a single entry from the feed. This method accepts both the
|
||||||
FeedEntry object to remove or the index of the entry as argument.
|
FeedEntry object to remove or the index of the entry as argument.
|
||||||
|
@ -1106,14 +1091,12 @@ class FeedGenerator(object):
|
||||||
else:
|
else:
|
||||||
self.__feed_entries.pop(entry)
|
self.__feed_entries.pop(entry)
|
||||||
|
|
||||||
|
|
||||||
def remove_item(self, item):
|
def remove_item(self, item):
|
||||||
'''Remove a single item from the feed. This is another name for
|
'''Remove a single item from the feed. This is another name for
|
||||||
remove_entry.
|
remove_entry.
|
||||||
'''
|
'''
|
||||||
self.remove_entry(item)
|
self.remove_entry(item)
|
||||||
|
|
||||||
|
|
||||||
def load_extension(self, name, atom=True, rss=True):
|
def load_extension(self, name, atom=True, rss=True):
|
||||||
'''Load a specific extension by name.
|
'''Load a specific extension by name.
|
||||||
|
|
||||||
|
@ -1145,9 +1128,8 @@ class FeedGenerator(object):
|
||||||
entryext = None
|
entryext = None
|
||||||
self.register_extension(name, feedext, entryext, atom, rss)
|
self.register_extension(name, feedext, entryext, atom, rss)
|
||||||
|
|
||||||
|
def register_extension(self, namespace, extension_class_feed=None,
|
||||||
def register_extension(self, namespace, extension_class_feed = None,
|
extension_class_entry=None, atom=True, rss=True):
|
||||||
extension_class_entry = None, atom=True, rss=True):
|
|
||||||
'''Registers an extension by class.
|
'''Registers an extension by class.
|
||||||
|
|
||||||
:param namespace: namespace for the extension
|
:param namespace: namespace for the extension
|
||||||
|
@ -1169,17 +1151,19 @@ class FeedGenerator(object):
|
||||||
|
|
||||||
# `load_extension` registry
|
# `load_extension` registry
|
||||||
self.__extensions[namespace] = {
|
self.__extensions[namespace] = {
|
||||||
'inst':extinst,
|
'inst': extinst,
|
||||||
'extension_class_feed': extension_class_feed,
|
'extension_class_feed': extension_class_feed,
|
||||||
'extension_class_entry': extension_class_entry,
|
'extension_class_entry': extension_class_entry,
|
||||||
'atom':atom,
|
'atom': atom,
|
||||||
'rss':rss
|
'rss': rss
|
||||||
}
|
}
|
||||||
|
|
||||||
# Try to load the extension for already existing entries:
|
# Try to load the extension for already existing entries:
|
||||||
for entry in self.__feed_entries:
|
for entry in self.__feed_entries:
|
||||||
try:
|
try:
|
||||||
entry.register_extension(namespace,
|
entry.register_extension(namespace,
|
||||||
extension_class_entry, atom, rss)
|
extension_class_entry,
|
||||||
|
atom,
|
||||||
|
rss)
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -8,7 +8,8 @@
|
||||||
:copyright: 2013, Lars Kiesow <lkiesow@uos.de>
|
:copyright: 2013, Lars Kiesow <lkiesow@uos.de>
|
||||||
:license: FreeBSD and LGPL, see license.* for more details.
|
:license: FreeBSD and LGPL, see license.* for more details.
|
||||||
'''
|
'''
|
||||||
import sys, locale
|
import locale
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
def ensure_format(val, allowed, required, allowed_values=None, defaults=None):
|
def ensure_format(val, allowed, required, allowed_values=None, defaults=None):
|
||||||
|
@ -19,7 +20,8 @@ def ensure_format(val, allowed, required, allowed_values=None, defaults=None):
|
||||||
:param val: Dictionaries to check.
|
:param val: Dictionaries to check.
|
||||||
:param allowed: Set of allowed keys.
|
:param allowed: Set of allowed keys.
|
||||||
:param required: Set of required keys.
|
:param required: Set of required keys.
|
||||||
:param allowed_values: Dictionary with keys and sets of their allowed values.
|
:param allowed_values: Dictionary with keys and sets of their allowed
|
||||||
|
values.
|
||||||
:param defaults: Dictionary with default values.
|
:param defaults: Dictionary with default values.
|
||||||
:returns: List of checked dictionaries.
|
:returns: List of checked dictionaries.
|
||||||
'''
|
'''
|
||||||
|
@ -44,7 +46,7 @@ def ensure_format(val, allowed, required, allowed_values=None, defaults=None):
|
||||||
else:
|
else:
|
||||||
items = defaults.items()
|
items = defaults.items()
|
||||||
|
|
||||||
for k,v in items:
|
for k, v in items:
|
||||||
elem[k] = elem.get(k, v)
|
elem[k] = elem.get(k, v)
|
||||||
if not set(elem.keys()) <= allowed:
|
if not set(elem.keys()) <= allowed:
|
||||||
raise ValueError('Data contains invalid keys')
|
raise ValueError('Data contains invalid keys')
|
||||||
|
@ -56,9 +58,9 @@ def ensure_format(val, allowed, required, allowed_values=None, defaults=None):
|
||||||
else:
|
else:
|
||||||
values = allowed_values.items()
|
values = allowed_values.items()
|
||||||
|
|
||||||
for k,v in values:
|
for k, v in values:
|
||||||
if elem.get(k) and not elem[k] in v:
|
if elem.get(k) and not elem[k] in v:
|
||||||
raise ValueError('Invalid value for %s' % k )
|
raise ValueError('Invalid value for %s' % k)
|
||||||
return val
|
return val
|
||||||
|
|
||||||
|
|
||||||
|
|
31
setup.py
31
setup.py
|
@ -6,24 +6,24 @@ import feedgen.version
|
||||||
|
|
||||||
packages = ['feedgen', 'feedgen/ext']
|
packages = ['feedgen', 'feedgen/ext']
|
||||||
|
|
||||||
setup(
|
setup(name='feedgen',
|
||||||
name = 'feedgen',
|
packages=packages,
|
||||||
packages = packages,
|
version=feedgen.version.version_full_str,
|
||||||
version = feedgen.version.version_full_str,
|
description='Feed Generator (ATOM, RSS, Podcasts)',
|
||||||
description = 'Feed Generator (ATOM, RSS, Podcasts)',
|
author='Lars Kiesow',
|
||||||
author = 'Lars Kiesow',
|
author_email='lkiesow@uos.de',
|
||||||
author_email = 'lkiesow@uos.de',
|
url='http://lkiesow.github.io/python-feedgen',
|
||||||
url = 'http://lkiesow.github.io/python-feedgen',
|
keywords=['feed', 'ATOM', 'RSS', 'podcast'],
|
||||||
keywords = ['feed','ATOM','RSS','podcast'],
|
license='FreeBSD and LGPLv3+',
|
||||||
license = 'FreeBSD and LGPLv3+',
|
install_requires=['lxml', 'python-dateutil'],
|
||||||
install_requires = ['lxml', 'python-dateutil'],
|
classifiers=[
|
||||||
classifiers = [
|
|
||||||
'Development Status :: 5 - Production/Stable',
|
'Development Status :: 5 - Production/Stable',
|
||||||
'Intended Audience :: Developers',
|
'Intended Audience :: Developers',
|
||||||
'Intended Audience :: Information Technology',
|
'Intended Audience :: Information Technology',
|
||||||
'Intended Audience :: Science/Research',
|
'Intended Audience :: Science/Research',
|
||||||
'License :: OSI Approved :: BSD License',
|
'License :: OSI Approved :: BSD License',
|
||||||
'License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)',
|
'License :: OSI Approved :: GNU Lesser General Public License v3 ' +
|
||||||
|
'or later (LGPLv3+)',
|
||||||
'Natural Language :: English',
|
'Natural Language :: English',
|
||||||
'Operating System :: OS Independent',
|
'Operating System :: OS Independent',
|
||||||
'Programming Language :: Python',
|
'Programming Language :: Python',
|
||||||
|
@ -35,7 +35,7 @@ setup(
|
||||||
'Topic :: Text Processing :: Markup',
|
'Topic :: Text Processing :: Markup',
|
||||||
'Topic :: Text Processing :: Markup :: XML'
|
'Topic :: Text Processing :: Markup :: XML'
|
||||||
],
|
],
|
||||||
long_description = '''\
|
long_description='''\
|
||||||
Feedgenerator
|
Feedgenerator
|
||||||
=============
|
=============
|
||||||
|
|
||||||
|
@ -46,5 +46,4 @@ Podcasts.
|
||||||
It is licensed under the terms of both, the FreeBSD license and the LGPLv3+.
|
It is licensed under the terms of both, the FreeBSD license and the LGPLv3+.
|
||||||
Choose the one which is more convenient for you. For more details have a look
|
Choose the one which is more convenient for you. For more details have a look
|
||||||
at license.bsd and license.lgpl.
|
at license.bsd and license.lgpl.
|
||||||
'''
|
''')
|
||||||
)
|
|
||||||
|
|
|
@ -7,9 +7,9 @@ These are test cases for a basic entry.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
from lxml import etree
|
|
||||||
from feedgen.feed import FeedGenerator
|
from feedgen.feed import FeedGenerator
|
||||||
|
|
||||||
|
|
||||||
class TestSequenceFunctions(unittest.TestCase):
|
class TestSequenceFunctions(unittest.TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
@ -25,7 +25,7 @@ class TestSequenceFunctions(unittest.TestCase):
|
||||||
fe.id('http://lernfunk.de/media/654321/1')
|
fe.id('http://lernfunk.de/media/654321/1')
|
||||||
fe.title('The First Episode')
|
fe.title('The First Episode')
|
||||||
|
|
||||||
#Use also the different name add_item
|
# Use also the different name add_item
|
||||||
fe = fg.add_item()
|
fe = fg.add_item()
|
||||||
fe.id('http://lernfunk.de/media/654321/1')
|
fe.id('http://lernfunk.de/media/654321/1')
|
||||||
fe.title('The Second Episode')
|
fe.title('The Second Episode')
|
||||||
|
@ -49,7 +49,7 @@ class TestSequenceFunctions(unittest.TestCase):
|
||||||
def test_checkEntryContent(self):
|
def test_checkEntryContent(self):
|
||||||
|
|
||||||
fg = self.fg
|
fg = self.fg
|
||||||
assert len(fg.entry()) != None
|
assert fg.entry()
|
||||||
|
|
||||||
def test_removeEntryByIndex(self):
|
def test_removeEntryByIndex(self):
|
||||||
fg = FeedGenerator()
|
fg = FeedGenerator()
|
||||||
|
@ -79,15 +79,15 @@ class TestSequenceFunctions(unittest.TestCase):
|
||||||
def test_categoryHasDomain(self):
|
def test_categoryHasDomain(self):
|
||||||
fg = FeedGenerator()
|
fg = FeedGenerator()
|
||||||
fg.title('some title')
|
fg.title('some title')
|
||||||
fg.link( href='http://www.dontcare.com', rel='alternate' )
|
fg.link(href='http://www.dontcare.com', rel='alternate')
|
||||||
fg.description('description')
|
fg.description('description')
|
||||||
fe = fg.add_entry()
|
fe = fg.add_entry()
|
||||||
fe.id('http://lernfunk.de/media/654321/1')
|
fe.id('http://lernfunk.de/media/654321/1')
|
||||||
fe.title('some title')
|
fe.title('some title')
|
||||||
fe.category([
|
fe.category([
|
||||||
{'term' : 'category',
|
{'term': 'category',
|
||||||
'scheme': 'http://www.somedomain.com/category',
|
'scheme': 'http://www.somedomain.com/category',
|
||||||
'label' : 'Category',
|
'label': 'Category',
|
||||||
}])
|
}])
|
||||||
|
|
||||||
result = fg.rss_str()
|
result = fg.rss_str()
|
||||||
|
@ -103,4 +103,3 @@ class TestSequenceFunctions(unittest.TestCase):
|
||||||
fe.content('content', type='CDATA')
|
fe.content('content', type='CDATA')
|
||||||
result = fg.atom_str()
|
result = fg.atom_str()
|
||||||
assert b'<content type="CDATA"><![CDATA[content]]></content>' in result
|
assert b'<content type="CDATA"><![CDATA[content]]></content>' in result
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,8 @@ from lxml import etree
|
||||||
|
|
||||||
class TestExtensionSyndication(unittest.TestCase):
|
class TestExtensionSyndication(unittest.TestCase):
|
||||||
|
|
||||||
|
SYN_NS = {'sy': 'http://purl.org/rss/1.0/modules/syndication/'}
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.fg = FeedGenerator()
|
self.fg = FeedGenerator()
|
||||||
self.fg.load_extension('syndication')
|
self.fg.load_extension('syndication')
|
||||||
|
@ -20,14 +22,11 @@ class TestExtensionSyndication(unittest.TestCase):
|
||||||
self.fg.description('description')
|
self.fg.description('description')
|
||||||
|
|
||||||
def test_update_period(self):
|
def test_update_period(self):
|
||||||
for period_type in ('hourly', 'daily', 'weekly',
|
for period_type in ('hourly', 'daily', 'weekly', 'monthly', 'yearly'):
|
||||||
'monthly', 'yearly'):
|
|
||||||
self.fg.syndication.update_period(period_type)
|
self.fg.syndication.update_period(period_type)
|
||||||
root = etree.fromstring(self.fg.rss_str())
|
root = etree.fromstring(self.fg.rss_str())
|
||||||
a = root.xpath('/rss/channel/sy:UpdatePeriod',
|
a = root.xpath('/rss/channel/sy:UpdatePeriod',
|
||||||
namespaces={
|
namespaces=self.SYN_NS)
|
||||||
'sy':'http://purl.org/rss/1.0/modules/syndication/'
|
|
||||||
})
|
|
||||||
assert a[0].text == period_type
|
assert a[0].text == period_type
|
||||||
|
|
||||||
def test_update_frequency(self):
|
def test_update_frequency(self):
|
||||||
|
@ -35,19 +34,14 @@ class TestExtensionSyndication(unittest.TestCase):
|
||||||
self.fg.syndication.update_frequency(frequency)
|
self.fg.syndication.update_frequency(frequency)
|
||||||
root = etree.fromstring(self.fg.rss_str())
|
root = etree.fromstring(self.fg.rss_str())
|
||||||
a = root.xpath('/rss/channel/sy:UpdateFrequency',
|
a = root.xpath('/rss/channel/sy:UpdateFrequency',
|
||||||
namespaces={
|
namespaces=self.SYN_NS)
|
||||||
'sy':'http://purl.org/rss/1.0/modules/syndication/'
|
|
||||||
})
|
|
||||||
assert a[0].text == str(frequency)
|
assert a[0].text == str(frequency)
|
||||||
|
|
||||||
def test_update_base(self):
|
def test_update_base(self):
|
||||||
base = '2000-01-01T12:00+00:00'
|
base = '2000-01-01T12:00+00:00'
|
||||||
self.fg.syndication.update_base(base)
|
self.fg.syndication.update_base(base)
|
||||||
root = etree.fromstring(self.fg.rss_str())
|
root = etree.fromstring(self.fg.rss_str())
|
||||||
a = root.xpath('/rss/channel/sy:UpdateBase',
|
a = root.xpath('/rss/channel/sy:UpdateBase', namespaces=self.SYN_NS)
|
||||||
namespaces={
|
|
||||||
'sy':'http://purl.org/rss/1.0/modules/syndication/'
|
|
||||||
})
|
|
||||||
assert a[0].text == base
|
assert a[0].text == base
|
||||||
|
|
||||||
|
|
||||||
|
@ -61,13 +55,13 @@ class TestExtensionPodcast(unittest.TestCase):
|
||||||
self.fg.description('description')
|
self.fg.description('description')
|
||||||
|
|
||||||
def test_category_new(self):
|
def test_category_new(self):
|
||||||
self.fg.podcast.itunes_category([{'cat':'Technology',
|
self.fg.podcast.itunes_category([{'cat': 'Technology',
|
||||||
'sub':'Podcasting'}])
|
'sub': 'Podcasting'}])
|
||||||
self.fg.podcast.itunes_explicit('no')
|
self.fg.podcast.itunes_explicit('no')
|
||||||
self.fg.podcast.itunes_complete('no')
|
self.fg.podcast.itunes_complete('no')
|
||||||
self.fg.podcast.itunes_new_feed_url('http://example.com/new-feed.rss')
|
self.fg.podcast.itunes_new_feed_url('http://example.com/new-feed.rss')
|
||||||
self.fg.podcast.itunes_owner('John Doe', 'john@example.com')
|
self.fg.podcast.itunes_owner('John Doe', 'john@example.com')
|
||||||
ns = {'itunes':'http://www.itunes.com/dtds/podcast-1.0.dtd'}
|
ns = {'itunes': 'http://www.itunes.com/dtds/podcast-1.0.dtd'}
|
||||||
root = etree.fromstring(self.fg.rss_str())
|
root = etree.fromstring(self.fg.rss_str())
|
||||||
cat = root.xpath('/rss/channel/itunes:category/@text', namespaces=ns)
|
cat = root.xpath('/rss/channel/itunes:category/@text', namespaces=ns)
|
||||||
scat = root.xpath('/rss/channel/itunes:category/itunes:category/@text',
|
scat = root.xpath('/rss/channel/itunes:category/itunes:category/@text',
|
||||||
|
@ -81,7 +75,7 @@ class TestExtensionPodcast(unittest.TestCase):
|
||||||
self.fg.podcast.itunes_complete('no')
|
self.fg.podcast.itunes_complete('no')
|
||||||
self.fg.podcast.itunes_new_feed_url('http://example.com/new-feed.rss')
|
self.fg.podcast.itunes_new_feed_url('http://example.com/new-feed.rss')
|
||||||
self.fg.podcast.itunes_owner('John Doe', 'john@example.com')
|
self.fg.podcast.itunes_owner('John Doe', 'john@example.com')
|
||||||
ns = {'itunes':'http://www.itunes.com/dtds/podcast-1.0.dtd'}
|
ns = {'itunes': 'http://www.itunes.com/dtds/podcast-1.0.dtd'}
|
||||||
root = etree.fromstring(self.fg.rss_str())
|
root = etree.fromstring(self.fg.rss_str())
|
||||||
cat = root.xpath('/rss/channel/itunes:category/@text', namespaces=ns)
|
cat = root.xpath('/rss/channel/itunes:category/@text', namespaces=ns)
|
||||||
scat = root.xpath('/rss/channel/itunes:category/itunes:category/@text',
|
scat = root.xpath('/rss/channel/itunes:category/itunes:category/@text',
|
||||||
|
|
|
@ -12,6 +12,7 @@ from lxml import etree
|
||||||
from feedgen.feed import FeedGenerator
|
from feedgen.feed import FeedGenerator
|
||||||
from feedgen.ext.dc import DcExtension, DcEntryExtension
|
from feedgen.ext.dc import DcExtension, DcEntryExtension
|
||||||
|
|
||||||
|
|
||||||
class TestSequenceFunctions(unittest.TestCase):
|
class TestSequenceFunctions(unittest.TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
@ -26,7 +27,7 @@ class TestSequenceFunctions(unittest.TestCase):
|
||||||
|
|
||||||
self.authorName = 'John Doe'
|
self.authorName = 'John Doe'
|
||||||
self.authorMail = 'john@example.de'
|
self.authorMail = 'john@example.de'
|
||||||
self.author = {'name': self.authorName,'email': self.authorMail}
|
self.author = {'name': self.authorName, 'email': self.authorMail}
|
||||||
|
|
||||||
self.linkHref = 'http://example.com'
|
self.linkHref = 'http://example.com'
|
||||||
self.linkRel = 'alternate'
|
self.linkRel = 'alternate'
|
||||||
|
@ -50,12 +51,14 @@ class TestSequenceFunctions(unittest.TestCase):
|
||||||
self.cloudProtocol = 'SOAP 1.1'
|
self.cloudProtocol = 'SOAP 1.1'
|
||||||
|
|
||||||
self.icon = "http://example.com/icon.png"
|
self.icon = "http://example.com/icon.png"
|
||||||
self.contributor = {'name':"Contributor Name", 'uri':"Contributor Uri",
|
self.contributor = {'name': "Contributor Name",
|
||||||
|
'uri': "Contributor Uri",
|
||||||
'email': 'Contributor email'}
|
'email': 'Contributor email'}
|
||||||
self.copyright = "The copyright notice"
|
self.copyright = "The copyright notice"
|
||||||
self.docs = 'http://www.rssboard.org/rss-specification'
|
self.docs = 'http://www.rssboard.org/rss-specification'
|
||||||
self.managingEditor = 'mail@example.com'
|
self.managingEditor = 'mail@example.com'
|
||||||
self.rating = '(PICS-1.1 "http://www.classify.org/safesurf/" 1 r (SS~~000 1))'
|
self.rating = '(PICS-1.1 "http://www.classify.org/safesurf/" ' + \
|
||||||
|
'1 r (SS~~000 1))'
|
||||||
self.skipDays = 'Tuesday'
|
self.skipDays = 'Tuesday'
|
||||||
self.skipHours = 23
|
self.skipHours = 23
|
||||||
|
|
||||||
|
@ -71,13 +74,14 @@ class TestSequenceFunctions(unittest.TestCase):
|
||||||
fg.id(self.feedId)
|
fg.id(self.feedId)
|
||||||
fg.title(self.title)
|
fg.title(self.title)
|
||||||
fg.author(self.author)
|
fg.author(self.author)
|
||||||
fg.link( href=self.linkHref, rel=self.linkRel )
|
fg.link(href=self.linkHref, rel=self.linkRel)
|
||||||
fg.logo(self.logo)
|
fg.logo(self.logo)
|
||||||
fg.subtitle(self.subtitle)
|
fg.subtitle(self.subtitle)
|
||||||
fg.link( href=self.link2Href, rel=self.link2Rel )
|
fg.link(href=self.link2Href, rel=self.link2Rel)
|
||||||
fg.language(self.language)
|
fg.language(self.language)
|
||||||
fg.cloud(domain=self.cloudDomain, port=self.cloudPort,
|
fg.cloud(domain=self.cloudDomain, port=self.cloudPort,
|
||||||
path=self.cloudPath, registerProcedure=self.cloudRegisterProcedure,
|
path=self.cloudPath,
|
||||||
|
registerProcedure=self.cloudRegisterProcedure,
|
||||||
protocol=self.cloudProtocol)
|
protocol=self.cloudProtocol)
|
||||||
fg.icon(self.icon)
|
fg.icon(self.icon)
|
||||||
fg.category(term=self.categoryTerm, scheme=self.categoryScheme,
|
fg.category(term=self.categoryTerm, scheme=self.categoryScheme,
|
||||||
|
@ -90,14 +94,13 @@ class TestSequenceFunctions(unittest.TestCase):
|
||||||
fg.skipDays(self.skipDays)
|
fg.skipDays(self.skipDays)
|
||||||
fg.skipHours(self.skipHours)
|
fg.skipHours(self.skipHours)
|
||||||
fg.textInput(title=self.textInputTitle,
|
fg.textInput(title=self.textInputTitle,
|
||||||
description=self.textInputDescription, name=self.textInputName,
|
description=self.textInputDescription,
|
||||||
link=self.textInputLink)
|
name=self.textInputName, link=self.textInputLink)
|
||||||
fg.ttl(self.ttl)
|
fg.ttl(self.ttl)
|
||||||
fg.webMaster(self.webMaster)
|
fg.webMaster(self.webMaster)
|
||||||
|
|
||||||
self.fg = fg
|
self.fg = fg
|
||||||
|
|
||||||
|
|
||||||
def test_baseFeed(self):
|
def test_baseFeed(self):
|
||||||
fg = self.fg
|
fg = self.fg
|
||||||
|
|
||||||
|
@ -123,8 +126,8 @@ class TestSequenceFunctions(unittest.TestCase):
|
||||||
filename = 'tmp_Atomfeed.xml'
|
filename = 'tmp_Atomfeed.xml'
|
||||||
fg.atom_file(filename=filename, pretty=True, xml_declaration=False)
|
fg.atom_file(filename=filename, pretty=True, xml_declaration=False)
|
||||||
|
|
||||||
with open (filename, "r") as myfile:
|
with open(filename, "r") as myfile:
|
||||||
atomString=myfile.read().replace('\n', '')
|
atomString = myfile.read().replace('\n', '')
|
||||||
|
|
||||||
self.checkAtomString(atomString)
|
self.checkAtomString(atomString)
|
||||||
|
|
||||||
|
@ -137,18 +140,22 @@ class TestSequenceFunctions(unittest.TestCase):
|
||||||
def test_rel_values_for_atom(self):
|
def test_rel_values_for_atom(self):
|
||||||
values_for_rel = [
|
values_for_rel = [
|
||||||
'about', 'alternate', 'appendix', 'archives', 'author', 'bookmark',
|
'about', 'alternate', 'appendix', 'archives', 'author', 'bookmark',
|
||||||
'canonical', 'chapter', 'collection', 'contents', 'copyright', 'create-form',
|
'canonical', 'chapter', 'collection', 'contents', 'copyright',
|
||||||
'current', 'derivedfrom', 'describedby', 'describes', 'disclosure',
|
'create-form', 'current', 'derivedfrom', 'describedby',
|
||||||
'duplicate', 'edit', 'edit-form', 'edit-media', 'enclosure', 'first', 'glossary',
|
'describes', 'disclosure', 'duplicate', 'edit', 'edit-form',
|
||||||
'help', 'hosts', 'hub', 'icon', 'index', 'item', 'last', 'latest-version', 'license',
|
'edit-media', 'enclosure', 'first', 'glossary', 'help', 'hosts',
|
||||||
'lrdd', 'memento', 'monitor', 'monitor-group', 'next', 'next-archive', 'nofollow',
|
'hub', 'icon', 'index', 'item', 'last', 'latest-version',
|
||||||
'noreferrer', 'original', 'payment', 'predecessor-version', 'prefetch', 'prev', 'preview',
|
'license', 'lrdd', 'memento', 'monitor', 'monitor-group', 'next',
|
||||||
'previous', 'prev-archive', 'privacy-policy', 'profile', 'related', 'replies', 'search',
|
'next-archive', 'nofollow', 'noreferrer', 'original', 'payment',
|
||||||
'section', 'self', 'service', 'start', 'stylesheet', 'subsection', 'successor-version',
|
'predecessor-version', 'prefetch', 'prev', 'preview', 'previous',
|
||||||
'tag', 'terms-of-service', 'timegate', 'timemap', 'type', 'up', 'version-history', 'via',
|
'prev-archive', 'privacy-policy', 'profile', 'related', 'replies',
|
||||||
'working-copy', 'working-copy-of'
|
'search', 'section', 'self', 'service', 'start', 'stylesheet',
|
||||||
]
|
'subsection', 'successor-version', 'tag', 'terms-of-service',
|
||||||
links = [{'href': '%s/%s' % (self.linkHref, val.replace('-', '_')), 'rel': val} for val in values_for_rel]
|
'timegate', 'timemap', 'type', 'up', 'version-history', 'via',
|
||||||
|
'working-copy', 'working-copy-of']
|
||||||
|
links = [{'href': '%s/%s' % (self.linkHref,
|
||||||
|
val.replace('-', '_')), 'rel': val}
|
||||||
|
for val in values_for_rel]
|
||||||
fg = self.fg
|
fg = self.fg
|
||||||
fg.link(links, replace=True)
|
fg.link(links, replace=True)
|
||||||
atomString = fg.atom_str(pretty=True, xml_declaration=False)
|
atomString = fg.atom_str(pretty=True, xml_declaration=False)
|
||||||
|
@ -165,18 +172,22 @@ class TestSequenceFunctions(unittest.TestCase):
|
||||||
def test_rel_values_for_rss(self):
|
def test_rel_values_for_rss(self):
|
||||||
values_for_rel = [
|
values_for_rel = [
|
||||||
'about', 'alternate', 'appendix', 'archives', 'author', 'bookmark',
|
'about', 'alternate', 'appendix', 'archives', 'author', 'bookmark',
|
||||||
'canonical', 'chapter', 'collection', 'contents', 'copyright', 'create-form',
|
'canonical', 'chapter', 'collection', 'contents', 'copyright',
|
||||||
'current', 'derivedfrom', 'describedby', 'describes', 'disclosure',
|
'create-form', 'current', 'derivedfrom', 'describedby',
|
||||||
'duplicate', 'edit', 'edit-form', 'edit-media', 'enclosure', 'first', 'glossary',
|
'describes', 'disclosure', 'duplicate', 'edit', 'edit-form',
|
||||||
'help', 'hosts', 'hub', 'icon', 'index', 'item', 'last', 'latest-version', 'license',
|
'edit-media', 'enclosure', 'first', 'glossary', 'help', 'hosts',
|
||||||
'lrdd', 'memento', 'monitor', 'monitor-group', 'next', 'next-archive', 'nofollow',
|
'hub', 'icon', 'index', 'item', 'last', 'latest-version',
|
||||||
'noreferrer', 'original', 'payment', 'predecessor-version', 'prefetch', 'prev', 'preview',
|
'license', 'lrdd', 'memento', 'monitor', 'monitor-group', 'next',
|
||||||
'previous', 'prev-archive', 'privacy-policy', 'profile', 'related', 'replies', 'search',
|
'next-archive', 'nofollow', 'noreferrer', 'original', 'payment',
|
||||||
'section', 'self', 'service', 'start', 'stylesheet', 'subsection', 'successor-version',
|
'predecessor-version', 'prefetch', 'prev', 'preview', 'previous',
|
||||||
'tag', 'terms-of-service', 'timegate', 'timemap', 'type', 'up', 'version-history', 'via',
|
'prev-archive', 'privacy-policy', 'profile', 'related', 'replies',
|
||||||
'working-copy', 'working-copy-of'
|
'search', 'section', 'self', 'service', 'start', 'stylesheet',
|
||||||
]
|
'subsection', 'successor-version', 'tag', 'terms-of-service',
|
||||||
links = [{'href': '%s/%s' % (self.linkHref, val.replace('-', '_')), 'rel': val} for val in values_for_rel]
|
'timegate', 'timemap', 'type', 'up', 'version-history', 'via',
|
||||||
|
'working-copy', 'working-copy-of']
|
||||||
|
links = [{'href': '%s/%s' % (self.linkHref,
|
||||||
|
val.replace('-', '_')), 'rel': val}
|
||||||
|
for val in values_for_rel]
|
||||||
fg = self.fg
|
fg = self.fg
|
||||||
fg.link(links, replace=True)
|
fg.link(links, replace=True)
|
||||||
rssString = fg.rss_str(pretty=True, xml_declaration=False)
|
rssString = fg.rss_str(pretty=True, xml_declaration=False)
|
||||||
|
@ -185,13 +196,16 @@ class TestSequenceFunctions(unittest.TestCase):
|
||||||
nsAtom = self.nsAtom
|
nsAtom = self.nsAtom
|
||||||
|
|
||||||
atom_links = channel.findall("{%s}link" % nsAtom)
|
atom_links = channel.findall("{%s}link" % nsAtom)
|
||||||
assert len(atom_links) == 1 # rss feed only implements atom's 'self' link
|
# rss feed only implements atom's 'self' link
|
||||||
|
assert len(atom_links) == 1
|
||||||
assert atom_links[0].get('href') == '%s/%s' % (self.linkHref, 'self')
|
assert atom_links[0].get('href') == '%s/%s' % (self.linkHref, 'self')
|
||||||
assert atom_links[0].get('rel') == 'self'
|
assert atom_links[0].get('rel') == 'self'
|
||||||
|
|
||||||
rss_links = channel.findall('link')
|
rss_links = channel.findall('link')
|
||||||
assert len(rss_links) == 1 # RSS only needs one URL. We use the first link for RSS:
|
# RSS only needs one URL. We use the first link for RSS:
|
||||||
assert rss_links[0].text == '%s/%s' % (self.linkHref, 'working-copy-of'.replace('-', '_'))
|
assert len(rss_links) == 1
|
||||||
|
assert rss_links[0].text == '%s/%s' % \
|
||||||
|
(self.linkHref, 'working-copy-of'.replace('-', '_'))
|
||||||
|
|
||||||
def checkAtomString(self, atomString):
|
def checkAtomString(self, atomString):
|
||||||
|
|
||||||
|
@ -200,22 +214,31 @@ class TestSequenceFunctions(unittest.TestCase):
|
||||||
nsAtom = self.nsAtom
|
nsAtom = self.nsAtom
|
||||||
|
|
||||||
assert feed.find("{%s}title" % nsAtom).text == self.title
|
assert feed.find("{%s}title" % nsAtom).text == self.title
|
||||||
assert feed.find("{%s}updated" % nsAtom).text != None
|
assert feed.find("{%s}updated" % nsAtom).text is not None
|
||||||
assert feed.find("{%s}id" % nsAtom).text == self.feedId
|
assert feed.find("{%s}id" % nsAtom).text == self.feedId
|
||||||
assert feed.find("{%s}category" % nsAtom).get('term') == self.categoryTerm
|
assert feed.find("{%s}category" % nsAtom)\
|
||||||
assert feed.find("{%s}category" % nsAtom).get('label') == self.categoryLabel
|
.get('term') == self.categoryTerm
|
||||||
assert feed.find("{%s}author" % nsAtom).find("{%s}name" % nsAtom).text == self.authorName
|
assert feed.find("{%s}category" % nsAtom)\
|
||||||
assert feed.find("{%s}author" % nsAtom).find("{%s}email" % nsAtom).text == self.authorMail
|
.get('label') == self.categoryLabel
|
||||||
assert feed.findall("{%s}link" % nsAtom)[0].get('href') == self.linkHref
|
assert feed.find("{%s}author" % nsAtom)\
|
||||||
|
.find("{%s}name" % nsAtom).text == self.authorName
|
||||||
|
assert feed.find("{%s}author" % nsAtom)\
|
||||||
|
.find("{%s}email" % nsAtom).text == self.authorMail
|
||||||
|
assert feed.findall("{%s}link" % nsAtom)[0]\
|
||||||
|
.get('href') == self.linkHref
|
||||||
assert feed.findall("{%s}link" % nsAtom)[0].get('rel') == self.linkRel
|
assert feed.findall("{%s}link" % nsAtom)[0].get('rel') == self.linkRel
|
||||||
assert feed.findall("{%s}link" % nsAtom)[1].get('href') == self.link2Href
|
assert feed.findall("{%s}link" % nsAtom)[1]\
|
||||||
|
.get('href') == self.link2Href
|
||||||
assert feed.findall("{%s}link" % nsAtom)[1].get('rel') == self.link2Rel
|
assert feed.findall("{%s}link" % nsAtom)[1].get('rel') == self.link2Rel
|
||||||
assert feed.find("{%s}logo" % nsAtom).text == self.logo
|
assert feed.find("{%s}logo" % nsAtom).text == self.logo
|
||||||
assert feed.find("{%s}icon" % nsAtom).text == self.icon
|
assert feed.find("{%s}icon" % nsAtom).text == self.icon
|
||||||
assert feed.find("{%s}subtitle" % nsAtom).text == self.subtitle
|
assert feed.find("{%s}subtitle" % nsAtom).text == self.subtitle
|
||||||
assert feed.find("{%s}contributor" % nsAtom).find("{%s}name" % nsAtom).text == self.contributor['name']
|
assert feed.find("{%s}contributor" % nsAtom)\
|
||||||
assert feed.find("{%s}contributor" % nsAtom).find("{%s}email" % nsAtom).text == self.contributor['email']
|
.find("{%s}name" % nsAtom).text == self.contributor['name']
|
||||||
assert feed.find("{%s}contributor" % nsAtom).find("{%s}uri" % nsAtom).text == self.contributor['uri']
|
assert feed.find("{%s}contributor" % nsAtom)\
|
||||||
|
.find("{%s}email" % nsAtom).text == self.contributor['email']
|
||||||
|
assert feed.find("{%s}contributor" % nsAtom)\
|
||||||
|
.find("{%s}uri" % nsAtom).text == self.contributor['uri']
|
||||||
assert feed.find("{%s}rights" % nsAtom).text == self.copyright
|
assert feed.find("{%s}rights" % nsAtom).text == self.copyright
|
||||||
|
|
||||||
def test_rssFeedFile(self):
|
def test_rssFeedFile(self):
|
||||||
|
@ -223,8 +246,8 @@ class TestSequenceFunctions(unittest.TestCase):
|
||||||
filename = 'tmp_Rssfeed.xml'
|
filename = 'tmp_Rssfeed.xml'
|
||||||
fg.rss_file(filename=filename, pretty=True, xml_declaration=False)
|
fg.rss_file(filename=filename, pretty=True, xml_declaration=False)
|
||||||
|
|
||||||
with open (filename, "r") as myfile:
|
with open(filename, "r") as myfile:
|
||||||
rssString=myfile.read().replace('\n', '')
|
rssString = myfile.read().replace('\n', '')
|
||||||
|
|
||||||
self.checkRssString(rssString)
|
self.checkRssString(rssString)
|
||||||
|
|
||||||
|
@ -248,7 +271,7 @@ class TestSequenceFunctions(unittest.TestCase):
|
||||||
def test_extensionAlreadyLoaded(self):
|
def test_extensionAlreadyLoaded(self):
|
||||||
fg = self.fg
|
fg = self.fg
|
||||||
fg.load_extension('dc', atom=True, rss=True)
|
fg.load_extension('dc', atom=True, rss=True)
|
||||||
with self.assertRaises(ImportError) as context:
|
with self.assertRaises(ImportError):
|
||||||
fg.load_extension('dc')
|
fg.load_extension('dc')
|
||||||
|
|
||||||
def test_registerCustomExtension(self):
|
def test_registerCustomExtension(self):
|
||||||
|
@ -261,39 +284,42 @@ class TestSequenceFunctions(unittest.TestCase):
|
||||||
|
|
||||||
feed = etree.fromstring(rssString)
|
feed = etree.fromstring(rssString)
|
||||||
nsAtom = self.nsAtom
|
nsAtom = self.nsAtom
|
||||||
nsRss = self.nsRss
|
|
||||||
|
|
||||||
channel = feed.find("channel")
|
ch = feed.find("channel")
|
||||||
assert channel != None
|
assert ch is not None
|
||||||
|
|
||||||
|
assert ch.find("title").text == self.title
|
||||||
|
assert ch.find("description").text == self.subtitle
|
||||||
|
assert ch.find("lastBuildDate").text is not None
|
||||||
|
docs = "http://www.rssboard.org/rss-specification"
|
||||||
|
assert ch.find("docs").text == docs
|
||||||
|
assert ch.find("generator").text == "python-feedgen"
|
||||||
|
assert ch.findall("{%s}link" % nsAtom)[0].get('href') == self.link2Href
|
||||||
|
assert ch.findall("{%s}link" % nsAtom)[0].get('rel') == self.link2Rel
|
||||||
|
assert ch.find("image").find("url").text == self.logo
|
||||||
|
assert ch.find("image").find("title").text == self.title
|
||||||
|
assert ch.find("image").find("link").text == self.link2Href
|
||||||
|
assert ch.find("category").text == self.categoryLabel
|
||||||
|
assert ch.find("cloud").get('domain') == self.cloudDomain
|
||||||
|
assert ch.find("cloud").get('port') == self.cloudPort
|
||||||
|
assert ch.find("cloud").get('path') == self.cloudPath
|
||||||
|
assert ch.find("cloud").get('registerProcedure') == \
|
||||||
|
self.cloudRegisterProcedure
|
||||||
|
assert ch.find("cloud").get('protocol') == self.cloudProtocol
|
||||||
|
assert ch.find("copyright").text == self.copyright
|
||||||
|
assert ch.find("docs").text == self.docs
|
||||||
|
assert ch.find("managingEditor").text == self.managingEditor
|
||||||
|
assert ch.find("rating").text == self.rating
|
||||||
|
assert ch.find("skipDays").find("day").text == self.skipDays
|
||||||
|
assert int(ch.find("skipHours").find("hour").text) == self.skipHours
|
||||||
|
assert ch.find("textInput").get('title') == self.textInputTitle
|
||||||
|
assert ch.find("textInput").get('description') == \
|
||||||
|
self.textInputDescription
|
||||||
|
assert ch.find("textInput").get('name') == self.textInputName
|
||||||
|
assert ch.find("textInput").get('link') == self.textInputLink
|
||||||
|
assert int(ch.find("ttl").text) == self.ttl
|
||||||
|
assert ch.find("webMaster").text == self.webMaster
|
||||||
|
|
||||||
assert channel.find("title").text == self.title
|
|
||||||
assert channel.find("description").text == self.subtitle
|
|
||||||
assert channel.find("lastBuildDate").text != None
|
|
||||||
assert channel.find("docs").text == "http://www.rssboard.org/rss-specification"
|
|
||||||
assert channel.find("generator").text == "python-feedgen"
|
|
||||||
assert channel.findall("{%s}link" % nsAtom)[0].get('href') == self.link2Href
|
|
||||||
assert channel.findall("{%s}link" % nsAtom)[0].get('rel') == self.link2Rel
|
|
||||||
assert channel.find("image").find("url").text == self.logo
|
|
||||||
assert channel.find("image").find("title").text == self.title
|
|
||||||
assert channel.find("image").find("link").text == self.link2Href
|
|
||||||
assert channel.find("category").text == self.categoryLabel
|
|
||||||
assert channel.find("cloud").get('domain') == self.cloudDomain
|
|
||||||
assert channel.find("cloud").get('port') == self.cloudPort
|
|
||||||
assert channel.find("cloud").get('path') == self.cloudPath
|
|
||||||
assert channel.find("cloud").get('registerProcedure') == self.cloudRegisterProcedure
|
|
||||||
assert channel.find("cloud").get('protocol') == self.cloudProtocol
|
|
||||||
assert channel.find("copyright").text == self.copyright
|
|
||||||
assert channel.find("docs").text == self.docs
|
|
||||||
assert channel.find("managingEditor").text == self.managingEditor
|
|
||||||
assert channel.find("rating").text == self.rating
|
|
||||||
assert channel.find("skipDays").find("day").text == self.skipDays
|
|
||||||
assert int(channel.find("skipHours").find("hour").text) == self.skipHours
|
|
||||||
assert channel.find("textInput").get('title') == self.textInputTitle
|
|
||||||
assert channel.find("textInput").get('description') == self.textInputDescription
|
|
||||||
assert channel.find("textInput").get('name') == self.textInputName
|
|
||||||
assert channel.find("textInput").get('link') == self.textInputLink
|
|
||||||
assert int(channel.find("ttl").text) == self.ttl
|
|
||||||
assert channel.find("webMaster").text == self.webMaster
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
Loading…
Reference in a new issue