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_extension
|
||||
@rm -f tmp_Atomfeed.xml tmp_Rssfeed.xml
|
||||
flake8 $$(find setup.py tests feedgen -name '*.py')
|
||||
|
|
94
doc/conf.py
94
doc/conf.py
|
@ -3,7 +3,10 @@
|
|||
# All configuration values have a default; values that are commented out
|
||||
# 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,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
|
@ -13,18 +16,18 @@ sys.path.insert(0, os.path.abspath('.'))
|
|||
|
||||
import feedgen.version
|
||||
|
||||
# -- General configuration -----------------------------------------------------
|
||||
# -- General configuration ----------------------------------------------------
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
#needs_sphinx = '1.0'
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be extensions
|
||||
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||
extensions = [
|
||||
'sphinx.ext.intersphinx',
|
||||
'sphinx.ext.coverage',
|
||||
'sphinx.ext.autodoc'
|
||||
]
|
||||
'sphinx.ext.intersphinx',
|
||||
'sphinx.ext.coverage',
|
||||
'sphinx.ext.autodoc'
|
||||
]
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
@ -65,7 +68,8 @@ release = feedgen.version.version_full_str
|
|||
# directories to ignore when looking for source files.
|
||||
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
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
|
@ -86,7 +90,7 @@ pygments_style = 'sphinx'
|
|||
#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
|
||||
# a list of builtin themes.
|
||||
|
@ -169,24 +173,24 @@ html_static_path = ['_static']
|
|||
htmlhelp_basename = 'pyFeedGen'
|
||||
|
||||
|
||||
# -- Options for LaTeX output --------------------------------------------------
|
||||
# -- Options for LaTeX output -------------------------------------------------
|
||||
|
||||
latex_elements = {
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#'papersize': 'letterpaper',
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#'papersize': 'letterpaper',
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#'pointsize': '10pt',
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#'pointsize': '10pt',
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#'preamble': '',
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#'preamble': '',
|
||||
}
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title, author, documentclass [howto/manual]).
|
||||
# Grouping the document tree into LaTeX files. List of tuples (source start
|
||||
# file, target name, title, author, documentclass [howto/manual]).
|
||||
latex_documents = [
|
||||
('index', 'pyFeedGen.tex', u'pyFeedGen Documentation',
|
||||
u'Lars Kiesow', 'manual'),
|
||||
('index', 'pyFeedGen.tex', u'pyFeedGen Documentation', u'Lars Kiesow',
|
||||
'manual'),
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
|
@ -210,28 +214,28 @@ latex_documents = [
|
|||
#latex_domain_indices = True
|
||||
|
||||
|
||||
# -- Options for manual page output --------------------------------------------
|
||||
# -- Options for manual page output -------------------------------------------
|
||||
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
('index', 'pyFeedGen.tex', u'pyFeedGen Documentation',
|
||||
[u'Lars Kiesow'], 1)
|
||||
('index', 'pyFeedGen.tex', u'pyFeedGen Documentation',
|
||||
[u'Lars Kiesow'], 1)
|
||||
]
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#man_show_urls = False
|
||||
|
||||
|
||||
# -- Options for Texinfo output ------------------------------------------------
|
||||
# -- Options for Texinfo output -----------------------------------------------
|
||||
|
||||
# Grouping the document tree into Texinfo files. List of tuples
|
||||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
('index', 'pyFeedGen.tex', u'pyFeedGen Documentation',
|
||||
u'Lars Kiesow', 'Lernfunk3', 'One line description of project.',
|
||||
'Miscellaneous'),
|
||||
('index', 'pyFeedGen.tex', u'pyFeedGen Documentation',
|
||||
u'Lars Kiesow', 'Lernfunk3', 'One line description of project.',
|
||||
'Miscellaneous'),
|
||||
]
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
|
@ -248,36 +252,18 @@ texinfo_documents = [
|
|||
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
|
||||
r = re.compile(r'\[`*([^\]`]+)`*\]\(([^\)]+)\)')
|
||||
r2 = re.compile(r'.. include-github-readme')
|
||||
|
||||
|
||||
def substitute_link(app, docname, text):
|
||||
if docname == 'index':
|
||||
readme_text = ''
|
||||
with codecs.open(os.path.abspath('../readme.md'), 'r', 'utf-8') as f:
|
||||
readme_text = r.sub(r'`\1 <\2>`_', f.read())
|
||||
text[0] = r2.sub(readme_text, text[0])
|
||||
if docname == 'index':
|
||||
readme_text = ''
|
||||
with codecs.open(os.path.abspath('../readme.md'), 'r', 'utf-8') as f:
|
||||
readme_text = r.sub(r'`\1 <\2>`_', f.read())
|
||||
text[0] = r2.sub(readme_text, text[0])
|
||||
|
||||
|
||||
def setup(app):
|
||||
app.connect('autodoc-process-docstring', process_docstring)
|
||||
app.connect('source-read', substitute_link)
|
||||
app.connect('source-read', substitute_link)
|
||||
|
|
|
@ -1,136 +1,137 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
=======
|
||||
feedgen
|
||||
=======
|
||||
=======
|
||||
feedgen
|
||||
=======
|
||||
|
||||
This module can be used to generate web feeds in both ATOM and RSS format.
|
||||
It has support for extensions. Included is for example an extension to
|
||||
produce Podcasts.
|
||||
This module can be used to generate web feeds in both ATOM and RSS format.
|
||||
It has support for extensions. Included is for example an extension to
|
||||
produce Podcasts.
|
||||
|
||||
:copyright: 2013 by Lars Kiesow
|
||||
:license: FreeBSD and LGPL, see license.* for more details.
|
||||
:copyright: 2013 by Lars Kiesow
|
||||
:license: FreeBSD and LGPL, see license.* for more details.
|
||||
|
||||
|
||||
-------------
|
||||
Create a Feed
|
||||
-------------
|
||||
-------------
|
||||
Create a Feed
|
||||
-------------
|
||||
|
||||
To create a feed simply instanciate the FeedGenerator class and insert some
|
||||
data::
|
||||
To create a feed simply instanciate the FeedGenerator class and insert some
|
||||
data::
|
||||
|
||||
>>> from feedgen.feed import FeedGenerator
|
||||
>>> fg = FeedGenerator()
|
||||
>>> fg.id('http://lernfunk.de/media/654321')
|
||||
>>> fg.title('Some Testfeed')
|
||||
>>> fg.author( {'name':'John Doe','email':'john@example.de'} )
|
||||
>>> fg.link( href='http://example.com', rel='alternate' )
|
||||
>>> fg.logo('http://ex.com/logo.jpg')
|
||||
>>> fg.subtitle('This is a cool feed!')
|
||||
>>> fg.link( href='http://larskiesow.de/test.atom', rel='self' )
|
||||
>>> fg.language('en')
|
||||
>>> from feedgen.feed import FeedGenerator
|
||||
>>> fg = FeedGenerator()
|
||||
>>> fg.id('http://lernfunk.de/media/654321')
|
||||
>>> fg.title('Some Testfeed')
|
||||
>>> fg.author( {'name':'John Doe','email':'john@example.de'} )
|
||||
>>> fg.link( href='http://example.com', rel='alternate' )
|
||||
>>> fg.logo('http://ex.com/logo.jpg')
|
||||
>>> fg.subtitle('This is a cool feed!')
|
||||
>>> fg.link( href='http://larskiesow.de/test.atom', rel='self' )
|
||||
>>> fg.language('en')
|
||||
|
||||
Note that for the methods which set fields that can occur more than once in
|
||||
a feed you can use all of the following ways to provide data:
|
||||
Note that for the methods which set fields that can occur more than once in
|
||||
a feed you can use all of the following ways to provide data:
|
||||
|
||||
- Provide the data for that element as keyword arguments
|
||||
- Provide the data for that element as dictionary
|
||||
- Provide a list of dictionaries with the data for several elements
|
||||
- Provide the data for that element as keyword arguments
|
||||
- Provide the data for that element as dictionary
|
||||
- Provide a list of dictionaries with the data for several elements
|
||||
|
||||
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
|
||||
>>> rssfeed = fg.rss_str(pretty=True) # Get the RSS feed as string
|
||||
>>> fg.atom_file('atom.xml') # Write the ATOM feed to a file
|
||||
>>> fg.rss_file('rss.xml') # Write the RSS feed to a file
|
||||
>>> atomfeed = fg.atom_str(pretty=True) # Get the ATOM feed as string
|
||||
>>> rssfeed = fg.rss_str(pretty=True) # Get the RSS feed as string
|
||||
>>> fg.atom_file('atom.xml') # Write the ATOM feed to a file
|
||||
>>> fg.rss_file('rss.xml') # Write the RSS feed to a file
|
||||
|
||||
|
||||
----------------
|
||||
Add Feed Entries
|
||||
----------------
|
||||
----------------
|
||||
Add Feed Entries
|
||||
----------------
|
||||
|
||||
To add entries (items) to a feed you need to create new FeedEntry objects
|
||||
and append them to the list of entries in the FeedGenerator. The most
|
||||
convenient way to go is to use the FeedGenerator itself for the
|
||||
instantiation of the FeedEntry object::
|
||||
To add entries (items) to a feed you need to create new FeedEntry objects
|
||||
and append them to the list of entries in the FeedGenerator. The most
|
||||
convenient way to go is to use the FeedGenerator itself for the
|
||||
instantiation of the FeedEntry object::
|
||||
|
||||
>>> fe = fg.add_entry()
|
||||
>>> fe.id('http://lernfunk.de/media/654321/1')
|
||||
>>> fe.title('The First Episode')
|
||||
>>> fe = fg.add_entry()
|
||||
>>> fe.id('http://lernfunk.de/media/654321/1')
|
||||
>>> fe.title('The First Episode')
|
||||
|
||||
The FeedGenerators method add_entry(...) without argument provides will
|
||||
automatically generate a new FeedEntry object, append it to the feeds
|
||||
internal list of entries and return it, so that additional data can be
|
||||
added.
|
||||
The FeedGenerators method add_entry(...) without argument provides will
|
||||
automatically generate a new FeedEntry object, append it to the feeds
|
||||
internal list of entries and return it, so that additional data can be
|
||||
added.
|
||||
|
||||
----------
|
||||
Extensions
|
||||
----------
|
||||
----------
|
||||
Extensions
|
||||
----------
|
||||
|
||||
The FeedGenerator supports extension to include additional data into the XML
|
||||
structure of the feeds. Extensions can be loaded like this::
|
||||
The FeedGenerator supports extension to include additional data into the
|
||||
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)
|
||||
|
||||
This will try to load the extension “someext” from the file
|
||||
`ext/someext.py`. It is required that `someext.py` contains a class named
|
||||
“SomextExtension” which is required to have at least the two methods
|
||||
`extend_rss(...)` and `extend_atom(...)`. Although not required, it is
|
||||
strongly suggested to use BaseExtension from `ext/base.py` as superclass.
|
||||
This will try to load the extension “someext” from the file
|
||||
`ext/someext.py`. It is required that `someext.py` contains a class named
|
||||
“SomextExtension” which is required to have at least the two methods
|
||||
`extend_rss(...)` and `extend_atom(...)`. Although not required, it is
|
||||
strongly suggested to use BaseExtension from `ext/base.py` as superclass.
|
||||
|
||||
`load_extension('someext', ...)` will also try to load a class named
|
||||
“SomextEntryExtension” for every entry of the feed. This class can be
|
||||
located either in the same file as SomextExtension or in
|
||||
`ext/someext_entry.py` which is suggested especially for large extensions.
|
||||
`load_extension('someext', ...)` will also try to load a class named
|
||||
“SomextEntryExtension” for every entry of the feed. This class can be
|
||||
located either in the same file as SomextExtension or in
|
||||
`ext/someext_entry.py` which is suggested especially for large 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
|
||||
parameters is true which means that the extension would be used for both
|
||||
kinds of feeds.
|
||||
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 parameters is true which means that the extension would be used for
|
||||
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
|
||||
feed with some additional elements for ITunes.
|
||||
One extension already provided is the podcast extension. A podcast is an
|
||||
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::
|
||||
|
||||
>>> from feedgen.feed import FeedGenerator
|
||||
>>> fg = FeedGenerator()
|
||||
>>> fg.load_extension('podcast')
|
||||
...
|
||||
>>> fg.podcast.itunes_category('Technology', 'Podcasting')
|
||||
...
|
||||
>>> fg.rss_str(pretty=True)
|
||||
>>> fg.rss_file('podcast.xml')
|
||||
>>> from feedgen.feed import FeedGenerator
|
||||
>>> fg = FeedGenerator()
|
||||
>>> fg.load_extension('podcast')
|
||||
...
|
||||
>>> fg.podcast.itunes_category('Technology', 'Podcasting')
|
||||
...
|
||||
>>> fg.rss_str(pretty=True)
|
||||
>>> fg.rss_file('podcast.xml')
|
||||
|
||||
Of cause the extension has to be loaded for the FeedEntry objects as well
|
||||
but this is done automatically by the FeedGenerator for every feed entry if
|
||||
the extension is loaded for the whole feed. You can, however, load an
|
||||
extension for a specific FeedEntry by calling `load_extension(...)` on that
|
||||
entry. But this is a rather uncommon use.
|
||||
Of cause the extension has to be loaded for the FeedEntry objects as well
|
||||
but this is done automatically by the FeedGenerator for every feed entry if
|
||||
the extension is loaded for the whole feed. You can, however, load an
|
||||
extension for a specific FeedEntry by calling `load_extension(...)` on that
|
||||
entry. But this is a rather uncommon use.
|
||||
|
||||
Of cause you can still produce a normal ATOM or RSS feed, even if you have
|
||||
loaded some plugins by temporary disabling them during the feed generation.
|
||||
This can be done by calling the generating method with the keyword argument
|
||||
`extensions` set to `False`.
|
||||
Of cause you can still produce a normal ATOM or RSS feed, even if you have
|
||||
loaded some plugins by temporary disabling them during the feed generation.
|
||||
This can be done by calling the generating method with the keyword argument
|
||||
`extensions` set to `False`.
|
||||
|
||||
---------------------
|
||||
Testing the Generator
|
||||
---------------------
|
||||
---------------------
|
||||
Testing the Generator
|
||||
---------------------
|
||||
|
||||
You can test the module by simply executing::
|
||||
You can test the module by simply executing::
|
||||
|
||||
$ python -m feedgen
|
||||
$ python -m feedgen
|
||||
|
||||
"""
|
||||
|
|
|
@ -1,129 +1,140 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
'''
|
||||
feedgen
|
||||
~~~~~~~
|
||||
feedgen
|
||||
~~~~~~~
|
||||
|
||||
:copyright: 2013-2016, 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 feedgen.feed import FeedGenerator
|
||||
import sys
|
||||
|
||||
def print_enc(s):
|
||||
'''Print function compatible with both python2 and python3 accepting strings
|
||||
and byte arrays.
|
||||
'''
|
||||
if sys.version_info[0] >= 3:
|
||||
print(s.decode('utf-8') if type(s) == type(b'') else s)
|
||||
else:
|
||||
print(s)
|
||||
|
||||
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):
|
||||
'''Print function compatible with both python2 and python3 accepting strings
|
||||
and byte arrays.
|
||||
'''
|
||||
if sys.version_info[0] >= 3:
|
||||
print(s.decode('utf-8') if isinstance(s, bytes) else s)
|
||||
else:
|
||||
print(s)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) != 2 or not (
|
||||
sys.argv[1].endswith('rss') \
|
||||
or sys.argv[1].endswith('atom') \
|
||||
or sys.argv[1].endswith('torrent') \
|
||||
or sys.argv[1].endswith('podcast') ):
|
||||
print_enc ('Usage: %s ( <file>.atom | atom | <file>.rss | rss | podcast | torrent )' % \
|
||||
'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()
|
||||
if len(sys.argv) != 2 or not (
|
||||
sys.argv[1].endswith('rss') or
|
||||
sys.argv[1].endswith('atom') or
|
||||
sys.argv[1] == 'torrent' or
|
||||
sys.argv[1] == 'podcast'):
|
||||
print(USAGE)
|
||||
exit()
|
||||
|
||||
arg = sys.argv[1]
|
||||
arg = sys.argv[1]
|
||||
|
||||
fg = FeedGenerator()
|
||||
fg.id('http://lernfunk.de/_MEDIAID_123')
|
||||
fg.title('Testfeed')
|
||||
fg.author( {'name':'Lars Kiesow','email':'lkiesow@uos.de'} )
|
||||
fg.link( href='http://example.com', rel='alternate' )
|
||||
fg.category(term='test')
|
||||
fg.contributor( name='Lars Kiesow', email='lkiesow@uos.de' )
|
||||
fg.contributor( name='John Doe', email='jdoe@example.com' )
|
||||
fg.icon('http://ex.com/icon.jpg')
|
||||
fg.logo('http://ex.com/logo.jpg')
|
||||
fg.rights('cc-by')
|
||||
fg.subtitle('This is a cool feed!')
|
||||
fg.link( href='http://larskiesow.de/test.atom', rel='self' )
|
||||
fg.language('de')
|
||||
fe = fg.add_entry()
|
||||
fe.id('http://lernfunk.de/_MEDIAID_123#1')
|
||||
fe.title('First Element')
|
||||
fe.content('''Lorem ipsum dolor sit amet, consectetur adipiscing elit. Tamen
|
||||
aberramus a proposito, et, ne longius, prorsus, inquam, Piso, si ista
|
||||
mala sunt, placet. Aut etiam, ut vestitum, sic sententiam habeas aliam
|
||||
domesticam, aliam forensem, ut in fronte ostentatio sit, intus veritas
|
||||
occultetur? Cum id fugiunt, re eadem defendunt, quae Peripatetici,
|
||||
verba.''')
|
||||
fe.summary(u'Lorem ipsum dolor sit amet, consectetur adipiscing elit…')
|
||||
fe.link( href='http://example.com', rel='alternate' )
|
||||
fe.author( name='Lars Kiesow', email='lkiesow@uos.de' )
|
||||
fg = FeedGenerator()
|
||||
fg.id('http://lernfunk.de/_MEDIAID_123')
|
||||
fg.title('Testfeed')
|
||||
fg.author({'name': 'Lars Kiesow', 'email': 'lkiesow@uos.de'})
|
||||
fg.link(href='http://example.com', rel='alternate')
|
||||
fg.category(term='test')
|
||||
fg.contributor(name='Lars Kiesow', email='lkiesow@uos.de')
|
||||
fg.contributor(name='John Doe', email='jdoe@example.com')
|
||||
fg.icon('http://ex.com/icon.jpg')
|
||||
fg.logo('http://ex.com/logo.jpg')
|
||||
fg.rights('cc-by')
|
||||
fg.subtitle('This is a cool feed!')
|
||||
fg.link(href='http://larskiesow.de/test.atom', rel='self')
|
||||
fg.language('de')
|
||||
fe = fg.add_entry()
|
||||
fe.id('http://lernfunk.de/_MEDIAID_123#1')
|
||||
fe.title('First Element')
|
||||
fe.content('''Lorem ipsum dolor sit amet, consectetur adipiscing elit. Tamen
|
||||
aberramus a proposito, et, ne longius, prorsus, inquam, Piso, si
|
||||
ista mala sunt, placet. Aut etiam, ut vestitum, sic sententiam
|
||||
habeas aliam domesticam, aliam forensem, ut in fronte ostentatio
|
||||
sit, intus veritas occultetur? Cum id fugiunt, re eadem defendunt,
|
||||
quae Peripatetici, verba.''')
|
||||
fe.summary(u'Lorem ipsum dolor sit amet, consectetur adipiscing elit…')
|
||||
fe.link(href='http://example.com', rel='alternate')
|
||||
fe.author(name='Lars Kiesow', email='lkiesow@uos.de')
|
||||
|
||||
if arg == 'atom':
|
||||
print_enc (fg.atom_str(pretty=True))
|
||||
elif arg == 'rss':
|
||||
print_enc (fg.rss_str(pretty=True))
|
||||
elif arg == 'podcast':
|
||||
# Load the podcast extension. It will automatically be loaded for all
|
||||
# entries in the feed, too. Thus also for our “fe”.
|
||||
fg.load_extension('podcast')
|
||||
fg.podcast.itunes_author('Lars Kiesow')
|
||||
fg.podcast.itunes_category('Technology', 'Podcasting')
|
||||
fg.podcast.itunes_explicit('no')
|
||||
fg.podcast.itunes_complete('no')
|
||||
fg.podcast.itunes_new_feed_url('http://example.com/new-feed.rss')
|
||||
fg.podcast.itunes_owner('John Doe', 'john@example.com')
|
||||
fg.podcast.itunes_summary('Lorem ipsum dolor sit amet, ' + \
|
||||
'consectetur adipiscing elit. ' + \
|
||||
'Verba tu fingas et ea dicas, quae non sentias?')
|
||||
fe.podcast.itunes_author('Lars Kiesow')
|
||||
print_enc (fg.rss_str(pretty=True))
|
||||
if arg == 'atom':
|
||||
print_enc(fg.atom_str(pretty=True))
|
||||
elif arg == 'rss':
|
||||
print_enc(fg.rss_str(pretty=True))
|
||||
elif arg == 'podcast':
|
||||
# Load the podcast extension. It will automatically be loaded for all
|
||||
# entries in the feed, too. Thus also for our “fe”.
|
||||
fg.load_extension('podcast')
|
||||
fg.podcast.itunes_author('Lars Kiesow')
|
||||
fg.podcast.itunes_category('Technology', 'Podcasting')
|
||||
fg.podcast.itunes_explicit('no')
|
||||
fg.podcast.itunes_complete('no')
|
||||
fg.podcast.itunes_new_feed_url('http://example.com/new-feed.rss')
|
||||
fg.podcast.itunes_owner('John Doe', 'john@example.com')
|
||||
fg.podcast.itunes_summary('Lorem ipsum dolor sit amet, consectetur ' +
|
||||
'adipiscing elit. Verba tu fingas et ea ' +
|
||||
'dicas, quae non sentias?')
|
||||
fe.podcast.itunes_author('Lars Kiesow')
|
||||
print_enc(fg.rss_str(pretty=True))
|
||||
|
||||
elif arg == '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.torrent.filename('debian-8.4.0-i386-netint.iso.torrent')
|
||||
fe.torrent.infohash('7661229811ef32014879ceedcdf4a48f256c88ba')
|
||||
fe.torrent.contentlength('331350016')
|
||||
fe.torrent.seeds('789')
|
||||
fe.torrent.peers('456')
|
||||
fe.torrent.verified('123')
|
||||
print_enc (fg.rss_str(pretty=True))
|
||||
elif arg == 'torrent':
|
||||
fg.load_extension('torrent')
|
||||
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.infohash('7661229811ef32014879ceedcdf4a48f256c88ba')
|
||||
fe.torrent.contentlength('331350016')
|
||||
fe.torrent.seeds('789')
|
||||
fe.torrent.peers('456')
|
||||
fe.torrent.verified('123')
|
||||
print_enc(fg.rss_str(pretty=True))
|
||||
|
||||
elif arg.startswith('dc.'):
|
||||
fg.load_extension('dc')
|
||||
fg.dc.dc_contributor('Lars Kiesow')
|
||||
if arg.endswith('.atom'):
|
||||
print_enc (fg.atom_str(pretty=True))
|
||||
else:
|
||||
print_enc (fg.rss_str(pretty=True))
|
||||
elif arg.startswith('dc.'):
|
||||
fg.load_extension('dc')
|
||||
fg.dc.dc_contributor('Lars Kiesow')
|
||||
if arg.endswith('.atom'):
|
||||
print_enc(fg.atom_str(pretty=True))
|
||||
else:
|
||||
print_enc(fg.rss_str(pretty=True))
|
||||
|
||||
elif arg.startswith('syndication'):
|
||||
fg.load_extension('syndication')
|
||||
fg.syndication.update_period('daily')
|
||||
fg.syndication.update_frequency(2)
|
||||
fg.syndication.update_base('2000-01-01T12:00+00:00')
|
||||
if arg.endswith('.rss'):
|
||||
print_enc (fg.rss_str(pretty=True))
|
||||
else:
|
||||
print_enc (fg.atom_str(pretty=True))
|
||||
elif arg.startswith('syndication'):
|
||||
fg.load_extension('syndication')
|
||||
fg.syndication.update_period('daily')
|
||||
fg.syndication.update_frequency(2)
|
||||
fg.syndication.update_base('2000-01-01T12:00+00:00')
|
||||
if arg.endswith('.rss'):
|
||||
print_enc(fg.rss_str(pretty=True))
|
||||
else:
|
||||
print_enc(fg.atom_str(pretty=True))
|
||||
|
||||
elif arg.endswith('atom'):
|
||||
fg.atom_file(arg)
|
||||
elif arg.endswith('atom'):
|
||||
fg.atom_file(arg)
|
||||
|
||||
elif arg.endswith('rss'):
|
||||
fg.rss_file(arg)
|
||||
elif arg.endswith('rss'):
|
||||
fg.rss_file(arg)
|
||||
|
|
|
@ -2,6 +2,6 @@
|
|||
|
||||
import sys
|
||||
if sys.version_info[0] >= 3:
|
||||
string_types = str
|
||||
string_types = str
|
||||
else:
|
||||
string_types = basestring
|
||||
string_types = basestring # noqa: F821
|
||||
|
|
1325
feedgen/entry.py
1325
feedgen/entry.py
File diff suppressed because it is too large
Load diff
|
@ -1,6 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
===========
|
||||
feedgen.ext
|
||||
===========
|
||||
===========
|
||||
feedgen.ext
|
||||
===========
|
||||
"""
|
||||
|
|
|
@ -1,43 +1,43 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
'''
|
||||
feedgen.ext.base
|
||||
~~~~~~~~~~~~~~~~
|
||||
feedgen.ext.base
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
Basic FeedGenerator extension which does nothing but provides all necessary
|
||||
methods.
|
||||
Basic FeedGenerator extension which does nothing but provides all necessary
|
||||
methods.
|
||||
|
||||
: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.
|
||||
'''
|
||||
|
||||
|
||||
class BaseExtension(object):
|
||||
'''Basic FeedGenerator extension.
|
||||
'''
|
||||
def extend_ns(self):
|
||||
'''Returns a dict that will be used in the namespace map for the feed.'''
|
||||
return dict()
|
||||
'''Basic FeedGenerator extension.
|
||||
'''
|
||||
def extend_ns(self):
|
||||
'''Returns a dict that will be used in the namespace map for the feed.
|
||||
'''
|
||||
return dict()
|
||||
|
||||
def extend_rss(self, feed):
|
||||
'''Extend a RSS feed xml structure containing all previously set fields.
|
||||
def extend_rss(self, feed):
|
||||
'''Extend a RSS feed xml structure containing all previously set fields.
|
||||
|
||||
:param feed: The feed xml root element.
|
||||
:returns: The feed root element.
|
||||
'''
|
||||
return feed
|
||||
:param feed: The feed xml root element.
|
||||
:returns: The feed root element.
|
||||
'''
|
||||
return feed
|
||||
|
||||
def extend_atom(self, feed):
|
||||
'''Extend an ATOM feed xml structure containing all previously set
|
||||
fields.
|
||||
|
||||
def extend_atom(self, feed):
|
||||
'''Extend an ATOM feed xml structure containing all previously set
|
||||
fields.
|
||||
|
||||
:param feed: The feed xml root element.
|
||||
:returns: The feed root element.
|
||||
'''
|
||||
return feed
|
||||
:param feed: The feed xml root element.
|
||||
:returns: The feed root element.
|
||||
'''
|
||||
return feed
|
||||
|
||||
|
||||
class BaseEntryExtension(BaseExtension):
|
||||
'''Basic FeedEntry extension.
|
||||
'''
|
||||
'''Basic FeedEntry extension.
|
||||
'''
|
||||
|
|
|
@ -1,419 +1,406 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
'''
|
||||
feedgen.ext.dc
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
feedgen.ext.dc
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Extends the FeedGenerator to add Dubline Core Elements to the feeds.
|
||||
Extends the FeedGenerator to add Dubline Core Elements to the feeds.
|
||||
|
||||
Descriptions partly taken from
|
||||
http://dublincore.org/documents/dcmi-terms/#elements-coverage
|
||||
Descriptions partly taken from
|
||||
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 feedgen.ext.base import BaseExtension, BaseEntryExtension
|
||||
from feedgen.ext.base import BaseExtension
|
||||
|
||||
|
||||
class DcBaseExtension(BaseExtension):
|
||||
'''Dublin Core Elements extension for podcasts.
|
||||
'''
|
||||
|
||||
|
||||
def __init__(self):
|
||||
# http://dublincore.org/documents/usageguide/elements.shtml
|
||||
# http://dublincore.org/documents/dces/
|
||||
# http://dublincore.org/documents/dcmi-terms/
|
||||
self._dcelem_contributor = None
|
||||
self._dcelem_coverage = None
|
||||
self._dcelem_creator = None
|
||||
self._dcelem_date = None
|
||||
self._dcelem_description = None
|
||||
self._dcelem_format = None
|
||||
self._dcelem_identifier = None
|
||||
self._dcelem_language = None
|
||||
self._dcelem_publisher = None
|
||||
self._dcelem_relation = None
|
||||
self._dcelem_rights = None
|
||||
self._dcelem_source = None
|
||||
self._dcelem_subject = None
|
||||
self._dcelem_title = None
|
||||
self._dcelem_type = None
|
||||
|
||||
def extend_ns(self):
|
||||
return {'dc' : 'http://purl.org/dc/elements/1.1/'}
|
||||
|
||||
def _extend_xml(self, xml_elem):
|
||||
'''Extend xml_elem with set DC fields.
|
||||
|
||||
:param xml_elem: etree element
|
||||
'''
|
||||
DCELEMENTS_NS = 'http://purl.org/dc/elements/1.1/'
|
||||
|
||||
for elem in ['contributor', 'coverage', 'creator', 'date', 'description',
|
||||
'language', 'publisher', 'relation', 'rights', 'source', 'subject',
|
||||
'title', 'type', 'format', 'identifier']:
|
||||
if hasattr(self, '_dcelem_%s' % elem):
|
||||
for val in getattr(self, '_dcelem_%s' % elem) or []:
|
||||
node = etree.SubElement(xml_elem, '{%s}%s' % (DCELEMENTS_NS, elem))
|
||||
node.text = val
|
||||
|
||||
|
||||
def extend_atom(self, atom_feed):
|
||||
'''Extend an Atom feed with the set DC fields.
|
||||
|
||||
:param atom_feed: The feed root element
|
||||
:returns: The feed root element
|
||||
'''
|
||||
|
||||
self._extend_xml(atom_feed)
|
||||
|
||||
return atom_feed
|
||||
|
||||
|
||||
|
||||
def extend_rss(self, rss_feed):
|
||||
'''Extend a RSS feed with the set DC fields.
|
||||
|
||||
:param rss_feed: The feed root element
|
||||
:returns: The feed root element.
|
||||
'''
|
||||
channel = rss_feed[0]
|
||||
self._extend_xml(channel)
|
||||
|
||||
return rss_feed
|
||||
|
||||
|
||||
def dc_contributor(self, contributor=None, replace=False):
|
||||
'''Get or set the dc:contributor which is an entity responsible for
|
||||
making contributions to the resource.
|
||||
|
||||
For more information see:
|
||||
http://dublincore.org/documents/dcmi-terms/#elements-contributor
|
||||
|
||||
:param contributor: Contributor or list of contributors.
|
||||
:param replace: Replace alredy set contributors (deault: False).
|
||||
:returns: List of contributors.
|
||||
'''
|
||||
if not contributor is None:
|
||||
if not isinstance(contributor, list):
|
||||
contributor = [contributor]
|
||||
if replace or not self._dcelem_contributor:
|
||||
self._dcelem_contributor = []
|
||||
self._dcelem_contributor += contributor
|
||||
return self._dcelem_contributor
|
||||
|
||||
|
||||
def dc_coverage(self, coverage=None, replace=True):
|
||||
'''Get or set the dc:coverage which indicated the spatial or temporal
|
||||
topic of the resource, the spatial applicability of the resource, or the
|
||||
jurisdiction under which the resource is relevant.
|
||||
|
||||
Spatial topic and spatial applicability may be a named place or a
|
||||
location specified by its geographic coordinates. Temporal topic may be a
|
||||
named period, date, or date range. A jurisdiction may be a named
|
||||
administrative entity or a geographic place to which the resource
|
||||
applies. Recommended best practice is to use a controlled vocabulary such
|
||||
as the Thesaurus of Geographic Names [TGN]. Where appropriate, named
|
||||
places or time periods can be used in preference to numeric identifiers
|
||||
such as sets of coordinates or date ranges.
|
||||
|
||||
References: [TGN] http://www.getty.edu/research/tools/vocabulary/tgn/index.html
|
||||
|
||||
:param coverage: Coverage of the feed.
|
||||
:param replace: Replace already set coverage (default: True).
|
||||
:returns: Coverage of the feed.
|
||||
'''
|
||||
if not coverage is None:
|
||||
if not isinstance(coverage, list):
|
||||
coverage = [coverage]
|
||||
if replace or not self._dcelem_coverage:
|
||||
self._dcelem_coverage = []
|
||||
self._dcelem_coverage = coverage
|
||||
return self._dcelem_coverage
|
||||
|
||||
|
||||
def dc_creator(self, creator=None, replace=False):
|
||||
'''Get or set the dc:creator which is an entity primarily responsible for
|
||||
making the resource.
|
||||
|
||||
For more information see:
|
||||
http://dublincore.org/documents/dcmi-terms/#elements-creator
|
||||
|
||||
:param creator: Creator or list of creators.
|
||||
:param replace: Replace alredy set creators (deault: False).
|
||||
:returns: List of creators.
|
||||
'''
|
||||
if not creator is None:
|
||||
if not isinstance(creator, list):
|
||||
creator = [creator]
|
||||
if replace or not self._dcelem_creator:
|
||||
self._dcelem_creator = []
|
||||
self._dcelem_creator += creator
|
||||
return self._dcelem_creator
|
||||
|
||||
|
||||
def dc_date(self, date=None, replace=True):
|
||||
'''Get or set the dc:date which describes a point or period of time
|
||||
associated with an event in the lifecycle of the resource.
|
||||
|
||||
For more information see:
|
||||
http://dublincore.org/documents/dcmi-terms/#elements-date
|
||||
|
||||
:param date: Date or list of dates.
|
||||
:param replace: Replace alredy set dates (deault: True).
|
||||
:returns: List of dates.
|
||||
'''
|
||||
if not date is None:
|
||||
if not isinstance(date, list):
|
||||
date = [date]
|
||||
if replace or not self._dcelem_date:
|
||||
self._dcelem_date = []
|
||||
self._dcelem_date += date
|
||||
return self._dcelem_date
|
||||
|
||||
|
||||
def dc_description(self, description=None, replace=True):
|
||||
'''Get or set the dc:description which is an account of the resource.
|
||||
|
||||
For more information see:
|
||||
http://dublincore.org/documents/dcmi-terms/#elements-description
|
||||
|
||||
:param description: Description or list of descriptions.
|
||||
:param replace: Replace alredy set descriptions (deault: True).
|
||||
:returns: List of descriptions.
|
||||
'''
|
||||
if not description is None:
|
||||
if not isinstance(description, list):
|
||||
description = [description]
|
||||
if replace or not self._dcelem_description:
|
||||
self._dcelem_description = []
|
||||
self._dcelem_description += description
|
||||
return self._dcelem_description
|
||||
|
||||
|
||||
def dc_format(self, format=None, replace=True):
|
||||
'''Get or set the dc:format which describes the file format, physical
|
||||
medium, or dimensions of the resource.
|
||||
|
||||
For more information see:
|
||||
http://dublincore.org/documents/dcmi-terms/#elements-format
|
||||
|
||||
:param format: Format of the resource or list of formats.
|
||||
:param replace: Replace alredy set format (deault: True).
|
||||
:returns: Format of the resource.
|
||||
'''
|
||||
if not format is None:
|
||||
if not isinstance(format, list):
|
||||
format = [format]
|
||||
if replace or not self._dcelem_format:
|
||||
self._dcelem_format = []
|
||||
self._dcelem_format += format
|
||||
return self._dcelem_format
|
||||
|
||||
|
||||
def dc_identifier(self, identifier=None, replace=True):
|
||||
'''Get or set the dc:identifier which should be an unambiguous reference
|
||||
to the resource within a given context.
|
||||
|
||||
For more inidentifierion see:
|
||||
http://dublincore.org/documents/dcmi-terms/#elements-identifier
|
||||
|
||||
:param identifier: Identifier of the resource or list of identifiers.
|
||||
:param replace: Replace alredy set identifier (deault: True).
|
||||
:returns: Identifiers of the resource.
|
||||
'''
|
||||
if not identifier is None:
|
||||
if not isinstance(identifier, list):
|
||||
identifier = [identifier]
|
||||
if replace or not self._dcelem_identifier:
|
||||
self._dcelem_identifier = []
|
||||
self._dcelem_identifier += identifier
|
||||
|
||||
|
||||
def dc_language(self, language=None, replace=True):
|
||||
'''Get or set the dc:language which describes a language of the resource.
|
||||
|
||||
For more information see:
|
||||
http://dublincore.org/documents/dcmi-terms/#elements-language
|
||||
|
||||
:param language: Language or list of languages.
|
||||
:param replace: Replace alredy set languages (deault: True).
|
||||
:returns: List of languages.
|
||||
'''
|
||||
if not language is None:
|
||||
if not isinstance(language, list):
|
||||
language = [language]
|
||||
if replace or not self._dcelem_language:
|
||||
self._dcelem_language = []
|
||||
self._dcelem_language += language
|
||||
return self._dcelem_language
|
||||
|
||||
|
||||
def dc_publisher(self, publisher=None, replace=False):
|
||||
'''Get or set the dc:publisher which is an entity responsible for making
|
||||
the resource available.
|
||||
|
||||
For more information see:
|
||||
http://dublincore.org/documents/dcmi-terms/#elements-publisher
|
||||
|
||||
:param publisher: Publisher or list of publishers.
|
||||
:param replace: Replace alredy set publishers (deault: False).
|
||||
:returns: List of publishers.
|
||||
'''
|
||||
if not publisher is None:
|
||||
if not isinstance(publisher, list):
|
||||
publisher = [publisher]
|
||||
if replace or not self._dcelem_publisher:
|
||||
self._dcelem_publisher = []
|
||||
self._dcelem_publisher += publisher
|
||||
return self._dcelem_publisher
|
||||
|
||||
|
||||
def dc_relation(self, relation=None, replace=False):
|
||||
'''Get or set the dc:relation which describes a related ressource.
|
||||
|
||||
For more information see:
|
||||
http://dublincore.org/documents/dcmi-terms/#elements-relation
|
||||
|
||||
:param relation: Relation or list of relations.
|
||||
:param replace: Replace alredy set relations (deault: False).
|
||||
:returns: List of relations.
|
||||
'''
|
||||
if not relation is None:
|
||||
if not isinstance(relation, list):
|
||||
relation = [relation]
|
||||
if replace or not self._dcelem_relation:
|
||||
self._dcelem_relation = []
|
||||
self._dcelem_relation += relation
|
||||
return self._dcelem_relation
|
||||
|
||||
|
||||
def dc_rights(self, rights=None, replace=False):
|
||||
'''Get or set the dc:rights which may contain information about rights
|
||||
held in and over the resource.
|
||||
|
||||
For more information see:
|
||||
http://dublincore.org/documents/dcmi-terms/#elements-rights
|
||||
|
||||
:param rights: Rights information or list of rights information.
|
||||
:param replace: Replace alredy set rightss (deault: False).
|
||||
:returns: List of rights information.
|
||||
'''
|
||||
if not rights is None:
|
||||
if not isinstance(rights, list):
|
||||
rights = [rights]
|
||||
if replace or not self._dcelem_rights:
|
||||
self._dcelem_rights = []
|
||||
self._dcelem_rights += rights
|
||||
return self._dcelem_rights
|
||||
|
||||
|
||||
def dc_source(self, source=None, replace=False):
|
||||
'''Get or set the dc:source which is a related resource from which the
|
||||
described resource is derived.
|
||||
|
||||
The described resource may be derived from the related resource in whole
|
||||
or in part. Recommended best practice is to identify the related resource
|
||||
by means of a string conforming to a formal identification system.
|
||||
|
||||
|
||||
For more information see:
|
||||
http://dublincore.org/documents/dcmi-terms/#elements-source
|
||||
|
||||
:param source: Source or list of sources.
|
||||
:param replace: Replace alredy set sources (deault: False).
|
||||
:returns: List of sources.
|
||||
'''
|
||||
if not source is None:
|
||||
if not isinstance(source, list):
|
||||
source = [source]
|
||||
if replace or not self._dcelem_source:
|
||||
self._dcelem_source = []
|
||||
self._dcelem_source += source
|
||||
return self._dcelem_source
|
||||
|
||||
|
||||
def dc_subject(self, subject=None, replace=False):
|
||||
'''Get or set the dc:subject which describes the topic of the resource.
|
||||
|
||||
For more information see:
|
||||
http://dublincore.org/documents/dcmi-terms/#elements-subject
|
||||
|
||||
:param subject: Subject or list of subjects.
|
||||
:param replace: Replace alredy set subjects (deault: False).
|
||||
:returns: List of subjects.
|
||||
'''
|
||||
if not subject is None:
|
||||
if not isinstance(subject, list):
|
||||
subject = [subject]
|
||||
if replace or not self._dcelem_subject:
|
||||
self._dcelem_subject = []
|
||||
self._dcelem_subject += subject
|
||||
return self._dcelem_subject
|
||||
|
||||
|
||||
def dc_title(self, title=None, replace=True):
|
||||
'''Get or set the dc:title which is a name given to the resource.
|
||||
|
||||
For more information see:
|
||||
http://dublincore.org/documents/dcmi-terms/#elements-title
|
||||
|
||||
:param title: Title or list of titles.
|
||||
:param replace: Replace alredy set titles (deault: False).
|
||||
:returns: List of titles.
|
||||
'''
|
||||
if not title is None:
|
||||
if not isinstance(title, list):
|
||||
title = [title]
|
||||
if replace or not self._dcelem_title:
|
||||
self._dcelem_title = []
|
||||
self._dcelem_title += title
|
||||
return self._dcelem_title
|
||||
|
||||
|
||||
def dc_type(self, type=None, replace=False):
|
||||
'''Get or set the dc:type which describes the nature or genre of the
|
||||
resource.
|
||||
|
||||
For more information see:
|
||||
http://dublincore.org/documents/dcmi-terms/#elements-type
|
||||
|
||||
:param type: Type or list of types.
|
||||
:param replace: Replace alredy set types (deault: False).
|
||||
:returns: List of types.
|
||||
'''
|
||||
if not type is None:
|
||||
if not isinstance(type, list):
|
||||
type = [type]
|
||||
if replace or not self._dcelem_type:
|
||||
self._dcelem_type = []
|
||||
self._dcelem_type += type
|
||||
return self._dcelem_type
|
||||
'''Dublin Core Elements extension for podcasts.
|
||||
'''
|
||||
|
||||
def __init__(self):
|
||||
# http://dublincore.org/documents/usageguide/elements.shtml
|
||||
# http://dublincore.org/documents/dces/
|
||||
# http://dublincore.org/documents/dcmi-terms/
|
||||
self._dcelem_contributor = None
|
||||
self._dcelem_coverage = None
|
||||
self._dcelem_creator = None
|
||||
self._dcelem_date = None
|
||||
self._dcelem_description = None
|
||||
self._dcelem_format = None
|
||||
self._dcelem_identifier = None
|
||||
self._dcelem_language = None
|
||||
self._dcelem_publisher = None
|
||||
self._dcelem_relation = None
|
||||
self._dcelem_rights = None
|
||||
self._dcelem_source = None
|
||||
self._dcelem_subject = None
|
||||
self._dcelem_title = None
|
||||
self._dcelem_type = None
|
||||
|
||||
def extend_ns(self):
|
||||
return {'dc': 'http://purl.org/dc/elements/1.1/'}
|
||||
|
||||
def _extend_xml(self, xml_elem):
|
||||
'''Extend xml_elem with set DC fields.
|
||||
|
||||
:param xml_elem: etree element
|
||||
'''
|
||||
DCELEMENTS_NS = 'http://purl.org/dc/elements/1.1/'
|
||||
|
||||
for elem in ['contributor', 'coverage', 'creator', 'date',
|
||||
'description', 'language', 'publisher', 'relation',
|
||||
'rights', 'source', 'subject', 'title', 'type', 'format',
|
||||
'identifier']:
|
||||
if hasattr(self, '_dcelem_%s' % elem):
|
||||
for val in getattr(self, '_dcelem_%s' % elem) or []:
|
||||
node = etree.SubElement(xml_elem,
|
||||
'{%s}%s' % (DCELEMENTS_NS, elem))
|
||||
node.text = val
|
||||
|
||||
def extend_atom(self, atom_feed):
|
||||
'''Extend an Atom feed with the set DC fields.
|
||||
|
||||
:param atom_feed: The feed root element
|
||||
:returns: The feed root element
|
||||
'''
|
||||
|
||||
self._extend_xml(atom_feed)
|
||||
|
||||
return atom_feed
|
||||
|
||||
def extend_rss(self, rss_feed):
|
||||
'''Extend a RSS feed with the set DC fields.
|
||||
|
||||
:param rss_feed: The feed root element
|
||||
:returns: The feed root element.
|
||||
'''
|
||||
channel = rss_feed[0]
|
||||
self._extend_xml(channel)
|
||||
|
||||
return rss_feed
|
||||
|
||||
def dc_contributor(self, contributor=None, replace=False):
|
||||
'''Get or set the dc:contributor which is an entity responsible for
|
||||
making contributions to the resource.
|
||||
|
||||
For more information see:
|
||||
http://dublincore.org/documents/dcmi-terms/#elements-contributor
|
||||
|
||||
:param contributor: Contributor or list of contributors.
|
||||
:param replace: Replace alredy set contributors (deault: False).
|
||||
:returns: List of contributors.
|
||||
'''
|
||||
if contributor is not None:
|
||||
if not isinstance(contributor, list):
|
||||
contributor = [contributor]
|
||||
if replace or not self._dcelem_contributor:
|
||||
self._dcelem_contributor = []
|
||||
self._dcelem_contributor += contributor
|
||||
return self._dcelem_contributor
|
||||
|
||||
def dc_coverage(self, coverage=None, replace=True):
|
||||
'''Get or set the dc:coverage which indicated the spatial or temporal
|
||||
topic of the resource, the spatial applicability of the resource, or
|
||||
the jurisdiction under which the resource is relevant.
|
||||
|
||||
Spatial topic and spatial applicability may be a named place or a
|
||||
location specified by its geographic coordinates. Temporal topic may be
|
||||
a named period, date, or date range. A jurisdiction may be a named
|
||||
administrative entity or a geographic place to which the resource
|
||||
applies. Recommended best practice is to use a controlled vocabulary
|
||||
such as the Thesaurus of Geographic Names [TGN]. Where appropriate,
|
||||
named places or time periods can be used in preference to numeric
|
||||
identifiers such as sets of coordinates or date ranges.
|
||||
|
||||
References:
|
||||
[TGN] http://www.getty.edu/research/tools/vocabulary/tgn/index.html
|
||||
|
||||
:param coverage: Coverage of the feed.
|
||||
:param replace: Replace already set coverage (default: True).
|
||||
:returns: Coverage of the feed.
|
||||
'''
|
||||
if coverage is not None:
|
||||
if not isinstance(coverage, list):
|
||||
coverage = [coverage]
|
||||
if replace or not self._dcelem_coverage:
|
||||
self._dcelem_coverage = []
|
||||
self._dcelem_coverage = coverage
|
||||
return self._dcelem_coverage
|
||||
|
||||
def dc_creator(self, creator=None, replace=False):
|
||||
'''Get or set the dc:creator which is an entity primarily responsible
|
||||
for making the resource.
|
||||
|
||||
For more information see:
|
||||
http://dublincore.org/documents/dcmi-terms/#elements-creator
|
||||
|
||||
:param creator: Creator or list of creators.
|
||||
:param replace: Replace alredy set creators (deault: False).
|
||||
:returns: List of creators.
|
||||
'''
|
||||
if creator is not None:
|
||||
if not isinstance(creator, list):
|
||||
creator = [creator]
|
||||
if replace or not self._dcelem_creator:
|
||||
self._dcelem_creator = []
|
||||
self._dcelem_creator += creator
|
||||
return self._dcelem_creator
|
||||
|
||||
def dc_date(self, date=None, replace=True):
|
||||
'''Get or set the dc:date which describes a point or period of time
|
||||
associated with an event in the lifecycle of the resource.
|
||||
|
||||
For more information see:
|
||||
http://dublincore.org/documents/dcmi-terms/#elements-date
|
||||
|
||||
:param date: Date or list of dates.
|
||||
:param replace: Replace alredy set dates (deault: True).
|
||||
:returns: List of dates.
|
||||
'''
|
||||
if date is not None:
|
||||
if not isinstance(date, list):
|
||||
date = [date]
|
||||
if replace or not self._dcelem_date:
|
||||
self._dcelem_date = []
|
||||
self._dcelem_date += date
|
||||
return self._dcelem_date
|
||||
|
||||
def dc_description(self, description=None, replace=True):
|
||||
'''Get or set the dc:description which is an account of the resource.
|
||||
|
||||
For more information see:
|
||||
http://dublincore.org/documents/dcmi-terms/#elements-description
|
||||
|
||||
:param description: Description or list of descriptions.
|
||||
:param replace: Replace alredy set descriptions (deault: True).
|
||||
:returns: List of descriptions.
|
||||
'''
|
||||
if description is not None:
|
||||
if not isinstance(description, list):
|
||||
description = [description]
|
||||
if replace or not self._dcelem_description:
|
||||
self._dcelem_description = []
|
||||
self._dcelem_description += description
|
||||
return self._dcelem_description
|
||||
|
||||
def dc_format(self, format=None, replace=True):
|
||||
'''Get or set the dc:format which describes the file format, physical
|
||||
medium, or dimensions of the resource.
|
||||
|
||||
For more information see:
|
||||
http://dublincore.org/documents/dcmi-terms/#elements-format
|
||||
|
||||
:param format: Format of the resource or list of formats.
|
||||
:param replace: Replace alredy set format (deault: True).
|
||||
:returns: Format of the resource.
|
||||
'''
|
||||
if format is not None:
|
||||
if not isinstance(format, list):
|
||||
format = [format]
|
||||
if replace or not self._dcelem_format:
|
||||
self._dcelem_format = []
|
||||
self._dcelem_format += format
|
||||
return self._dcelem_format
|
||||
|
||||
def dc_identifier(self, identifier=None, replace=True):
|
||||
'''Get or set the dc:identifier which should be an unambiguous
|
||||
reference to the resource within a given context.
|
||||
|
||||
For more inidentifierion see:
|
||||
http://dublincore.org/documents/dcmi-terms/#elements-identifier
|
||||
|
||||
:param identifier: Identifier of the resource or list of identifiers.
|
||||
:param replace: Replace alredy set identifier (deault: True).
|
||||
:returns: Identifiers of the resource.
|
||||
'''
|
||||
if identifier is not None:
|
||||
if not isinstance(identifier, list):
|
||||
identifier = [identifier]
|
||||
if replace or not self._dcelem_identifier:
|
||||
self._dcelem_identifier = []
|
||||
self._dcelem_identifier += identifier
|
||||
|
||||
def dc_language(self, language=None, replace=True):
|
||||
'''Get or set the dc:language which describes a language of the
|
||||
resource.
|
||||
|
||||
For more information see:
|
||||
http://dublincore.org/documents/dcmi-terms/#elements-language
|
||||
|
||||
:param language: Language or list of languages.
|
||||
:param replace: Replace alredy set languages (deault: True).
|
||||
:returns: List of languages.
|
||||
'''
|
||||
if language is not None:
|
||||
if not isinstance(language, list):
|
||||
language = [language]
|
||||
if replace or not self._dcelem_language:
|
||||
self._dcelem_language = []
|
||||
self._dcelem_language += language
|
||||
return self._dcelem_language
|
||||
|
||||
def dc_publisher(self, publisher=None, replace=False):
|
||||
'''Get or set the dc:publisher which is an entity responsible for
|
||||
making the resource available.
|
||||
|
||||
For more information see:
|
||||
http://dublincore.org/documents/dcmi-terms/#elements-publisher
|
||||
|
||||
:param publisher: Publisher or list of publishers.
|
||||
:param replace: Replace alredy set publishers (deault: False).
|
||||
:returns: List of publishers.
|
||||
'''
|
||||
if publisher is not None:
|
||||
if not isinstance(publisher, list):
|
||||
publisher = [publisher]
|
||||
if replace or not self._dcelem_publisher:
|
||||
self._dcelem_publisher = []
|
||||
self._dcelem_publisher += publisher
|
||||
return self._dcelem_publisher
|
||||
|
||||
def dc_relation(self, relation=None, replace=False):
|
||||
'''Get or set the dc:relation which describes a related ressource.
|
||||
|
||||
For more information see:
|
||||
http://dublincore.org/documents/dcmi-terms/#elements-relation
|
||||
|
||||
:param relation: Relation or list of relations.
|
||||
:param replace: Replace alredy set relations (deault: False).
|
||||
:returns: List of relations.
|
||||
'''
|
||||
if relation is not None:
|
||||
if not isinstance(relation, list):
|
||||
relation = [relation]
|
||||
if replace or not self._dcelem_relation:
|
||||
self._dcelem_relation = []
|
||||
self._dcelem_relation += relation
|
||||
return self._dcelem_relation
|
||||
|
||||
def dc_rights(self, rights=None, replace=False):
|
||||
'''Get or set the dc:rights which may contain information about rights
|
||||
held in and over the resource.
|
||||
|
||||
For more information see:
|
||||
http://dublincore.org/documents/dcmi-terms/#elements-rights
|
||||
|
||||
:param rights: Rights information or list of rights information.
|
||||
:param replace: Replace alredy set rightss (deault: False).
|
||||
:returns: List of rights information.
|
||||
'''
|
||||
if rights is not None:
|
||||
if not isinstance(rights, list):
|
||||
rights = [rights]
|
||||
if replace or not self._dcelem_rights:
|
||||
self._dcelem_rights = []
|
||||
self._dcelem_rights += rights
|
||||
return self._dcelem_rights
|
||||
|
||||
def dc_source(self, source=None, replace=False):
|
||||
'''Get or set the dc:source which is a related resource from which the
|
||||
described resource is derived.
|
||||
|
||||
The described resource may be derived from the related resource in
|
||||
whole or in part. Recommended best practice is to identify the related
|
||||
resource by means of a string conforming to a formal identification
|
||||
system.
|
||||
|
||||
For more information see:
|
||||
http://dublincore.org/documents/dcmi-terms/#elements-source
|
||||
|
||||
:param source: Source or list of sources.
|
||||
:param replace: Replace alredy set sources (deault: False).
|
||||
:returns: List of sources.
|
||||
'''
|
||||
if source is not None:
|
||||
if not isinstance(source, list):
|
||||
source = [source]
|
||||
if replace or not self._dcelem_source:
|
||||
self._dcelem_source = []
|
||||
self._dcelem_source += source
|
||||
return self._dcelem_source
|
||||
|
||||
def dc_subject(self, subject=None, replace=False):
|
||||
'''Get or set the dc:subject which describes the topic of the resource.
|
||||
|
||||
For more information see:
|
||||
http://dublincore.org/documents/dcmi-terms/#elements-subject
|
||||
|
||||
:param subject: Subject or list of subjects.
|
||||
:param replace: Replace alredy set subjects (deault: False).
|
||||
:returns: List of subjects.
|
||||
'''
|
||||
if subject is not None:
|
||||
if not isinstance(subject, list):
|
||||
subject = [subject]
|
||||
if replace or not self._dcelem_subject:
|
||||
self._dcelem_subject = []
|
||||
self._dcelem_subject += subject
|
||||
return self._dcelem_subject
|
||||
|
||||
def dc_title(self, title=None, replace=True):
|
||||
'''Get or set the dc:title which is a name given to the resource.
|
||||
|
||||
For more information see:
|
||||
http://dublincore.org/documents/dcmi-terms/#elements-title
|
||||
|
||||
:param title: Title or list of titles.
|
||||
:param replace: Replace alredy set titles (deault: False).
|
||||
:returns: List of titles.
|
||||
'''
|
||||
if title is not None:
|
||||
if not isinstance(title, list):
|
||||
title = [title]
|
||||
if replace or not self._dcelem_title:
|
||||
self._dcelem_title = []
|
||||
self._dcelem_title += title
|
||||
return self._dcelem_title
|
||||
|
||||
def dc_type(self, type=None, replace=False):
|
||||
'''Get or set the dc:type which describes the nature or genre of the
|
||||
resource.
|
||||
|
||||
For more information see:
|
||||
http://dublincore.org/documents/dcmi-terms/#elements-type
|
||||
|
||||
:param type: Type or list of types.
|
||||
:param replace: Replace alredy set types (deault: False).
|
||||
:returns: List of types.
|
||||
'''
|
||||
if type is not None:
|
||||
if not isinstance(type, list):
|
||||
type = [type]
|
||||
if replace or not self._dcelem_type:
|
||||
self._dcelem_type = []
|
||||
self._dcelem_type += type
|
||||
return self._dcelem_type
|
||||
|
||||
|
||||
class DcExtension(DcBaseExtension):
|
||||
'''Dublin Core Elements extension for podcasts.
|
||||
'''
|
||||
'''Dublin Core Elements extension for podcasts.
|
||||
'''
|
||||
|
||||
|
||||
class DcEntryExtension(DcBaseExtension):
|
||||
'''Dublin Core Elements extension for podcasts.
|
||||
'''
|
||||
def extend_atom(self, entry):
|
||||
'''Add dc elements to an atom item. Alters the item itself.
|
||||
'''Dublin Core Elements extension for podcasts.
|
||||
'''
|
||||
def extend_atom(self, entry):
|
||||
'''Add dc elements to an atom item. Alters the item itself.
|
||||
|
||||
:param entry: An atom entry element.
|
||||
:returns: The entry element.
|
||||
'''
|
||||
self._extend_xml(entry)
|
||||
return entry
|
||||
:param entry: An atom entry element.
|
||||
:returns: The entry element.
|
||||
'''
|
||||
self._extend_xml(entry)
|
||||
return entry
|
||||
|
||||
def extend_rss(self, item):
|
||||
'''Add dc elements to a RSS item. Alters the item itself.
|
||||
def extend_rss(self, item):
|
||||
'''Add dc elements to a RSS item. Alters the item itself.
|
||||
|
||||
:param item: A RSS item element.
|
||||
:returns: The item element.
|
||||
'''
|
||||
self._extend_xml(item)
|
||||
return item
|
||||
:param item: A RSS item element.
|
||||
:returns: The item element.
|
||||
'''
|
||||
self._extend_xml(item)
|
||||
return item
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
'''
|
||||
feedgen.ext.podcast
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
feedgen.ext.podcast
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Extends the FeedGenerator to produce podcasts.
|
||||
Extends the FeedGenerator to produce podcasts.
|
||||
|
||||
: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.
|
||||
'''
|
||||
|
||||
from lxml import etree
|
||||
|
@ -17,343 +17,343 @@ from feedgen.compat import string_types
|
|||
|
||||
|
||||
class PodcastExtension(BaseExtension):
|
||||
'''FeedGenerator extension for podcasts.
|
||||
'''
|
||||
|
||||
|
||||
def __init__(self):
|
||||
## ITunes tags
|
||||
# http://www.apple.com/itunes/podcasts/specs.html#rss
|
||||
self.__itunes_author = None
|
||||
self.__itunes_block = None
|
||||
self.__itunes_category = None
|
||||
self.__itunes_image = None
|
||||
self.__itunes_explicit = None
|
||||
self.__itunes_complete = None
|
||||
self.__itunes_new_feed_url = None
|
||||
self.__itunes_owner = None
|
||||
self.__itunes_subtitle = None
|
||||
self.__itunes_summary = None
|
||||
|
||||
|
||||
def extend_ns(self):
|
||||
return {'itunes' : 'http://www.itunes.com/dtds/podcast-1.0.dtd'}
|
||||
|
||||
|
||||
def extend_rss(self, rss_feed):
|
||||
'''Extend an RSS feed root with set itunes fields.
|
||||
|
||||
:returns: The feed root element.
|
||||
'''
|
||||
ITUNES_NS = 'http://www.itunes.com/dtds/podcast-1.0.dtd'
|
||||
channel = rss_feed[0]
|
||||
|
||||
if self.__itunes_author:
|
||||
author = etree.SubElement(channel, '{%s}author' % ITUNES_NS)
|
||||
author.text = self.__itunes_author
|
||||
|
||||
if not self.__itunes_block is None:
|
||||
block = etree.SubElement(channel, '{%s}block' % ITUNES_NS)
|
||||
block.text = 'yes' if self.__itunes_block else 'no'
|
||||
|
||||
for c in self.__itunes_category or []:
|
||||
if not c.get('cat'):
|
||||
continue
|
||||
category = channel.find('{%s}category[@text="%s"]' % (ITUNES_NS,c.get('cat')))
|
||||
if category == None:
|
||||
category = etree.SubElement(channel, '{%s}category' % ITUNES_NS)
|
||||
category.attrib['text'] = c.get('cat')
|
||||
|
||||
if c.get('sub'):
|
||||
subcategory = etree.SubElement(category, '{%s}category' % ITUNES_NS)
|
||||
subcategory.attrib['text'] = c.get('sub')
|
||||
|
||||
if self.__itunes_image:
|
||||
image = etree.SubElement(channel, '{%s}image' % ITUNES_NS)
|
||||
image.attrib['href'] = self.__itunes_image
|
||||
|
||||
if self.__itunes_explicit in ('yes', 'no', 'clean'):
|
||||
explicit = etree.SubElement(channel, '{%s}explicit' % ITUNES_NS)
|
||||
explicit.text = self.__itunes_explicit
|
||||
|
||||
if self.__itunes_complete in ('yes', 'no'):
|
||||
complete = etree.SubElement(channel, '{%s}complete' % ITUNES_NS)
|
||||
complete.text = self.__itunes_complete
|
||||
|
||||
if self.__itunes_new_feed_url:
|
||||
new_feed_url = etree.SubElement(channel, '{%s}new-feed-url' % ITUNES_NS)
|
||||
new_feed_url.text = self.__itunes_new_feed_url
|
||||
|
||||
if self.__itunes_owner:
|
||||
owner = etree.SubElement(channel, '{%s}owner' % ITUNES_NS)
|
||||
owner_name = etree.SubElement(owner, '{%s}name' % ITUNES_NS)
|
||||
owner_name.text = self.__itunes_owner.get('name')
|
||||
owner_email = etree.SubElement(owner, '{%s}email' % ITUNES_NS)
|
||||
owner_email.text = self.__itunes_owner.get('email')
|
||||
|
||||
if self.__itunes_subtitle:
|
||||
subtitle = etree.SubElement(channel, '{%s}subtitle' % ITUNES_NS)
|
||||
subtitle.text = self.__itunes_subtitle
|
||||
|
||||
if self.__itunes_summary:
|
||||
summary = etree.SubElement(channel, '{%s}summary' % ITUNES_NS)
|
||||
summary.text = self.__itunes_summary
|
||||
|
||||
return rss_feed
|
||||
|
||||
|
||||
def itunes_author(self, itunes_author=None):
|
||||
'''Get or set the itunes:author. The content of 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> is not present at the
|
||||
feed level, iTunes will use the contents of <managingEditor>.
|
||||
|
||||
:param itunes_author: The author of the podcast.
|
||||
:returns: The author of the podcast.
|
||||
'''
|
||||
if not itunes_author is None:
|
||||
self.__itunes_author = itunes_author
|
||||
return self.__itunes_author
|
||||
|
||||
|
||||
def itunes_block(self, itunes_block=None):
|
||||
'''Get or set the ITunes block attribute. Use this to prevent the entire
|
||||
podcast from appearing in the iTunes podcast directory.
|
||||
|
||||
:param itunes_block: Block the podcast.
|
||||
:returns: If the podcast is blocked.
|
||||
'''
|
||||
if not itunes_block is None:
|
||||
self.__itunes_block = itunes_block
|
||||
return self.__itunes_block
|
||||
|
||||
def itunes_category(self, itunes_category=None, replace=False, **kwargs):
|
||||
'''Get or set the ITunes category which appears in the category column
|
||||
and in iTunes Store Browser.
|
||||
|
||||
The (sub-)category has to be one from the values defined at
|
||||
http://www.apple.com/itunes/podcasts/specs.html#categories
|
||||
|
||||
This method can be called with:
|
||||
|
||||
- the fields of an itunes_category as keyword arguments
|
||||
- the fields of an itunes_category as a dictionary
|
||||
- a list of dictionaries containing the itunes_category fields
|
||||
|
||||
An itunes_category has the following fields:
|
||||
|
||||
- *cat* name for a category.
|
||||
- *sub* name for a subcategory, child of category
|
||||
|
||||
If a podcast has more than one subcategory from the same category, the
|
||||
category is called more than once.
|
||||
|
||||
Likei the parameter::
|
||||
|
||||
[{"cat":"Arts","sub":"Design"},{"cat":"Arts","sub":"Food"}]
|
||||
|
||||
…would become::
|
||||
|
||||
<itunes:category text="Arts">
|
||||
<itunes:category text="Design"/>
|
||||
<itunes:category text="Food"/>
|
||||
</itunes:category>
|
||||
|
||||
|
||||
:param itunes_category: Dictionary or list of dictionaries with
|
||||
itunes_category data.
|
||||
:param replace: Add or replace old data.
|
||||
:returns: List of itunes_categories as dictionaries.
|
||||
|
||||
---
|
||||
|
||||
**Important note about deprecated parameter syntax:** Old version of the
|
||||
feedgen did only support one category plus one subcategory which would be
|
||||
passed to this ducntion as first two parameters. For compatibility
|
||||
reasons, this still works but should not be used any may be removed at
|
||||
any time.
|
||||
'''
|
||||
# Ensure old API still works for now. Note that the API is deprecated and
|
||||
# this fallback may be removed at any time.
|
||||
if isinstance(itunes_category, string_types):
|
||||
itunes_category = {'cat':itunes_category}
|
||||
if replace:
|
||||
itunes_category['sub'] = replace
|
||||
replace=True
|
||||
if itunes_category is None and kwargs:
|
||||
itunes_category = kwargs
|
||||
if not itunes_category is None:
|
||||
if replace or self.__itunes_category is None:
|
||||
self.__itunes_category = []
|
||||
self.__itunes_category += ensure_format( itunes_category,
|
||||
set(['cat', 'sub']), set(['cat']))
|
||||
return self.__itunes_category
|
||||
|
||||
|
||||
def itunes_image(self, itunes_image=None):
|
||||
'''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
|
||||
prefers square .jpg images that are at least 1400x1400 pixels, which is
|
||||
different from what is specified for the standard RSS image tag. In order
|
||||
for a podcast to be eligible for an 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
|
||||
(CMYK is not supported). The URL must end in ".jpg" or ".png". If the
|
||||
<itunes:image> tag is not present, iTunes will use the contents of the
|
||||
RSS image tag.
|
||||
|
||||
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
|
||||
same. The server hosting your cover art image must allow HTTP head
|
||||
requests for iTS to be able to automatically update your cover art.
|
||||
|
||||
:param itunes_image: Image of the podcast.
|
||||
:returns: Image of the podcast.
|
||||
'''
|
||||
if not itunes_image is None:
|
||||
if not ( itunes_image.endswith('.jpg') or itunes_image.endswith('.png') ):
|
||||
ValueError('Image file must be png or jpg')
|
||||
self.__itunes_image = itunes_image
|
||||
return self.__itunes_image
|
||||
|
||||
|
||||
def itunes_explicit(self, itunes_explicit=None):
|
||||
'''Get or the the itunes:explicit value of the podcast. This tag should
|
||||
be used to indicate whether your podcast contains explicit material. The
|
||||
three values for this tag are "yes", "no", and "clean".
|
||||
|
||||
If you populate this tag with "yes", an "explicit" parental advisory
|
||||
graphic will appear next to your podcast artwork on the iTunes Store 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
|
||||
adult content is included anywhere in the episodes, and a "clean" graphic
|
||||
will appear. If the explicit tag is present and has any other value
|
||||
(e.g., "no"), you see no indicator — blank is the default advisory type.
|
||||
|
||||
:param itunes_explicit: If the podcast contains explicit material.
|
||||
:returns: If the podcast contains explicit material.
|
||||
'''
|
||||
if not itunes_explicit is None:
|
||||
if not itunes_explicit in ('', 'yes', 'no', 'clean'):
|
||||
raise ValueError('Invalid value for explicit tag')
|
||||
self.__itunes_explicit = itunes_explicit
|
||||
return self.__itunes_explicit
|
||||
|
||||
|
||||
def itunes_complete(self, itunes_complete=None):
|
||||
'''Get or set the itunes:complete value of the podcast. This tag can be
|
||||
used to indicate the completion of a podcast.
|
||||
|
||||
If you populate this tag with "yes", you are indicating that no more
|
||||
episodes will be added to the podcast. If the <itunes:complete> tag is
|
||||
present and has any other value (e.g. “no”), it will have no effect on
|
||||
the podcast.
|
||||
|
||||
:param itunes_complete: If the podcast is complete.
|
||||
:returns: If the podcast is complete.
|
||||
'''
|
||||
if not itunes_complete is None:
|
||||
if not itunes_complete in ('yes', 'no', '', True, False):
|
||||
raise ValueError('Invalid value for complete tag')
|
||||
if itunes_complete == True:
|
||||
itunes_complete = 'yes'
|
||||
if itunes_complete == False:
|
||||
itunes_complete = 'no'
|
||||
self.__itunes_complete = itunes_complete
|
||||
return self.__itunes_complete
|
||||
|
||||
|
||||
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
|
||||
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
|
||||
for 48 hours before retiring it. At that point, iTunes will have updated
|
||||
the directory with the new feed URL.
|
||||
|
||||
:param itunes_new_feed_url: New feed URL.
|
||||
:returns: New feed URL.
|
||||
'''
|
||||
if not itunes_new_feed_url is None:
|
||||
self.__itunes_new_feed_url = itunes_new_feed_url
|
||||
return self.__itunes_new_feed_url
|
||||
|
||||
|
||||
def itunes_owner(self, name=None, email=None):
|
||||
'''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
|
||||
communication specifically about the podcast. It will not be publicly
|
||||
displayed.
|
||||
|
||||
:param itunes_owner: The owner of the feed.
|
||||
:returns: Data of the owner of the feed.
|
||||
'''
|
||||
if not name is None:
|
||||
if name and email:
|
||||
self.__itunes_owner = {'name':name, 'email':email}
|
||||
elif not name and not email:
|
||||
self.__itunes_owner = None
|
||||
else:
|
||||
raise ValueError('Both name and email have to be set.')
|
||||
return self.__itunes_owner
|
||||
|
||||
|
||||
def itunes_subtitle(self, itunes_subtitle=None):
|
||||
'''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
|
||||
displays best if it is only a few words long.
|
||||
|
||||
:param itunes_subtitle: Subtitle of the podcast.
|
||||
:returns: Subtitle of the podcast.
|
||||
'''
|
||||
if not itunes_subtitle is None:
|
||||
self.__itunes_subtitle = itunes_subtitle
|
||||
return self.__itunes_subtitle
|
||||
|
||||
|
||||
def itunes_summary(self, itunes_summary=None):
|
||||
'''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"
|
||||
in the Description column is clicked. It also appears on the iTunes page
|
||||
for your podcast. This field can be up to 4000 characters. If
|
||||
<itunes:summary> is not included, the contents of the <description> tag
|
||||
are used.
|
||||
|
||||
:param itunes_summary: Summary of the podcast.
|
||||
:returns: Summary of the podcast.
|
||||
'''
|
||||
if not itunes_summary is None:
|
||||
self.__itunes_summary = itunes_summary
|
||||
return self.__itunes_summary
|
||||
|
||||
|
||||
_itunes_categories = {
|
||||
'Arts': [ 'Design', 'Fashion & Beauty', 'Food', 'Literature',
|
||||
'Performing Arts', 'Visual Arts' ],
|
||||
'Business' : [ 'Business News', 'Careers', 'Investing',
|
||||
'Management & Marketing', 'Shopping' ],
|
||||
'Comedy' : [],
|
||||
'Education' : [ 'Education', 'Education Technology',
|
||||
'Higher Education', 'K-12', 'Language Courses', 'Training' ],
|
||||
'Games & Hobbies' : [ 'Automotive', 'Aviation', 'Hobbies',
|
||||
'Other Games', 'Video Games' ],
|
||||
'Government & Organizations' : [ 'Local', 'National', 'Non-Profit',
|
||||
'Regional' ],
|
||||
'Health' : [ 'Alternative Health', 'Fitness & Nutrition', 'Self-Help',
|
||||
'Sexuality' ],
|
||||
'Kids & Family' : [],
|
||||
'Music' : [],
|
||||
'News & Politics' : [],
|
||||
'Religion & Spirituality' : [ 'Buddhism', 'Christianity', 'Hinduism',
|
||||
'Islam', 'Judaism', 'Other', 'Spirituality' ],
|
||||
'Science & Medicine' : [ 'Medicine', 'Natural Sciences',
|
||||
'Social Sciences' ],
|
||||
'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' : []
|
||||
}
|
||||
'''FeedGenerator extension for podcasts.
|
||||
'''
|
||||
|
||||
def __init__(self):
|
||||
# ITunes tags
|
||||
# http://www.apple.com/itunes/podcasts/specs.html#rss
|
||||
self.__itunes_author = None
|
||||
self.__itunes_block = None
|
||||
self.__itunes_category = None
|
||||
self.__itunes_image = None
|
||||
self.__itunes_explicit = None
|
||||
self.__itunes_complete = None
|
||||
self.__itunes_new_feed_url = None
|
||||
self.__itunes_owner = None
|
||||
self.__itunes_subtitle = None
|
||||
self.__itunes_summary = None
|
||||
|
||||
def extend_ns(self):
|
||||
return {'itunes': 'http://www.itunes.com/dtds/podcast-1.0.dtd'}
|
||||
|
||||
def extend_rss(self, rss_feed):
|
||||
'''Extend an RSS feed root with set itunes fields.
|
||||
|
||||
:returns: The feed root element.
|
||||
'''
|
||||
ITUNES_NS = 'http://www.itunes.com/dtds/podcast-1.0.dtd'
|
||||
channel = rss_feed[0]
|
||||
|
||||
if self.__itunes_author:
|
||||
author = etree.SubElement(channel, '{%s}author' % ITUNES_NS)
|
||||
author.text = self.__itunes_author
|
||||
|
||||
if self.__itunes_block is not None:
|
||||
block = etree.SubElement(channel, '{%s}block' % ITUNES_NS)
|
||||
block.text = 'yes' if self.__itunes_block else 'no'
|
||||
|
||||
for c in self.__itunes_category or []:
|
||||
if not c.get('cat'):
|
||||
continue
|
||||
category = channel.find(
|
||||
'{%s}category[@text="%s"]' % (ITUNES_NS, c.get('cat')))
|
||||
if category is None:
|
||||
category = etree.SubElement(channel,
|
||||
'{%s}category' % ITUNES_NS)
|
||||
category.attrib['text'] = c.get('cat')
|
||||
|
||||
if c.get('sub'):
|
||||
subcategory = etree.SubElement(category,
|
||||
'{%s}category' % ITUNES_NS)
|
||||
subcategory.attrib['text'] = c.get('sub')
|
||||
|
||||
if self.__itunes_image:
|
||||
image = etree.SubElement(channel, '{%s}image' % ITUNES_NS)
|
||||
image.attrib['href'] = self.__itunes_image
|
||||
|
||||
if self.__itunes_explicit in ('yes', 'no', 'clean'):
|
||||
explicit = etree.SubElement(channel, '{%s}explicit' % ITUNES_NS)
|
||||
explicit.text = self.__itunes_explicit
|
||||
|
||||
if self.__itunes_complete in ('yes', 'no'):
|
||||
complete = etree.SubElement(channel, '{%s}complete' % ITUNES_NS)
|
||||
complete.text = self.__itunes_complete
|
||||
|
||||
if self.__itunes_new_feed_url:
|
||||
new_feed_url = etree.SubElement(channel,
|
||||
'{%s}new-feed-url' % ITUNES_NS)
|
||||
new_feed_url.text = self.__itunes_new_feed_url
|
||||
|
||||
if self.__itunes_owner:
|
||||
owner = etree.SubElement(channel, '{%s}owner' % ITUNES_NS)
|
||||
owner_name = etree.SubElement(owner, '{%s}name' % ITUNES_NS)
|
||||
owner_name.text = self.__itunes_owner.get('name')
|
||||
owner_email = etree.SubElement(owner, '{%s}email' % ITUNES_NS)
|
||||
owner_email.text = self.__itunes_owner.get('email')
|
||||
|
||||
if self.__itunes_subtitle:
|
||||
subtitle = etree.SubElement(channel, '{%s}subtitle' % ITUNES_NS)
|
||||
subtitle.text = self.__itunes_subtitle
|
||||
|
||||
if self.__itunes_summary:
|
||||
summary = etree.SubElement(channel, '{%s}summary' % ITUNES_NS)
|
||||
summary.text = self.__itunes_summary
|
||||
|
||||
return rss_feed
|
||||
|
||||
def itunes_author(self, itunes_author=None):
|
||||
'''Get or set the itunes:author. The content of 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> is not present at the
|
||||
feed level, iTunes will use the contents of <managingEditor>.
|
||||
|
||||
:param itunes_author: The author of the podcast.
|
||||
:returns: The author of the podcast.
|
||||
'''
|
||||
if itunes_author is not None:
|
||||
self.__itunes_author = itunes_author
|
||||
return self.__itunes_author
|
||||
|
||||
def itunes_block(self, itunes_block=None):
|
||||
'''Get or set the ITunes block attribute. Use this to prevent the
|
||||
entire podcast from appearing in the iTunes podcast directory.
|
||||
|
||||
:param itunes_block: Block the podcast.
|
||||
:returns: If the podcast is blocked.
|
||||
'''
|
||||
if itunes_block is not None:
|
||||
self.__itunes_block = itunes_block
|
||||
return self.__itunes_block
|
||||
|
||||
def itunes_category(self, itunes_category=None, replace=False, **kwargs):
|
||||
'''Get or set the ITunes category which appears in the category column
|
||||
and in iTunes Store Browser.
|
||||
|
||||
The (sub-)category has to be one from the values defined at
|
||||
http://www.apple.com/itunes/podcasts/specs.html#categories
|
||||
|
||||
This method can be called with:
|
||||
|
||||
- the fields of an itunes_category as keyword arguments
|
||||
- the fields of an itunes_category as a dictionary
|
||||
- a list of dictionaries containing the itunes_category fields
|
||||
|
||||
An itunes_category has the following fields:
|
||||
|
||||
- *cat* name for a category.
|
||||
- *sub* name for a subcategory, child of category
|
||||
|
||||
If a podcast has more than one subcategory from the same category, the
|
||||
category is called more than once.
|
||||
|
||||
Likei the parameter::
|
||||
|
||||
[{"cat":"Arts","sub":"Design"},{"cat":"Arts","sub":"Food"}]
|
||||
|
||||
…would become::
|
||||
|
||||
<itunes:category text="Arts">
|
||||
<itunes:category text="Design"/>
|
||||
<itunes:category text="Food"/>
|
||||
</itunes:category>
|
||||
|
||||
|
||||
:param itunes_category: Dictionary or list of dictionaries with
|
||||
itunes_category data.
|
||||
:param replace: Add or replace old data.
|
||||
:returns: List of itunes_categories as dictionaries.
|
||||
|
||||
---
|
||||
|
||||
**Important note about deprecated parameter syntax:** Old version of
|
||||
the feedgen did only support one category plus one subcategory which
|
||||
would be passed to this ducntion as first two parameters. For
|
||||
compatibility reasons, this still works but should not be used any may
|
||||
be removed at any time.
|
||||
'''
|
||||
# Ensure old API still works for now. Note that the API is deprecated
|
||||
# and this fallback may be removed at any time.
|
||||
if isinstance(itunes_category, string_types):
|
||||
itunes_category = {'cat': itunes_category}
|
||||
if replace:
|
||||
itunes_category['sub'] = replace
|
||||
replace = True
|
||||
if itunes_category is None and kwargs:
|
||||
itunes_category = kwargs
|
||||
if itunes_category is not None:
|
||||
if replace or self.__itunes_category is None:
|
||||
self.__itunes_category = []
|
||||
self.__itunes_category += ensure_format(itunes_category,
|
||||
set(['cat', 'sub']),
|
||||
set(['cat']))
|
||||
return self.__itunes_category
|
||||
|
||||
def itunes_image(self, itunes_image=None):
|
||||
'''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 prefers square .jpg images that are at least 1400x1400 pixels,
|
||||
which is different from what is specified for the standard RSS image
|
||||
tag. In order for a podcast to be eligible for an 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
|
||||
(CMYK is not supported). The URL must end in ".jpg" or ".png". If the
|
||||
<itunes:image> tag is not present, iTunes will use the contents of the
|
||||
RSS image tag.
|
||||
|
||||
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 same. The server hosting your cover art image must allow HTTP head
|
||||
requests for iTS to be able to automatically update your cover art.
|
||||
|
||||
:param itunes_image: Image of the podcast.
|
||||
:returns: Image of the podcast.
|
||||
'''
|
||||
if itunes_image is not None:
|
||||
if itunes_image.endswith('.jpg') or itunes_image.endswith('.png'):
|
||||
self.__itunes_image = itunes_image
|
||||
else:
|
||||
ValueError('Image file must be png or jpg')
|
||||
return self.__itunes_image
|
||||
|
||||
def itunes_explicit(self, itunes_explicit=None):
|
||||
'''Get or the the itunes:explicit value of the podcast. This tag should
|
||||
be used to indicate whether your podcast contains explicit material.
|
||||
The three values for this tag are "yes", "no", and "clean".
|
||||
|
||||
If you populate this tag with "yes", an "explicit" parental advisory
|
||||
graphic will appear next to your podcast artwork on the iTunes Store
|
||||
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
|
||||
adult content is included anywhere in the episodes, and a "clean"
|
||||
graphic will appear. If the explicit tag is present and has any other
|
||||
value (e.g., "no"), you see no indicator — blank is the default
|
||||
advisory type.
|
||||
|
||||
:param itunes_explicit: If the podcast contains explicit material.
|
||||
:returns: If the podcast contains explicit material.
|
||||
'''
|
||||
if itunes_explicit is not None:
|
||||
if itunes_explicit not in ('', 'yes', 'no', 'clean'):
|
||||
raise ValueError('Invalid value for explicit tag')
|
||||
self.__itunes_explicit = itunes_explicit
|
||||
return self.__itunes_explicit
|
||||
|
||||
def itunes_complete(self, itunes_complete=None):
|
||||
'''Get or set the itunes:complete value of the podcast. This tag can be
|
||||
used to indicate the completion of a podcast.
|
||||
|
||||
If you populate this tag with "yes", you are indicating that no more
|
||||
episodes will be added to the podcast. If the <itunes:complete> tag is
|
||||
present and has any other value (e.g. “no”), it will have no effect on
|
||||
the podcast.
|
||||
|
||||
:param itunes_complete: If the podcast is complete.
|
||||
:returns: If the podcast is complete.
|
||||
'''
|
||||
if itunes_complete is not None:
|
||||
if itunes_complete not in ('yes', 'no', '', True, False):
|
||||
raise ValueError('Invalid value for complete tag')
|
||||
if itunes_complete is True:
|
||||
itunes_complete = 'yes'
|
||||
if itunes_complete is False:
|
||||
itunes_complete = 'no'
|
||||
self.__itunes_complete = itunes_complete
|
||||
return self.__itunes_complete
|
||||
|
||||
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
|
||||
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
|
||||
for 48 hours before retiring it. At that point, iTunes will have
|
||||
updated the directory with the new feed URL.
|
||||
|
||||
:param itunes_new_feed_url: New feed URL.
|
||||
:returns: New feed URL.
|
||||
'''
|
||||
if itunes_new_feed_url is not None:
|
||||
self.__itunes_new_feed_url = itunes_new_feed_url
|
||||
return self.__itunes_new_feed_url
|
||||
|
||||
def itunes_owner(self, name=None, email=None):
|
||||
'''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
|
||||
communication specifically about the podcast. It will not be publicly
|
||||
displayed.
|
||||
|
||||
:param itunes_owner: The owner of the feed.
|
||||
:returns: Data of the owner of the feed.
|
||||
'''
|
||||
if name is not None:
|
||||
if name and email:
|
||||
self.__itunes_owner = {'name': name, 'email': email}
|
||||
elif not name and not email:
|
||||
self.__itunes_owner = None
|
||||
else:
|
||||
raise ValueError('Both name and email have to be set.')
|
||||
return self.__itunes_owner
|
||||
|
||||
def itunes_subtitle(self, itunes_subtitle=None):
|
||||
'''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
|
||||
displays best if it is only a few words long.
|
||||
|
||||
:param itunes_subtitle: Subtitle of the podcast.
|
||||
:returns: Subtitle of the podcast.
|
||||
'''
|
||||
if itunes_subtitle is not None:
|
||||
self.__itunes_subtitle = itunes_subtitle
|
||||
return self.__itunes_subtitle
|
||||
|
||||
def itunes_summary(self, itunes_summary=None):
|
||||
'''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" in the Description column is clicked. It also appears on the iTunes
|
||||
page for your podcast. This field can be up to 4000 characters. If
|
||||
`<itunes:summary>` is not included, the contents of the <description>
|
||||
tag are used.
|
||||
|
||||
:param itunes_summary: Summary of the podcast.
|
||||
:returns: Summary of the podcast.
|
||||
'''
|
||||
if itunes_summary is not None:
|
||||
self.__itunes_summary = itunes_summary
|
||||
return self.__itunes_summary
|
||||
|
||||
_itunes_categories = {
|
||||
'Arts': [
|
||||
'Design', 'Fashion & Beauty', 'Food', 'Literature',
|
||||
'Performing Arts', 'Visual Arts'],
|
||||
'Business': [
|
||||
'Business News', 'Careers', 'Investing',
|
||||
'Management & Marketing', 'Shopping'],
|
||||
'Comedy': [],
|
||||
'Education': [
|
||||
'Education', 'Education Technology', 'Higher Education',
|
||||
'K-12', 'Language Courses', 'Training'],
|
||||
'Games & Hobbies': [
|
||||
'Automotive', 'Aviation', 'Hobbies', 'Other Games',
|
||||
'Video Games'],
|
||||
'Government & Organizations': [
|
||||
'Local', 'National', 'Non-Profit', 'Regional'],
|
||||
'Health': [
|
||||
'Alternative Health', 'Fitness & Nutrition', 'Self-Help',
|
||||
'Sexuality'],
|
||||
'Kids & Family': [],
|
||||
'Music': [],
|
||||
'News & Politics': [],
|
||||
'Religion & Spirituality': [
|
||||
'Buddhism', 'Christianity', 'Hinduism', 'Islam', 'Judaism',
|
||||
'Other', 'Spirituality'],
|
||||
'Science & Medicine': [
|
||||
'Medicine', 'Natural Sciences', 'Social Sciences'],
|
||||
'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': []}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
'''
|
||||
feedgen.ext.podcast_entry
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
feedgen.ext.podcast_entry
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
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.
|
||||
'''
|
||||
|
||||
from lxml import etree
|
||||
|
@ -15,229 +15,230 @@ from feedgen.ext.base import BaseEntryExtension
|
|||
|
||||
|
||||
class PodcastEntryExtension(BaseEntryExtension):
|
||||
'''FeedEntry extension for podcasts.
|
||||
'''
|
||||
'''FeedEntry extension for podcasts.
|
||||
'''
|
||||
|
||||
def __init__(self):
|
||||
# ITunes tags
|
||||
# http://www.apple.com/itunes/podcasts/specs.html#rss
|
||||
self.__itunes_author = None
|
||||
self.__itunes_block = None
|
||||
self.__itunes_image = None
|
||||
self.__itunes_duration = None
|
||||
self.__itunes_explicit = None
|
||||
self.__itunes_is_closed_captioned = None
|
||||
self.__itunes_order = None
|
||||
self.__itunes_subtitle = None
|
||||
self.__itunes_summary = None
|
||||
|
||||
def __init__(self):
|
||||
## ITunes tags
|
||||
# http://www.apple.com/itunes/podcasts/specs.html#rss
|
||||
self.__itunes_author = None
|
||||
self.__itunes_block = None
|
||||
self.__itunes_image = None
|
||||
self.__itunes_duration = None
|
||||
self.__itunes_explicit = None
|
||||
self.__itunes_is_closed_captioned = None
|
||||
self.__itunes_order = None
|
||||
self.__itunes_subtitle = None
|
||||
self.__itunes_summary = None
|
||||
def extend_rss(self, entry):
|
||||
'''Add additional fields to an RSS item.
|
||||
|
||||
:param feed: The RSS item XML element to use.
|
||||
'''
|
||||
ITUNES_NS = 'http://www.itunes.com/dtds/podcast-1.0.dtd'
|
||||
|
||||
def extend_rss(self, entry):
|
||||
'''Add additional fields to an RSS item.
|
||||
if self.__itunes_author:
|
||||
author = etree.SubElement(entry, '{%s}author' % ITUNES_NS)
|
||||
author.text = self.__itunes_author
|
||||
|
||||
:param feed: The RSS item XML element to use.
|
||||
'''
|
||||
ITUNES_NS = 'http://www.itunes.com/dtds/podcast-1.0.dtd'
|
||||
if self.__itunes_block is not None:
|
||||
block = etree.SubElement(entry, '{%s}block' % ITUNES_NS)
|
||||
block.text = 'yes' if self.__itunes_block else 'no'
|
||||
|
||||
if self.__itunes_author:
|
||||
author = etree.SubElement(entry, '{%s}author' % ITUNES_NS)
|
||||
author.text = self.__itunes_author
|
||||
if self.__itunes_image:
|
||||
image = etree.SubElement(entry, '{%s}image' % ITUNES_NS)
|
||||
image.attrib['href'] = self.__itunes_image
|
||||
|
||||
if not self.__itunes_block is None:
|
||||
block = etree.SubElement(entry, '{%s}block' % ITUNES_NS)
|
||||
block.text = 'yes' if self.__itunes_block else 'no'
|
||||
if self.__itunes_duration:
|
||||
duration = etree.SubElement(entry, '{%s}duration' % ITUNES_NS)
|
||||
duration.text = self.__itunes_duration
|
||||
|
||||
if self.__itunes_image:
|
||||
image = etree.SubElement(entry, '{%s}image' % ITUNES_NS)
|
||||
image.attrib['href'] = self.__itunes_image
|
||||
if self.__itunes_explicit in ('yes', 'no', 'clean'):
|
||||
explicit = etree.SubElement(entry, '{%s}explicit' % ITUNES_NS)
|
||||
explicit.text = self.__itunes_explicit
|
||||
|
||||
if self.__itunes_duration:
|
||||
duration = etree.SubElement(entry, '{%s}duration' % ITUNES_NS)
|
||||
duration.text = self.__itunes_duration
|
||||
if self.__itunes_is_closed_captioned is not None:
|
||||
is_closed_captioned = etree.SubElement(
|
||||
entry, '{%s}isClosedCaptioned' % ITUNES_NS)
|
||||
if self.__itunes_is_closed_captioned:
|
||||
is_closed_captioned.text = 'yes'
|
||||
else:
|
||||
is_closed_captioned.text = 'no'
|
||||
|
||||
if self.__itunes_explicit in ('yes', 'no', 'clean'):
|
||||
explicit = etree.SubElement(entry, '{%s}explicit' % ITUNES_NS)
|
||||
explicit.text = self.__itunes_explicit
|
||||
if self.__itunes_order is not None and self.__itunes_order >= 0:
|
||||
order = etree.SubElement(entry, '{%s}order' % ITUNES_NS)
|
||||
order.text = str(self.__itunes_order)
|
||||
|
||||
if not self.__itunes_is_closed_captioned is None:
|
||||
is_closed_captioned = etree.SubElement(entry, '{%s}isClosedCaptioned' % ITUNES_NS)
|
||||
is_closed_captioned.text = 'yes' if self.__itunes_is_closed_captioned else 'no'
|
||||
if self.__itunes_subtitle:
|
||||
subtitle = etree.SubElement(entry, '{%s}subtitle' % ITUNES_NS)
|
||||
subtitle.text = self.__itunes_subtitle
|
||||
|
||||
if not self.__itunes_order is None and self.__itunes_order >= 0:
|
||||
order = etree.SubElement(entry, '{%s}order' % ITUNES_NS)
|
||||
order.text = str(self.__itunes_order)
|
||||
if self.__itunes_summary:
|
||||
summary = etree.SubElement(entry, '{%s}summary' % ITUNES_NS)
|
||||
summary.text = self.__itunes_summary
|
||||
return entry
|
||||
|
||||
if self.__itunes_subtitle:
|
||||
subtitle = etree.SubElement(entry, '{%s}subtitle' % ITUNES_NS)
|
||||
subtitle.text = self.__itunes_subtitle
|
||||
def itunes_author(self, itunes_author=None):
|
||||
'''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
|
||||
present, iTunes uses the contents of the <author> tag. If
|
||||
<itunes:author> is not present at the feed level, iTunes will use the
|
||||
contents of <managingEditor>.
|
||||
|
||||
if self.__itunes_summary:
|
||||
summary = etree.SubElement(entry, '{%s}summary' % ITUNES_NS)
|
||||
summary.text = self.__itunes_summary
|
||||
return entry
|
||||
:param itunes_author: The author of the podcast.
|
||||
:returns: The author of the podcast.
|
||||
'''
|
||||
if itunes_author is not None:
|
||||
self.__itunes_author = itunes_author
|
||||
return self.__itunes_author
|
||||
|
||||
def itunes_block(self, itunes_block=None):
|
||||
'''Get or set the ITunes block attribute. Use this to prevent episodes
|
||||
from appearing in the iTunes podcast directory.
|
||||
|
||||
def itunes_author(self, itunes_author=None):
|
||||
'''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
|
||||
present, iTunes uses the contents of the <author> tag. If <itunes:author>
|
||||
is not present at the feed level, iTunes will use the contents of
|
||||
<managingEditor>.
|
||||
:param itunes_block: Block podcast episodes.
|
||||
:returns: If the podcast episode is blocked.
|
||||
'''
|
||||
if itunes_block is not None:
|
||||
self.__itunes_block = itunes_block
|
||||
return self.__itunes_block
|
||||
|
||||
:param itunes_author: The author of the podcast.
|
||||
:returns: The author of the podcast.
|
||||
'''
|
||||
if not itunes_author is None:
|
||||
self.__itunes_author = itunes_author
|
||||
return self.__itunes_author
|
||||
def itunes_image(self, itunes_image=None):
|
||||
'''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. iTunes prefers square .jpg images that are at least
|
||||
1400x1400 pixels, which is different from what is specified for the
|
||||
standard RSS image tag. In order for a podcast to be eligible for an
|
||||
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
|
||||
(CMYK is not supported). The URL must end in ".jpg" or ".png". If the
|
||||
<itunes:image> tag is not present, iTunes will use the contents of the
|
||||
RSS image tag.
|
||||
|
||||
def itunes_block(self, itunes_block=None):
|
||||
'''Get or set the ITunes block attribute. Use this to prevent episodes
|
||||
from appearing in the iTunes podcast directory.
|
||||
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 same. The server hosting your cover art image must allow HTTP head
|
||||
requests for iTS to be able to automatically update your cover art.
|
||||
|
||||
:param itunes_block: Block podcast episodes.
|
||||
:returns: If the podcast episode is blocked.
|
||||
'''
|
||||
if not itunes_block is None:
|
||||
self.__itunes_block = itunes_block
|
||||
return self.__itunes_block
|
||||
:param itunes_image: Image of the podcast.
|
||||
:returns: Image of the podcast.
|
||||
'''
|
||||
if itunes_image is not None:
|
||||
if itunes_image.endswith('.jpg') or itunes_image.endswith('.png'):
|
||||
self.__itunes_image = itunes_image
|
||||
else:
|
||||
ValueError('Image file must be png or jpg')
|
||||
return self.__itunes_image
|
||||
|
||||
def itunes_duration(self, itunes_duration=None):
|
||||
'''Get or set the duration of the podcast episode. The content of this
|
||||
tag is shown in the Time column in iTunes.
|
||||
|
||||
def itunes_image(self, itunes_image=None):
|
||||
'''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.
|
||||
iTunes prefers square .jpg images that are at least 1400x1400 pixels,
|
||||
which is different from what is specified for the standard RSS image tag.
|
||||
In order for a podcast to be eligible for an iTunes Store feature, the
|
||||
accompanying image must be at least 1400x1400 pixels.
|
||||
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), the value is assumed to be in seconds. If one colon is
|
||||
present, the number to the left is assumed to be minutes, and the
|
||||
number to the right is assumed to be seconds. If more than two colons
|
||||
are present, the numbers farthest to the right are ignored.
|
||||
|
||||
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
|
||||
<itunes:image> tag is not present, iTunes will use the contents of the
|
||||
RSS image tag.
|
||||
:param itunes_duration: Duration of the podcast episode.
|
||||
:returns: Duration of the podcast episode.
|
||||
'''
|
||||
if itunes_duration is not None:
|
||||
itunes_duration = str(itunes_duration)
|
||||
if len(itunes_duration.split(':')) > 3 or \
|
||||
itunes_duration.lstrip('0123456789:') != '':
|
||||
ValueError('Invalid duration format')
|
||||
self.__itunes_duration = itunes_duration
|
||||
return self.itunes_duration
|
||||
|
||||
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
|
||||
same. The server hosting your cover art image must allow HTTP head
|
||||
requests for iTS to be able to automatically update your cover art.
|
||||
def itunes_explicit(self, itunes_explicit=None):
|
||||
'''Get or the the itunes:explicit value of the podcast episode. This
|
||||
tag should be used to indicate whether your podcast episode contains
|
||||
explicit material. The three values for this tag are "yes", "no", and
|
||||
"clean".
|
||||
|
||||
:param itunes_image: Image of the podcast.
|
||||
:returns: Image of the podcast.
|
||||
'''
|
||||
if not itunes_image is None:
|
||||
if not ( itunes_image.endswith('.jpg') or itunes_image.endswith('.png') ):
|
||||
ValueError('Image file must be png or jpg')
|
||||
self.__itunes_image = itunes_image
|
||||
return self.__itunes_image
|
||||
If you populate this tag with "yes", an "explicit" parental advisory
|
||||
graphic will appear next to your podcast artwork on the iTunes Store
|
||||
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
|
||||
adult content is included anywhere in the episodes, and a "clean"
|
||||
graphic will appear. If the explicit tag is present and has any other
|
||||
value (e.g., "no"), you see no indicator — blank is the default
|
||||
advisory type.
|
||||
|
||||
:param itunes_explicit: If the podcast episode contains explicit
|
||||
material.
|
||||
:returns: If the podcast episode contains explicit material.
|
||||
'''
|
||||
if itunes_explicit is not None:
|
||||
if itunes_explicit not in ('', 'yes', 'no', 'clean'):
|
||||
raise ValueError('Invalid value for explicit tag')
|
||||
self.__itunes_explicit = itunes_explicit
|
||||
return self.__itunes_explicit
|
||||
|
||||
def itunes_duration(self, itunes_duration=None):
|
||||
'''Get or set the duration of the podcast episode. The content of this
|
||||
tag is shown in the Time column in iTunes.
|
||||
def itunes_is_closed_captioned(self, itunes_is_closed_captioned=None):
|
||||
'''Get or set the is_closed_captioned value of the podcast episode.
|
||||
This tag should be used if your podcast includes a video episode with
|
||||
embedded closed captioning support. The two values for this tag are
|
||||
"yes" and "no”.
|
||||
|
||||
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),
|
||||
the value is assumed to be in seconds. If one colon is present, the
|
||||
number to the left is assumed to be minutes, and the number to the right
|
||||
is assumed to be seconds. If more than two colons are present, the
|
||||
numbers farthest to the right are ignored.
|
||||
:param is_closed_captioned: If the episode has closed captioning
|
||||
support.
|
||||
:returns: If the episode has closed captioning support.
|
||||
'''
|
||||
if itunes_is_closed_captioned is not None:
|
||||
self.__itunes_is_closed_captioned = \
|
||||
itunes_is_closed_captioned in ('yes', True)
|
||||
return self.__itunes_is_closed_captioned
|
||||
|
||||
:param itunes_duration: Duration of the podcast episode.
|
||||
:returns: Duration of the podcast episode.
|
||||
'''
|
||||
if not itunes_duration is None:
|
||||
itunes_duration = str(itunes_duration)
|
||||
if len(itunes_duration.split(':')) > 3 or \
|
||||
itunes_duration.lstrip('0123456789:') != '':
|
||||
ValueError('Invalid duration format')
|
||||
self.__itunes_duration = itunes_duration
|
||||
return self.itunes_duration
|
||||
def itunes_order(self, itunes_order=None):
|
||||
'''Get or set the itunes:order value of the podcast episode. This tag
|
||||
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
|
||||
in which you would like the episode to appear on the store. For
|
||||
example, if you would like an <item> to appear as the first episode in
|
||||
the podcast, you would populate the <itunes:order> tag with “1”. If
|
||||
conflicting order values are present in multiple episodes, the store
|
||||
will use default ordering (pubDate).
|
||||
|
||||
def itunes_explicit(self, itunes_explicit=None):
|
||||
'''Get or the the itunes:explicit value of the podcast episode. This tag
|
||||
should be used to indicate whether your podcast episode contains explicit
|
||||
material. The three values for this tag are "yes", "no", and "clean".
|
||||
To remove the order from the episode set the order to a value below
|
||||
zero.
|
||||
|
||||
If you populate this tag with "yes", an "explicit" parental advisory
|
||||
graphic will appear next to your podcast artwork on the iTunes Store 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
|
||||
adult content is included anywhere in the episodes, and a "clean" graphic
|
||||
will appear. If the explicit tag is present and has any other value
|
||||
(e.g., "no"), you see no indicator — blank is the default advisory type.
|
||||
:param itunes_order: The order of the episode.
|
||||
:returns: The order of the episode.
|
||||
'''
|
||||
if itunes_order is not None:
|
||||
self.__itunes_order = int(itunes_order)
|
||||
return self.__itunes_order
|
||||
|
||||
:param itunes_explicit: If the podcast episode contains explicit material.
|
||||
:returns: If the podcast episode contains explicit material.
|
||||
'''
|
||||
if not itunes_explicit is None:
|
||||
if not itunes_explicit in ('', 'yes', 'no', 'clean'):
|
||||
raise ValueError('Invalid value for explicit tag')
|
||||
self.__itunes_explicit = itunes_explicit
|
||||
return self.__itunes_explicit
|
||||
def itunes_subtitle(self, itunes_subtitle=None):
|
||||
'''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
|
||||
subtitle displays best if it is only a few words long.
|
||||
|
||||
:param itunes_subtitle: Subtitle of the podcast episode.
|
||||
:returns: Subtitle of the podcast episode.
|
||||
'''
|
||||
if itunes_subtitle is not None:
|
||||
self.__itunes_subtitle = itunes_subtitle
|
||||
return self.__itunes_subtitle
|
||||
|
||||
def itunes_is_closed_captioned(self, itunes_is_closed_captioned=None):
|
||||
'''Get or set the is_closed_captioned value of the podcast episode. This
|
||||
tag should be used if your podcast includes a video episode with embedded
|
||||
closed captioning support. The two values for this tag are "yes" and
|
||||
"no”.
|
||||
def itunes_summary(self, itunes_summary=None):
|
||||
'''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 "circled i" in the Description column is clicked. It also appears
|
||||
on the iTunes page for your podcast. This field can be up to 4000
|
||||
characters. If <itunes:summary> is not included, the contents of the
|
||||
<description> tag are used.
|
||||
|
||||
:param is_closed_captioned: If the episode has closed captioning support.
|
||||
:returns: If the episode has closed captioning support.
|
||||
'''
|
||||
if not itunes_is_closed_captioned is None:
|
||||
self.__itunes_is_closed_captioned = itunes_is_closed_captioned in ('yes', True)
|
||||
return self.__itunes_is_closed_captioned
|
||||
|
||||
|
||||
def itunes_order(self, itunes_order=None):
|
||||
'''Get or set the itunes:order value of the podcast episode. This tag 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
|
||||
in which you would like the episode to appear on the store. For example,
|
||||
if you would like an <item> to appear as the first episode in the
|
||||
podcast, you would populate the <itunes:order> tag with “1”. If
|
||||
conflicting order values are present in multiple episodes, the store will
|
||||
use default ordering (pubDate).
|
||||
|
||||
To remove the order from the episode set the order to a value below zero.
|
||||
|
||||
:param itunes_order: The order of the episode.
|
||||
:returns: The order of the episode.
|
||||
'''
|
||||
if not itunes_order is None:
|
||||
self.__itunes_order = int(itunes_order)
|
||||
return self.__itunes_order
|
||||
|
||||
|
||||
def itunes_subtitle(self, itunes_subtitle=None):
|
||||
'''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
|
||||
subtitle displays best if it is only a few words long.
|
||||
|
||||
:param itunes_subtitle: Subtitle of the podcast episode.
|
||||
:returns: Subtitle of the podcast episode.
|
||||
'''
|
||||
if not itunes_subtitle is None:
|
||||
self.__itunes_subtitle = itunes_subtitle
|
||||
return self.__itunes_subtitle
|
||||
|
||||
|
||||
def itunes_summary(self, itunes_summary=None):
|
||||
'''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
|
||||
"circled i" in the Description column is clicked. It also appears on the
|
||||
iTunes page for your podcast. This field can be up to 4000 characters. If
|
||||
<itunes:summary> is not included, the contents of the <description> tag
|
||||
are used.
|
||||
|
||||
:param itunes_summary: Summary of the podcast episode.
|
||||
:returns: Summary of the podcast episode.
|
||||
'''
|
||||
if not itunes_summary is None:
|
||||
self.__itunes_summary = itunes_summary
|
||||
return self.__itunes_summary
|
||||
:param itunes_summary: Summary of the podcast episode.
|
||||
:returns: Summary of the podcast episode.
|
||||
'''
|
||||
if itunes_summary is not None:
|
||||
self.__itunes_summary = itunes_summary
|
||||
return self.__itunes_summary
|
||||
|
|
|
@ -1,127 +1,126 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
'''
|
||||
feedgen.ext.torrent
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
feedgen.ext.torrent
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Extends the FeedGenerator to produce torrent feeds.
|
||||
Extends the FeedGenerator to produce torrent feeds.
|
||||
|
||||
:copyright: 2016, Raspbeguy <raspbeguy@hashtagueule.fr>
|
||||
:copyright: 2016, Raspbeguy <raspbeguy@hashtagueule.fr>
|
||||
|
||||
:license: FreeBSD and LGPL, see license.* for more details.
|
||||
:license: FreeBSD and LGPL, see license.* for more details.
|
||||
'''
|
||||
|
||||
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/'
|
||||
|
||||
|
||||
class TorrentExtension(BaseExtension):
|
||||
'''FeedGenerator extension for torrent feeds.
|
||||
'''
|
||||
def extend_ns(self):
|
||||
return {'torrent' : TORRENT_NS}
|
||||
'''FeedGenerator extension for torrent feeds.
|
||||
'''
|
||||
def extend_ns(self):
|
||||
return {'torrent': TORRENT_NS}
|
||||
|
||||
|
||||
class TorrentEntryExtension(BaseEntryExtension):
|
||||
'''FeedEntry extention for torrent feeds
|
||||
'''
|
||||
def __init__(self):
|
||||
self.__torrent_filename = None
|
||||
self.__torrent_infohash = None
|
||||
self.__torrent_contentlength = None
|
||||
self.__torrent_seeds = None
|
||||
self.__torrent_peers = None
|
||||
self.__torrent=verified = None
|
||||
'''FeedEntry extention for torrent feeds
|
||||
'''
|
||||
def __init__(self):
|
||||
self.__torrent_filename = None
|
||||
self.__torrent_infohash = None
|
||||
self.__torrent_contentlength = None
|
||||
self.__torrent_seeds = None
|
||||
self.__torrent_peers = None
|
||||
|
||||
def extend_rss(self, entry):
|
||||
'''Add additional fields to an RSS item.
|
||||
|
||||
def extend_rss(self, entry):
|
||||
'''Add additional fields to an RSS item.
|
||||
:param feed: The RSS item XML element to use.
|
||||
'''
|
||||
if self.__torrent_filename:
|
||||
filename = etree.SubElement(entry, '{%s}filename' % TORRENT_NS)
|
||||
filename.text = self.__torrent_filename
|
||||
|
||||
:param feed: The RSS item XML element to use.
|
||||
'''
|
||||
if self.__torrent_filename:
|
||||
filename = etree.SubElement(entry, '{%s}filename' % TORRENT_NS)
|
||||
filename.text = self.__torrent_filename
|
||||
if self.__torrent_contentlength:
|
||||
contentlength = etree.SubElement(entry,
|
||||
'{%s}contentlength' % TORRENT_NS)
|
||||
contentlength.text = self.__torrent_contentlength
|
||||
|
||||
if self.__torrent_contentlength:
|
||||
contentlength = etree.SubElement(entry, '{%s}contentlength' % TORRENT_NS)
|
||||
contentlength.text = self.__torrent_contentlength
|
||||
if self.__torrent_infohash:
|
||||
infohash = etree.SubElement(entry, '{%s}infohash' % TORRENT_NS)
|
||||
infohash.text = self.__torrent_infohash
|
||||
magnet = etree.SubElement(entry, '{%s}magneturi' % TORRENT_NS)
|
||||
magnet.text = 'magnet:?xt=urn:btih:' + self.__torrent_infohash
|
||||
|
||||
if self.__torrent_infohash:
|
||||
infohash = etree.SubElement(entry, '{%s}infohash' % TORRENT_NS)
|
||||
infohash.text = self.__torrent_infohash
|
||||
magnet = etree.SubElement(entry, '{%s}magneturi' % TORRENT_NS)
|
||||
magnet.text = 'magnet:?xt=urn:btih:' + self.__torrent_infohash
|
||||
if self.__torrent_seeds:
|
||||
seeds = etree.SubElement(entry, '{%s}seed' % TORRENT_NS)
|
||||
seeds.text = self.__torrent_seeds
|
||||
|
||||
if self.__torrent_seeds:
|
||||
seeds = etree.SubElement(entry, '{%s}seed' % TORRENT_NS)
|
||||
seeds.text = self.__torrent_seeds
|
||||
if self.__torrent_peers:
|
||||
peers = etree.SubElement(entry, '{%s}peers' % TORRENT_NS)
|
||||
peers.text = self.__torrent_peers
|
||||
|
||||
if self.__torrent_peers:
|
||||
peers = etree.SubElement(entry, '{%s}peers' % TORRENT_NS)
|
||||
peers.text = self.__torrent_peers
|
||||
if self.__torrent_seeds:
|
||||
verified = etree.SubElement(entry, '{%s}verified' % TORRENT_NS)
|
||||
verified.text = self.__torrent_verified
|
||||
|
||||
if self.__torrent_seeds:
|
||||
verified = etree.SubElement(entry, '{%s}verified' % TORRENT_NS)
|
||||
verified.text = self.__torrent_verified
|
||||
def filename(self, torrent_filename=None):
|
||||
'''Get or set the name of the torrent file.
|
||||
|
||||
:param torrent_filename: The name of the torrent file.
|
||||
:returns: The name of the torrent file.
|
||||
'''
|
||||
if torrent_filename is not None:
|
||||
self.__torrent_filename = torrent_filename
|
||||
return self.__torrent_filename
|
||||
|
||||
def filename(self, torrent_filename=None):
|
||||
'''Get or set the name of the torrent file.
|
||||
def infohash(self, torrent_infohash=None):
|
||||
'''Get or set the hash of the target file.
|
||||
|
||||
:param torrent_filename: The name of the torrent file.
|
||||
:returns: The name of the torrent file.
|
||||
'''
|
||||
if not torrent_filename is None:
|
||||
self.__torrent_filename = torrent_filename
|
||||
return self.__torrent_filename
|
||||
:param torrent_infohash: The target file hash.
|
||||
:returns: The target hash file.
|
||||
'''
|
||||
if torrent_infohash is not None:
|
||||
self.__torrent_infohash = torrent_infohash
|
||||
return self.__torrent_infohash
|
||||
|
||||
def infohash (self, torrent_infohash=None):
|
||||
'''Get or set the hash of the target file.
|
||||
def contentlength(self, torrent_contentlength=None):
|
||||
'''Get or set the size of the target file.
|
||||
|
||||
:param torrent_infohash: The target file hash.
|
||||
:returns: The target hash file.
|
||||
'''
|
||||
if not torrent_infohash is None:
|
||||
self.__torrent_infohash = torrent_infohash
|
||||
return self.__torrent_infohash
|
||||
:param torrent_contentlength: The target file size.
|
||||
:returns: The target file size.
|
||||
'''
|
||||
if torrent_contentlength is not None:
|
||||
self.__torrent_contentlength = torrent_contentlength
|
||||
return self.__torrent_contentlength
|
||||
|
||||
def contentlength (self, torrent_contentlength=None):
|
||||
'''Get or set the size of the target file.
|
||||
def seeds(self, torrent_seeds=None):
|
||||
'''Get or set the number of seeds.
|
||||
|
||||
:param torrent_contentlength: The target file size.
|
||||
:returns: The target file size.
|
||||
'''
|
||||
if not torrent_contentlength is None:
|
||||
self.__torrent_contentlength = torrent_contentlength
|
||||
return self.__torrent_contentlength
|
||||
:param torrent_seeds: The seeds number.
|
||||
:returns: The seeds number.
|
||||
'''
|
||||
if torrent_seeds is not None:
|
||||
self.__torrent_seeds = torrent_seeds
|
||||
return self.__torrent_seeds
|
||||
|
||||
def seeds (self, torrent_seeds=None):
|
||||
'''Get or set the number of seeds.
|
||||
def peers(self, torrent_peers=None):
|
||||
'''Get or set the number od peers
|
||||
|
||||
:param torrent_seeds: The seeds number.
|
||||
:returns: The seeds number.
|
||||
'''
|
||||
if not torrent_seeds is None:
|
||||
self.__torrent_seeds = torrent_seeds
|
||||
return self.__torrent_seeds
|
||||
:param torrent_infohash: The peers number.
|
||||
:returns: The peers number.
|
||||
'''
|
||||
if torrent_peers is not None:
|
||||
self.__torrent_peers = torrent_peers
|
||||
return self.__torrent_peers
|
||||
|
||||
def peers (self, torrent_peers=None):
|
||||
'''Get or set the number od peers
|
||||
def verified(self, torrent_verified=None):
|
||||
'''Get or set the number of verified peers.
|
||||
|
||||
:param torrent_infohash: The peers number.
|
||||
:returns: The peers number.
|
||||
'''
|
||||
if not torrent_peers is None:
|
||||
self.__torrent_peers = torrent_peers
|
||||
return self.__torrent_peers
|
||||
|
||||
def verified (self, torrent_verified=None):
|
||||
'''Get or set the number of verified peers.
|
||||
|
||||
:param torrent_infohash: The verified peers number.
|
||||
:returns: The verified peers number.
|
||||
'''
|
||||
if not torrent_verified is None:
|
||||
self.__torrent_verified = torrent_verified
|
||||
return self.__torrent_verified
|
||||
:param torrent_infohash: The verified peers number.
|
||||
:returns: The verified peers number.
|
||||
'''
|
||||
if torrent_verified is not None:
|
||||
self.__torrent_verified = torrent_verified
|
||||
return self.__torrent_verified
|
||||
|
|
2310
feedgen/feed.py
2310
feedgen/feed.py
File diff suppressed because it is too large
Load diff
112
feedgen/util.py
112
feedgen/util.py
|
@ -1,72 +1,74 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
'''
|
||||
feedgen.util
|
||||
~~~~~~~~~~~~
|
||||
feedgen.util
|
||||
~~~~~~~~~~~~
|
||||
|
||||
This file contains helper functions for the feed generator module.
|
||||
This file contains helper functions for the feed generator module.
|
||||
|
||||
:copyright: 2013, Lars Kiesow <lkiesow@uos.de>
|
||||
:license: FreeBSD and LGPL, see license.* for more details.
|
||||
:copyright: 2013, Lars Kiesow <lkiesow@uos.de>
|
||||
: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):
|
||||
'''Takes a dictionary or a list of dictionaries and check if all keys are in
|
||||
the set of allowed keys, if all required keys are present and if the values
|
||||
of a specific key are ok.
|
||||
'''Takes a dictionary or a list of dictionaries and check if all keys are in
|
||||
the set of allowed keys, if all required keys are present and if the values
|
||||
of a specific key are ok.
|
||||
|
||||
:param val: Dictionaries to check.
|
||||
:param allowed: Set of allowed keys.
|
||||
:param required: Set of required keys.
|
||||
:param allowed_values: Dictionary with keys and sets of their allowed values.
|
||||
:param defaults: Dictionary with default values.
|
||||
:returns: List of checked dictionaries.
|
||||
'''
|
||||
if not val:
|
||||
return None
|
||||
if allowed_values is None:
|
||||
allowed_values = {}
|
||||
if defaults is None:
|
||||
defaults = {}
|
||||
# Make shure that we have a list of dicts. Even if there is only one.
|
||||
if not isinstance(val, list):
|
||||
val = [val]
|
||||
for elem in val:
|
||||
if not isinstance(elem, dict):
|
||||
raise ValueError('Invalid data (value is no dictionary)')
|
||||
# Set default values
|
||||
:param val: Dictionaries to check.
|
||||
:param allowed: Set of allowed keys.
|
||||
:param required: Set of required keys.
|
||||
:param allowed_values: Dictionary with keys and sets of their allowed
|
||||
values.
|
||||
:param defaults: Dictionary with default values.
|
||||
:returns: List of checked dictionaries.
|
||||
'''
|
||||
if not val:
|
||||
return None
|
||||
if allowed_values is None:
|
||||
allowed_values = {}
|
||||
if defaults is None:
|
||||
defaults = {}
|
||||
# Make shure that we have a list of dicts. Even if there is only one.
|
||||
if not isinstance(val, list):
|
||||
val = [val]
|
||||
for elem in val:
|
||||
if not isinstance(elem, dict):
|
||||
raise ValueError('Invalid data (value is no dictionary)')
|
||||
# Set default values
|
||||
|
||||
version = sys.version_info[0]
|
||||
version = sys.version_info[0]
|
||||
|
||||
if version == 2:
|
||||
items = defaults.iteritems()
|
||||
else:
|
||||
items = defaults.items()
|
||||
if version == 2:
|
||||
items = defaults.iteritems()
|
||||
else:
|
||||
items = defaults.items()
|
||||
|
||||
for k,v in items:
|
||||
elem[k] = elem.get(k, v)
|
||||
if not set(elem.keys()) <= allowed:
|
||||
raise ValueError('Data contains invalid keys')
|
||||
if not set(elem.keys()) >= required:
|
||||
raise ValueError('Data contains not all required keys')
|
||||
for k, v in items:
|
||||
elem[k] = elem.get(k, v)
|
||||
if not set(elem.keys()) <= allowed:
|
||||
raise ValueError('Data contains invalid keys')
|
||||
if not set(elem.keys()) >= required:
|
||||
raise ValueError('Data contains not all required keys')
|
||||
|
||||
if version == 2:
|
||||
values = allowed_values.iteritems()
|
||||
else:
|
||||
values = allowed_values.items()
|
||||
if version == 2:
|
||||
values = allowed_values.iteritems()
|
||||
else:
|
||||
values = allowed_values.items()
|
||||
|
||||
for k,v in values:
|
||||
if elem.get(k) and not elem[k] in v:
|
||||
raise ValueError('Invalid value for %s' % k )
|
||||
return val
|
||||
for k, v in values:
|
||||
if elem.get(k) and not elem[k] in v:
|
||||
raise ValueError('Invalid value for %s' % k)
|
||||
return val
|
||||
|
||||
|
||||
def formatRFC2822(d):
|
||||
'''Make sure the locale setting do not interfere with the time format.
|
||||
'''
|
||||
l = locale.setlocale(locale.LC_ALL)
|
||||
locale.setlocale(locale.LC_ALL, 'C')
|
||||
d = d.strftime('%a, %d %b %Y %H:%M:%S %z')
|
||||
locale.setlocale(locale.LC_ALL, l)
|
||||
return d
|
||||
'''Make sure the locale setting do not interfere with the time format.
|
||||
'''
|
||||
l = locale.setlocale(locale.LC_ALL)
|
||||
locale.setlocale(locale.LC_ALL, 'C')
|
||||
d = d.strftime('%a, %d %b %Y %H:%M:%S %z')
|
||||
locale.setlocale(locale.LC_ALL, l)
|
||||
return d
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
'''
|
||||
feedgen.version
|
||||
~~~~~~~~~~~~~~~
|
||||
feedgen.version
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
:copyright: 2013-2017, Lars Kiesow <lkiesow@uos.de>
|
||||
|
||||
:license: FreeBSD and LGPL, see license.* for more details.
|
||||
:license: FreeBSD and LGPL, see license.* for more details.
|
||||
|
||||
'''
|
||||
|
||||
|
@ -18,8 +18,8 @@ version_str = '.'.join([str(x) for x in version])
|
|||
|
||||
version_major = version[:1]
|
||||
version_minor = version[:2]
|
||||
version_full = version
|
||||
version_full = version
|
||||
|
||||
version_major_str = '.'.join([str(x) for x in version_major])
|
||||
version_minor_str = '.'.join([str(x) for x in version_minor])
|
||||
version_full_str = '.'.join([str(x) for x in version_full])
|
||||
version_full_str = '.'.join([str(x) for x in version_full])
|
||||
|
|
63
setup.py
63
setup.py
|
@ -6,36 +6,36 @@ import feedgen.version
|
|||
|
||||
packages = ['feedgen', 'feedgen/ext']
|
||||
|
||||
setup(
|
||||
name = 'feedgen',
|
||||
packages = packages,
|
||||
version = feedgen.version.version_full_str,
|
||||
description = 'Feed Generator (ATOM, RSS, Podcasts)',
|
||||
author = 'Lars Kiesow',
|
||||
author_email = 'lkiesow@uos.de',
|
||||
url = 'http://lkiesow.github.io/python-feedgen',
|
||||
keywords = ['feed','ATOM','RSS','podcast'],
|
||||
license = 'FreeBSD and LGPLv3+',
|
||||
install_requires = ['lxml', 'python-dateutil'],
|
||||
classifiers = [
|
||||
'Development Status :: 5 - Production/Stable',
|
||||
'Intended Audience :: Developers',
|
||||
'Intended Audience :: Information Technology',
|
||||
'Intended Audience :: Science/Research',
|
||||
'License :: OSI Approved :: BSD License',
|
||||
'License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)',
|
||||
'Natural Language :: English',
|
||||
'Operating System :: OS Independent',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 2',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Topic :: Communications',
|
||||
'Topic :: Internet',
|
||||
'Topic :: Text Processing',
|
||||
'Topic :: Text Processing :: Markup',
|
||||
'Topic :: Text Processing :: Markup :: XML'
|
||||
],
|
||||
long_description = '''\
|
||||
setup(name='feedgen',
|
||||
packages=packages,
|
||||
version=feedgen.version.version_full_str,
|
||||
description='Feed Generator (ATOM, RSS, Podcasts)',
|
||||
author='Lars Kiesow',
|
||||
author_email='lkiesow@uos.de',
|
||||
url='http://lkiesow.github.io/python-feedgen',
|
||||
keywords=['feed', 'ATOM', 'RSS', 'podcast'],
|
||||
license='FreeBSD and LGPLv3+',
|
||||
install_requires=['lxml', 'python-dateutil'],
|
||||
classifiers=[
|
||||
'Development Status :: 5 - Production/Stable',
|
||||
'Intended Audience :: Developers',
|
||||
'Intended Audience :: Information Technology',
|
||||
'Intended Audience :: Science/Research',
|
||||
'License :: OSI Approved :: BSD License',
|
||||
'License :: OSI Approved :: GNU Lesser General Public License v3 ' +
|
||||
'or later (LGPLv3+)',
|
||||
'Natural Language :: English',
|
||||
'Operating System :: OS Independent',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 2',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Topic :: Communications',
|
||||
'Topic :: Internet',
|
||||
'Topic :: Text Processing',
|
||||
'Topic :: Text Processing :: Markup',
|
||||
'Topic :: Text Processing :: Markup :: XML'
|
||||
],
|
||||
long_description='''\
|
||||
Feedgenerator
|
||||
=============
|
||||
|
||||
|
@ -46,5 +46,4 @@ Podcasts.
|
|||
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
|
||||
at license.bsd and license.lgpl.
|
||||
'''
|
||||
)
|
||||
''')
|
||||
|
|
|
@ -7,100 +7,99 @@ These are test cases for a basic entry.
|
|||
"""
|
||||
|
||||
import unittest
|
||||
from lxml import etree
|
||||
from feedgen.feed import FeedGenerator
|
||||
|
||||
|
||||
class TestSequenceFunctions(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
def setUp(self):
|
||||
|
||||
fg = FeedGenerator()
|
||||
self.feedId = 'http://example.com'
|
||||
self.title = 'Some Testfeed'
|
||||
fg = FeedGenerator()
|
||||
self.feedId = 'http://example.com'
|
||||
self.title = 'Some Testfeed'
|
||||
|
||||
fg.id(self.feedId)
|
||||
fg.title(self.title)
|
||||
fg.id(self.feedId)
|
||||
fg.title(self.title)
|
||||
|
||||
fe = fg.add_entry()
|
||||
fe.id('http://lernfunk.de/media/654321/1')
|
||||
fe.title('The First Episode')
|
||||
fe = fg.add_entry()
|
||||
fe.id('http://lernfunk.de/media/654321/1')
|
||||
fe.title('The First Episode')
|
||||
|
||||
#Use also the different name add_item
|
||||
fe = fg.add_item()
|
||||
fe.id('http://lernfunk.de/media/654321/1')
|
||||
fe.title('The Second Episode')
|
||||
# Use also the different name add_item
|
||||
fe = fg.add_item()
|
||||
fe.id('http://lernfunk.de/media/654321/1')
|
||||
fe.title('The Second Episode')
|
||||
|
||||
fe = fg.add_entry()
|
||||
fe.id('http://lernfunk.de/media/654321/1')
|
||||
fe.title('The Third Episode')
|
||||
fe = fg.add_entry()
|
||||
fe.id('http://lernfunk.de/media/654321/1')
|
||||
fe.title('The Third Episode')
|
||||
|
||||
self.fg = fg
|
||||
self.fg = fg
|
||||
|
||||
def test_checkEntryNumbers(self):
|
||||
def test_checkEntryNumbers(self):
|
||||
|
||||
fg = self.fg
|
||||
assert len(fg.entry()) == 3
|
||||
fg = self.fg
|
||||
assert len(fg.entry()) == 3
|
||||
|
||||
def test_checkItemNumbers(self):
|
||||
def test_checkItemNumbers(self):
|
||||
|
||||
fg = self.fg
|
||||
assert len(fg.item()) == 3
|
||||
fg = self.fg
|
||||
assert len(fg.item()) == 3
|
||||
|
||||
def test_checkEntryContent(self):
|
||||
def test_checkEntryContent(self):
|
||||
|
||||
fg = self.fg
|
||||
assert len(fg.entry()) != None
|
||||
fg = self.fg
|
||||
assert fg.entry()
|
||||
|
||||
def test_removeEntryByIndex(self):
|
||||
fg = FeedGenerator()
|
||||
self.feedId = 'http://example.com'
|
||||
self.title = 'Some Testfeed'
|
||||
def test_removeEntryByIndex(self):
|
||||
fg = FeedGenerator()
|
||||
self.feedId = 'http://example.com'
|
||||
self.title = 'Some Testfeed'
|
||||
|
||||
fe = fg.add_entry()
|
||||
fe.id('http://lernfunk.de/media/654321/1')
|
||||
fe.title('The Third Episode')
|
||||
assert len(fg.entry()) == 1
|
||||
fg.remove_entry(0)
|
||||
assert len(fg.entry()) == 0
|
||||
fe = fg.add_entry()
|
||||
fe.id('http://lernfunk.de/media/654321/1')
|
||||
fe.title('The Third Episode')
|
||||
assert len(fg.entry()) == 1
|
||||
fg.remove_entry(0)
|
||||
assert len(fg.entry()) == 0
|
||||
|
||||
def test_removeEntryByEntry(self):
|
||||
fg = FeedGenerator()
|
||||
self.feedId = 'http://example.com'
|
||||
self.title = 'Some Testfeed'
|
||||
def test_removeEntryByEntry(self):
|
||||
fg = FeedGenerator()
|
||||
self.feedId = 'http://example.com'
|
||||
self.title = 'Some Testfeed'
|
||||
|
||||
fe = fg.add_entry()
|
||||
fe.id('http://lernfunk.de/media/654321/1')
|
||||
fe.title('The Third Episode')
|
||||
fe = fg.add_entry()
|
||||
fe.id('http://lernfunk.de/media/654321/1')
|
||||
fe.title('The Third Episode')
|
||||
|
||||
assert len(fg.entry()) == 1
|
||||
fg.remove_entry(fe)
|
||||
assert len(fg.entry()) == 0
|
||||
assert len(fg.entry()) == 1
|
||||
fg.remove_entry(fe)
|
||||
assert len(fg.entry()) == 0
|
||||
|
||||
def test_categoryHasDomain(self):
|
||||
fg = FeedGenerator()
|
||||
fg.title('some title')
|
||||
fg.link( href='http://www.dontcare.com', rel='alternate' )
|
||||
fg.description('description')
|
||||
fe = fg.add_entry()
|
||||
fe.id('http://lernfunk.de/media/654321/1')
|
||||
fe.title('some title')
|
||||
fe.category([
|
||||
{'term' : 'category',
|
||||
'scheme': 'http://www.somedomain.com/category',
|
||||
'label' : 'Category',
|
||||
}])
|
||||
def test_categoryHasDomain(self):
|
||||
fg = FeedGenerator()
|
||||
fg.title('some title')
|
||||
fg.link(href='http://www.dontcare.com', rel='alternate')
|
||||
fg.description('description')
|
||||
fe = fg.add_entry()
|
||||
fe.id('http://lernfunk.de/media/654321/1')
|
||||
fe.title('some title')
|
||||
fe.category([
|
||||
{'term': 'category',
|
||||
'scheme': 'http://www.somedomain.com/category',
|
||||
'label': 'Category',
|
||||
}])
|
||||
|
||||
result = fg.rss_str()
|
||||
assert b'domain="http://www.somedomain.com/category"' in result
|
||||
|
||||
def test_content_cdata_type(self):
|
||||
fg = FeedGenerator()
|
||||
fg.title('some title')
|
||||
fg.id('http://lernfunk.de/media/654322/1')
|
||||
fe = fg.add_entry()
|
||||
fe.id('http://lernfunk.de/media/654322/1')
|
||||
fe.title('some title')
|
||||
fe.content('content', type='CDATA')
|
||||
result = fg.atom_str()
|
||||
assert b'<content type="CDATA"><![CDATA[content]]></content>' in result
|
||||
result = fg.rss_str()
|
||||
assert b'domain="http://www.somedomain.com/category"' in result
|
||||
|
||||
def test_content_cdata_type(self):
|
||||
fg = FeedGenerator()
|
||||
fg.title('some title')
|
||||
fg.id('http://lernfunk.de/media/654322/1')
|
||||
fe = fg.add_entry()
|
||||
fe.id('http://lernfunk.de/media/654322/1')
|
||||
fe.title('some title')
|
||||
fe.content('content', type='CDATA')
|
||||
result = fg.atom_str()
|
||||
assert b'<content type="CDATA"><![CDATA[content]]></content>' in result
|
||||
|
|
|
@ -12,6 +12,8 @@ from lxml import etree
|
|||
|
||||
class TestExtensionSyndication(unittest.TestCase):
|
||||
|
||||
SYN_NS = {'sy': 'http://purl.org/rss/1.0/modules/syndication/'}
|
||||
|
||||
def setUp(self):
|
||||
self.fg = FeedGenerator()
|
||||
self.fg.load_extension('syndication')
|
||||
|
@ -20,14 +22,11 @@ class TestExtensionSyndication(unittest.TestCase):
|
|||
self.fg.description('description')
|
||||
|
||||
def test_update_period(self):
|
||||
for period_type in ('hourly', 'daily', 'weekly',
|
||||
'monthly', 'yearly'):
|
||||
for period_type in ('hourly', 'daily', 'weekly', 'monthly', 'yearly'):
|
||||
self.fg.syndication.update_period(period_type)
|
||||
root = etree.fromstring(self.fg.rss_str())
|
||||
a = root.xpath('/rss/channel/sy:UpdatePeriod',
|
||||
namespaces={
|
||||
'sy':'http://purl.org/rss/1.0/modules/syndication/'
|
||||
})
|
||||
namespaces=self.SYN_NS)
|
||||
assert a[0].text == period_type
|
||||
|
||||
def test_update_frequency(self):
|
||||
|
@ -35,19 +34,14 @@ class TestExtensionSyndication(unittest.TestCase):
|
|||
self.fg.syndication.update_frequency(frequency)
|
||||
root = etree.fromstring(self.fg.rss_str())
|
||||
a = root.xpath('/rss/channel/sy:UpdateFrequency',
|
||||
namespaces={
|
||||
'sy':'http://purl.org/rss/1.0/modules/syndication/'
|
||||
})
|
||||
namespaces=self.SYN_NS)
|
||||
assert a[0].text == str(frequency)
|
||||
|
||||
def test_update_base(self):
|
||||
base = '2000-01-01T12:00+00:00'
|
||||
self.fg.syndication.update_base(base)
|
||||
root = etree.fromstring(self.fg.rss_str())
|
||||
a = root.xpath('/rss/channel/sy:UpdateBase',
|
||||
namespaces={
|
||||
'sy':'http://purl.org/rss/1.0/modules/syndication/'
|
||||
})
|
||||
a = root.xpath('/rss/channel/sy:UpdateBase', namespaces=self.SYN_NS)
|
||||
assert a[0].text == base
|
||||
|
||||
|
||||
|
@ -61,17 +55,17 @@ class TestExtensionPodcast(unittest.TestCase):
|
|||
self.fg.description('description')
|
||||
|
||||
def test_category_new(self):
|
||||
self.fg.podcast.itunes_category([{'cat':'Technology',
|
||||
'sub':'Podcasting'}])
|
||||
self.fg.podcast.itunes_category([{'cat': 'Technology',
|
||||
'sub': 'Podcasting'}])
|
||||
self.fg.podcast.itunes_explicit('no')
|
||||
self.fg.podcast.itunes_complete('no')
|
||||
self.fg.podcast.itunes_new_feed_url('http://example.com/new-feed.rss')
|
||||
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())
|
||||
cat = root.xpath('/rss/channel/itunes:category/@text', namespaces=ns)
|
||||
scat = root.xpath('/rss/channel/itunes:category/itunes:category/@text',
|
||||
namespaces=ns)
|
||||
namespaces=ns)
|
||||
assert cat[0] == 'Technology'
|
||||
assert scat[0] == 'Podcasting'
|
||||
|
||||
|
@ -81,10 +75,10 @@ class TestExtensionPodcast(unittest.TestCase):
|
|||
self.fg.podcast.itunes_complete('no')
|
||||
self.fg.podcast.itunes_new_feed_url('http://example.com/new-feed.rss')
|
||||
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())
|
||||
cat = root.xpath('/rss/channel/itunes:category/@text', namespaces=ns)
|
||||
scat = root.xpath('/rss/channel/itunes:category/itunes:category/@text',
|
||||
namespaces=ns)
|
||||
namespaces=ns)
|
||||
assert cat[0] == 'Technology'
|
||||
assert scat[0] == 'Podcasting'
|
||||
|
|
|
@ -12,288 +12,314 @@ from lxml import etree
|
|||
from feedgen.feed import FeedGenerator
|
||||
from feedgen.ext.dc import DcExtension, DcEntryExtension
|
||||
|
||||
|
||||
class TestSequenceFunctions(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
def setUp(self):
|
||||
|
||||
fg = FeedGenerator()
|
||||
fg = FeedGenerator()
|
||||
|
||||
self.nsAtom = "http://www.w3.org/2005/Atom"
|
||||
self.nsRss = "http://purl.org/rss/1.0/modules/content/"
|
||||
self.nsAtom = "http://www.w3.org/2005/Atom"
|
||||
self.nsRss = "http://purl.org/rss/1.0/modules/content/"
|
||||
|
||||
self.feedId = 'http://lernfunk.de/media/654321'
|
||||
self.title = 'Some Testfeed'
|
||||
self.feedId = 'http://lernfunk.de/media/654321'
|
||||
self.title = 'Some Testfeed'
|
||||
|
||||
self.authorName = 'John Doe'
|
||||
self.authorMail = 'john@example.de'
|
||||
self.author = {'name': self.authorName,'email': self.authorMail}
|
||||
self.authorName = 'John Doe'
|
||||
self.authorMail = 'john@example.de'
|
||||
self.author = {'name': self.authorName, 'email': self.authorMail}
|
||||
|
||||
self.linkHref = 'http://example.com'
|
||||
self.linkRel = 'alternate'
|
||||
self.linkHref = 'http://example.com'
|
||||
self.linkRel = 'alternate'
|
||||
|
||||
self.logo = 'http://ex.com/logo.jpg'
|
||||
self.subtitle = 'This is a cool feed!'
|
||||
self.logo = 'http://ex.com/logo.jpg'
|
||||
self.subtitle = 'This is a cool feed!'
|
||||
|
||||
self.link2Href = 'http://larskiesow.de/test.atom'
|
||||
self.link2Rel = 'self'
|
||||
self.link2Href = 'http://larskiesow.de/test.atom'
|
||||
self.link2Rel = 'self'
|
||||
|
||||
self.language = 'en'
|
||||
self.language = 'en'
|
||||
|
||||
self.categoryTerm = 'This category term'
|
||||
self.categoryScheme = 'This category scheme'
|
||||
self.categoryLabel = 'This category label'
|
||||
self.categoryTerm = 'This category term'
|
||||
self.categoryScheme = 'This category scheme'
|
||||
self.categoryLabel = 'This category label'
|
||||
|
||||
self.cloudDomain = 'example.com'
|
||||
self.cloudPort = '4711'
|
||||
self.cloudPath = '/ws/example'
|
||||
self.cloudRegisterProcedure = 'registerProcedure'
|
||||
self.cloudProtocol = 'SOAP 1.1'
|
||||
self.cloudDomain = 'example.com'
|
||||
self.cloudPort = '4711'
|
||||
self.cloudPath = '/ws/example'
|
||||
self.cloudRegisterProcedure = 'registerProcedure'
|
||||
self.cloudProtocol = 'SOAP 1.1'
|
||||
|
||||
self.icon = "http://example.com/icon.png"
|
||||
self.contributor = {'name':"Contributor Name", 'uri':"Contributor Uri",
|
||||
'email': 'Contributor email'}
|
||||
self.copyright = "The copyright notice"
|
||||
self.docs = 'http://www.rssboard.org/rss-specification'
|
||||
self.managingEditor = 'mail@example.com'
|
||||
self.rating = '(PICS-1.1 "http://www.classify.org/safesurf/" 1 r (SS~~000 1))'
|
||||
self.skipDays = 'Tuesday'
|
||||
self.skipHours = 23
|
||||
self.icon = "http://example.com/icon.png"
|
||||
self.contributor = {'name': "Contributor Name",
|
||||
'uri': "Contributor Uri",
|
||||
'email': 'Contributor email'}
|
||||
self.copyright = "The copyright notice"
|
||||
self.docs = 'http://www.rssboard.org/rss-specification'
|
||||
self.managingEditor = 'mail@example.com'
|
||||
self.rating = '(PICS-1.1 "http://www.classify.org/safesurf/" ' + \
|
||||
'1 r (SS~~000 1))'
|
||||
self.skipDays = 'Tuesday'
|
||||
self.skipHours = 23
|
||||
|
||||
self.textInputTitle = "Text input title"
|
||||
self.textInputDescription = "Text input description"
|
||||
self.textInputName = "Text input name"
|
||||
self.textInputLink = "Text input link"
|
||||
self.textInputTitle = "Text input title"
|
||||
self.textInputDescription = "Text input description"
|
||||
self.textInputName = "Text input name"
|
||||
self.textInputLink = "Text input link"
|
||||
|
||||
self.ttl = 900
|
||||
self.ttl = 900
|
||||
|
||||
self.webMaster = 'webmaster@example.com'
|
||||
self.webMaster = 'webmaster@example.com'
|
||||
|
||||
fg.id(self.feedId)
|
||||
fg.title(self.title)
|
||||
fg.author(self.author)
|
||||
fg.link( href=self.linkHref, rel=self.linkRel )
|
||||
fg.logo(self.logo)
|
||||
fg.subtitle(self.subtitle)
|
||||
fg.link( href=self.link2Href, rel=self.link2Rel )
|
||||
fg.language(self.language)
|
||||
fg.cloud(domain=self.cloudDomain, port=self.cloudPort,
|
||||
path=self.cloudPath, registerProcedure=self.cloudRegisterProcedure,
|
||||
protocol=self.cloudProtocol)
|
||||
fg.icon(self.icon)
|
||||
fg.category(term=self.categoryTerm, scheme=self.categoryScheme,
|
||||
label=self.categoryLabel)
|
||||
fg.contributor(self.contributor)
|
||||
fg.copyright(self.copyright)
|
||||
fg.docs(docs=self.docs)
|
||||
fg.managingEditor(self.managingEditor)
|
||||
fg.rating(self.rating)
|
||||
fg.skipDays(self.skipDays)
|
||||
fg.skipHours(self.skipHours)
|
||||
fg.textInput(title=self.textInputTitle,
|
||||
description=self.textInputDescription, name=self.textInputName,
|
||||
link=self.textInputLink)
|
||||
fg.ttl(self.ttl)
|
||||
fg.webMaster(self.webMaster)
|
||||
fg.id(self.feedId)
|
||||
fg.title(self.title)
|
||||
fg.author(self.author)
|
||||
fg.link(href=self.linkHref, rel=self.linkRel)
|
||||
fg.logo(self.logo)
|
||||
fg.subtitle(self.subtitle)
|
||||
fg.link(href=self.link2Href, rel=self.link2Rel)
|
||||
fg.language(self.language)
|
||||
fg.cloud(domain=self.cloudDomain, port=self.cloudPort,
|
||||
path=self.cloudPath,
|
||||
registerProcedure=self.cloudRegisterProcedure,
|
||||
protocol=self.cloudProtocol)
|
||||
fg.icon(self.icon)
|
||||
fg.category(term=self.categoryTerm, scheme=self.categoryScheme,
|
||||
label=self.categoryLabel)
|
||||
fg.contributor(self.contributor)
|
||||
fg.copyright(self.copyright)
|
||||
fg.docs(docs=self.docs)
|
||||
fg.managingEditor(self.managingEditor)
|
||||
fg.rating(self.rating)
|
||||
fg.skipDays(self.skipDays)
|
||||
fg.skipHours(self.skipHours)
|
||||
fg.textInput(title=self.textInputTitle,
|
||||
description=self.textInputDescription,
|
||||
name=self.textInputName, link=self.textInputLink)
|
||||
fg.ttl(self.ttl)
|
||||
fg.webMaster(self.webMaster)
|
||||
|
||||
self.fg = fg
|
||||
self.fg = fg
|
||||
|
||||
def test_baseFeed(self):
|
||||
fg = self.fg
|
||||
|
||||
def test_baseFeed(self):
|
||||
fg = self.fg
|
||||
assert fg.id() == self.feedId
|
||||
assert fg.title() == self.title
|
||||
|
||||
assert fg.id() == self.feedId
|
||||
assert fg.title() == self.title
|
||||
assert fg.author()[0]['name'] == self.authorName
|
||||
assert fg.author()[0]['email'] == self.authorMail
|
||||
|
||||
assert fg.author()[0]['name'] == self.authorName
|
||||
assert fg.author()[0]['email'] == self.authorMail
|
||||
assert fg.link()[0]['href'] == self.linkHref
|
||||
assert fg.link()[0]['rel'] == self.linkRel
|
||||
|
||||
assert fg.link()[0]['href'] == self.linkHref
|
||||
assert fg.link()[0]['rel'] == self.linkRel
|
||||
assert fg.logo() == self.logo
|
||||
assert fg.subtitle() == self.subtitle
|
||||
|
||||
assert fg.logo() == self.logo
|
||||
assert fg.subtitle() == self.subtitle
|
||||
assert fg.link()[1]['href'] == self.link2Href
|
||||
assert fg.link()[1]['rel'] == self.link2Rel
|
||||
|
||||
assert fg.link()[1]['href'] == self.link2Href
|
||||
assert fg.link()[1]['rel'] == self.link2Rel
|
||||
assert fg.language() == self.language
|
||||
|
||||
assert fg.language() == self.language
|
||||
def test_atomFeedFile(self):
|
||||
fg = self.fg
|
||||
filename = 'tmp_Atomfeed.xml'
|
||||
fg.atom_file(filename=filename, pretty=True, xml_declaration=False)
|
||||
|
||||
def test_atomFeedFile(self):
|
||||
fg = self.fg
|
||||
filename = 'tmp_Atomfeed.xml'
|
||||
fg.atom_file(filename=filename, pretty=True, xml_declaration=False)
|
||||
with open(filename, "r") as myfile:
|
||||
atomString = myfile.read().replace('\n', '')
|
||||
|
||||
with open (filename, "r") as myfile:
|
||||
atomString=myfile.read().replace('\n', '')
|
||||
self.checkAtomString(atomString)
|
||||
|
||||
self.checkAtomString(atomString)
|
||||
def test_atomFeedString(self):
|
||||
fg = self.fg
|
||||
|
||||
def test_atomFeedString(self):
|
||||
fg = self.fg
|
||||
atomString = fg.atom_str(pretty=True, xml_declaration=False)
|
||||
self.checkAtomString(atomString)
|
||||
|
||||
atomString = fg.atom_str(pretty=True, xml_declaration=False)
|
||||
self.checkAtomString(atomString)
|
||||
def test_rel_values_for_atom(self):
|
||||
values_for_rel = [
|
||||
'about', 'alternate', 'appendix', 'archives', 'author', 'bookmark',
|
||||
'canonical', 'chapter', 'collection', 'contents', 'copyright',
|
||||
'create-form', 'current', 'derivedfrom', 'describedby',
|
||||
'describes', 'disclosure', 'duplicate', 'edit', 'edit-form',
|
||||
'edit-media', 'enclosure', 'first', 'glossary', 'help', 'hosts',
|
||||
'hub', 'icon', 'index', 'item', 'last', 'latest-version',
|
||||
'license', 'lrdd', 'memento', 'monitor', 'monitor-group', 'next',
|
||||
'next-archive', 'nofollow', 'noreferrer', 'original', 'payment',
|
||||
'predecessor-version', 'prefetch', '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']
|
||||
links = [{'href': '%s/%s' % (self.linkHref,
|
||||
val.replace('-', '_')), 'rel': val}
|
||||
for val in values_for_rel]
|
||||
fg = self.fg
|
||||
fg.link(links, replace=True)
|
||||
atomString = fg.atom_str(pretty=True, xml_declaration=False)
|
||||
feed = etree.fromstring(atomString)
|
||||
nsAtom = self.nsAtom
|
||||
feed_links = feed.findall("{%s}link" % nsAtom)
|
||||
idx = 0
|
||||
assert len(links) == len(feed_links)
|
||||
while idx < len(values_for_rel):
|
||||
assert feed_links[idx].get('href') == links[idx]['href']
|
||||
assert feed_links[idx].get('rel') == links[idx]['rel']
|
||||
idx += 1
|
||||
|
||||
def test_rel_values_for_atom(self):
|
||||
values_for_rel = [
|
||||
'about', 'alternate', 'appendix', 'archives', 'author', 'bookmark',
|
||||
'canonical', 'chapter', 'collection', 'contents', 'copyright', 'create-form',
|
||||
'current', 'derivedfrom', 'describedby', 'describes', 'disclosure',
|
||||
'duplicate', 'edit', 'edit-form', 'edit-media', 'enclosure', 'first', 'glossary',
|
||||
'help', 'hosts', 'hub', 'icon', 'index', 'item', 'last', 'latest-version', 'license',
|
||||
'lrdd', 'memento', 'monitor', 'monitor-group', 'next', 'next-archive', 'nofollow',
|
||||
'noreferrer', 'original', 'payment', 'predecessor-version', 'prefetch', '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'
|
||||
]
|
||||
links = [{'href': '%s/%s' % (self.linkHref, val.replace('-', '_')), 'rel': val} for val in values_for_rel]
|
||||
fg = self.fg
|
||||
fg.link(links, replace=True)
|
||||
atomString = fg.atom_str(pretty=True, xml_declaration=False)
|
||||
feed = etree.fromstring(atomString)
|
||||
nsAtom = self.nsAtom
|
||||
feed_links = feed.findall("{%s}link" % nsAtom)
|
||||
idx = 0
|
||||
assert len(links) == len(feed_links)
|
||||
while idx < len(values_for_rel):
|
||||
assert feed_links[idx].get('href') == links[idx]['href']
|
||||
assert feed_links[idx].get('rel') == links[idx]['rel']
|
||||
idx += 1
|
||||
def test_rel_values_for_rss(self):
|
||||
values_for_rel = [
|
||||
'about', 'alternate', 'appendix', 'archives', 'author', 'bookmark',
|
||||
'canonical', 'chapter', 'collection', 'contents', 'copyright',
|
||||
'create-form', 'current', 'derivedfrom', 'describedby',
|
||||
'describes', 'disclosure', 'duplicate', 'edit', 'edit-form',
|
||||
'edit-media', 'enclosure', 'first', 'glossary', 'help', 'hosts',
|
||||
'hub', 'icon', 'index', 'item', 'last', 'latest-version',
|
||||
'license', 'lrdd', 'memento', 'monitor', 'monitor-group', 'next',
|
||||
'next-archive', 'nofollow', 'noreferrer', 'original', 'payment',
|
||||
'predecessor-version', 'prefetch', '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']
|
||||
links = [{'href': '%s/%s' % (self.linkHref,
|
||||
val.replace('-', '_')), 'rel': val}
|
||||
for val in values_for_rel]
|
||||
fg = self.fg
|
||||
fg.link(links, replace=True)
|
||||
rssString = fg.rss_str(pretty=True, xml_declaration=False)
|
||||
feed = etree.fromstring(rssString)
|
||||
channel = feed.find("channel")
|
||||
nsAtom = self.nsAtom
|
||||
|
||||
def test_rel_values_for_rss(self):
|
||||
values_for_rel = [
|
||||
'about', 'alternate', 'appendix', 'archives', 'author', 'bookmark',
|
||||
'canonical', 'chapter', 'collection', 'contents', 'copyright', 'create-form',
|
||||
'current', 'derivedfrom', 'describedby', 'describes', 'disclosure',
|
||||
'duplicate', 'edit', 'edit-form', 'edit-media', 'enclosure', 'first', 'glossary',
|
||||
'help', 'hosts', 'hub', 'icon', 'index', 'item', 'last', 'latest-version', 'license',
|
||||
'lrdd', 'memento', 'monitor', 'monitor-group', 'next', 'next-archive', 'nofollow',
|
||||
'noreferrer', 'original', 'payment', 'predecessor-version', 'prefetch', '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'
|
||||
]
|
||||
links = [{'href': '%s/%s' % (self.linkHref, val.replace('-', '_')), 'rel': val} for val in values_for_rel]
|
||||
fg = self.fg
|
||||
fg.link(links, replace=True)
|
||||
rssString = fg.rss_str(pretty=True, xml_declaration=False)
|
||||
feed = etree.fromstring(rssString)
|
||||
channel = feed.find("channel")
|
||||
nsAtom = self.nsAtom
|
||||
atom_links = channel.findall("{%s}link" % nsAtom)
|
||||
# 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('rel') == 'self'
|
||||
|
||||
atom_links = channel.findall("{%s}link" % nsAtom)
|
||||
assert len(atom_links) == 1 # rss feed only implements atom's 'self' link
|
||||
assert atom_links[0].get('href') == '%s/%s' % (self.linkHref, 'self')
|
||||
assert atom_links[0].get('rel') == 'self'
|
||||
rss_links = channel.findall('link')
|
||||
# RSS only needs one URL. We use the first link for RSS:
|
||||
assert len(rss_links) == 1
|
||||
assert rss_links[0].text == '%s/%s' % \
|
||||
(self.linkHref, 'working-copy-of'.replace('-', '_'))
|
||||
|
||||
rss_links = channel.findall('link')
|
||||
assert len(rss_links) == 1 # 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('-', '_'))
|
||||
def checkAtomString(self, atomString):
|
||||
|
||||
def checkAtomString(self, atomString):
|
||||
feed = etree.fromstring(atomString)
|
||||
|
||||
feed = etree.fromstring(atomString)
|
||||
nsAtom = self.nsAtom
|
||||
|
||||
nsAtom = self.nsAtom
|
||||
assert feed.find("{%s}title" % nsAtom).text == self.title
|
||||
assert feed.find("{%s}updated" % nsAtom).text is not None
|
||||
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)\
|
||||
.get('label') == self.categoryLabel
|
||||
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)[1]\
|
||||
.get('href') == self.link2Href
|
||||
assert feed.findall("{%s}link" % nsAtom)[1].get('rel') == self.link2Rel
|
||||
assert feed.find("{%s}logo" % nsAtom).text == self.logo
|
||||
assert feed.find("{%s}icon" % nsAtom).text == self.icon
|
||||
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)\
|
||||
.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}title" % nsAtom).text == self.title
|
||||
assert feed.find("{%s}updated" % nsAtom).text != None
|
||||
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).get('label') == self.categoryLabel
|
||||
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)[1].get('href') == self.link2Href
|
||||
assert feed.findall("{%s}link" % nsAtom)[1].get('rel') == self.link2Rel
|
||||
assert feed.find("{%s}logo" % nsAtom).text == self.logo
|
||||
assert feed.find("{%s}icon" % nsAtom).text == self.icon
|
||||
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).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
|
||||
def test_rssFeedFile(self):
|
||||
fg = self.fg
|
||||
filename = 'tmp_Rssfeed.xml'
|
||||
fg.rss_file(filename=filename, pretty=True, xml_declaration=False)
|
||||
|
||||
def test_rssFeedFile(self):
|
||||
fg = self.fg
|
||||
filename = 'tmp_Rssfeed.xml'
|
||||
fg.rss_file(filename=filename, pretty=True, xml_declaration=False)
|
||||
with open(filename, "r") as myfile:
|
||||
rssString = myfile.read().replace('\n', '')
|
||||
|
||||
with open (filename, "r") as myfile:
|
||||
rssString=myfile.read().replace('\n', '')
|
||||
self.checkRssString(rssString)
|
||||
|
||||
self.checkRssString(rssString)
|
||||
def test_rssFeedString(self):
|
||||
fg = self.fg
|
||||
rssString = fg.rss_str(pretty=True, xml_declaration=False)
|
||||
self.checkRssString(rssString)
|
||||
|
||||
def test_rssFeedString(self):
|
||||
fg = self.fg
|
||||
rssString = fg.rss_str(pretty=True, xml_declaration=False)
|
||||
self.checkRssString(rssString)
|
||||
def test_loadPodcastExtension(self):
|
||||
fg = self.fg
|
||||
fg.add_entry()
|
||||
fg.load_extension('podcast', atom=True, rss=True)
|
||||
fg.add_entry()
|
||||
|
||||
def test_loadPodcastExtension(self):
|
||||
fg = self.fg
|
||||
fg.add_entry()
|
||||
fg.load_extension('podcast', atom=True, rss=True)
|
||||
fg.add_entry()
|
||||
def test_loadDcExtension(self):
|
||||
fg = self.fg
|
||||
fg.add_entry()
|
||||
fg.load_extension('dc', atom=True, rss=True)
|
||||
fg.add_entry()
|
||||
|
||||
def test_loadDcExtension(self):
|
||||
fg = self.fg
|
||||
fg.add_entry()
|
||||
fg.load_extension('dc', atom=True, rss=True)
|
||||
fg.add_entry()
|
||||
def test_extensionAlreadyLoaded(self):
|
||||
fg = self.fg
|
||||
fg.load_extension('dc', atom=True, rss=True)
|
||||
with self.assertRaises(ImportError):
|
||||
fg.load_extension('dc')
|
||||
|
||||
def test_extensionAlreadyLoaded(self):
|
||||
fg = self.fg
|
||||
fg.load_extension('dc', atom=True, rss=True)
|
||||
with self.assertRaises(ImportError) as context:
|
||||
fg.load_extension('dc')
|
||||
def test_registerCustomExtension(self):
|
||||
fg = self.fg
|
||||
fg.add_entry()
|
||||
fg.register_extension('dc', DcExtension, DcEntryExtension)
|
||||
fg.add_entry()
|
||||
|
||||
def test_registerCustomExtension(self):
|
||||
fg = self.fg
|
||||
fg.add_entry()
|
||||
fg.register_extension('dc', DcExtension, DcEntryExtension)
|
||||
fg.add_entry()
|
||||
def checkRssString(self, rssString):
|
||||
|
||||
def checkRssString(self, rssString):
|
||||
feed = etree.fromstring(rssString)
|
||||
nsAtom = self.nsAtom
|
||||
|
||||
feed = etree.fromstring(rssString)
|
||||
nsAtom = self.nsAtom
|
||||
nsRss = self.nsRss
|
||||
ch = feed.find("channel")
|
||||
assert ch is not None
|
||||
|
||||
channel = feed.find("channel")
|
||||
assert channel != 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__':
|
||||
unittest.main()
|
||||
unittest.main()
|
||||
|
|
Loading…
Reference in a new issue