diff --git a/.gitignore b/.gitignore index af64cfd..8fd7f17 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,7 @@ tmp_Rssfeed.xml # testing artifacts .coverage *.egg-info/ +build/ +dist/ +_build/ +/docs/ diff --git a/doc/conf.py b/doc/conf.py index 4df01a6..4f93ee5 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -249,7 +249,7 @@ # Example configuration for intersphinx: refer to the Python standard library. -intersphinx_mapping = {'http://docs.python.org/': None} +intersphinx_mapping = {'python': ('https://docs.python.org/3', None)} # Include the GitHub readme file in index.rst diff --git a/feedgen/entry.py b/feedgen/entry.py index 5dd21c2..a4cf504 100644 --- a/feedgen/entry.py +++ b/feedgen/entry.py @@ -54,7 +54,7 @@ def _add_text_elm(entry, data, name): ) # Add type description of the content if type_: - elm.attrib['type'] = type_ + elm.attrib['type'] = 'html' if type_ == 'CDATA' else type_ class FeedEntry(object): @@ -67,6 +67,7 @@ def __init__(self): # required self.__atom_id = None self.__atom_title = None + self.__atom_title_type = None self.__atom_updated = datetime.now(dateutil.tz.tzutc()) # recommended @@ -106,7 +107,10 @@ def atom_entry(self, extensions=True): raise ValueError('Required fields not set') id = xml_elem('id', entry) id.text = self.__atom_id - title = xml_elem('title', entry) + if self.__atom_title_type is not None: + title = xml_elem('title', entry, type=self.__atom_title_type) + else: + title = xml_elem('title', entry) title.text = self.__atom_title updated = xml_elem('updated', entry) updated.text = self.__atom_updated.isoformat() @@ -137,17 +141,17 @@ def atom_entry(self, extensions=True): _add_text_elm(entry, self.__atom_content, 'content') for link in self.__atom_link or []: - link = xml_elem('link', entry, href=link['href']) + _link = xml_elem('link', entry, href=link['href']) if link.get('rel'): - link.attrib['rel'] = link['rel'] + _link.attrib['rel'] = link['rel'] if link.get('type'): - link.attrib['type'] = link['type'] + _link.attrib['type'] = link['type'] if link.get('hreflang'): - link.attrib['hreflang'] = link['hreflang'] + _link.attrib['hreflang'] = link['hreflang'] if link.get('title'): - link.attrib['title'] = link['title'] + _link.attrib['title'] = link['title'] if link.get('length'): - link.attrib['length'] = link['length'] + _link.attrib['length'] = link['length'] _add_text_elm(entry, self.__atom_summary, 'summary') @@ -260,7 +264,7 @@ def rss_entry(self, extensions=True): return entry - def title(self, title=None): + def title(self, title=None, ttype=None): '''Get or set the title value of the entry. It should contain a human readable title for the entry. Title is mandatory for both ATOM and RSS and should not be blank. @@ -271,6 +275,9 @@ def title(self, title=None): if title is not None: self.__atom_title = title self.__rss_title = title + if ttype not in ('text', 'html', 'xhtml', None): + raise ValueError('title type must be text, html, or xhtml') + self.__atom_title_type = ttype return self.__atom_title def id(self, id=None): diff --git a/feedgen/version.py b/feedgen/version.py index c8f76bf..e48d661 100644 --- a/feedgen/version.py +++ b/feedgen/version.py @@ -1,25 +1,26 @@ # -*- coding: utf-8 -*- -''' - feedgen.version - ~~~~~~~~~~~~~~~ +""" +feedgen.version +~~~~~~~~~~~~~~~ - :copyright: 2013-2018, Lars Kiesow +:copyright: 2013-2018, Lars Kiesow +:copyright: 2026, Fabio Manganiello - :license: FreeBSD and LGPL, see license.* for more details. +:license: FreeBSD and LGPL, see license.* for more details. -''' +""" -'Version of python-feedgen represented as tuple' -version = (1, 0, 0) +"Version of feedgen2 represented as tuple" +version = (2, 0, 1) -'Version of python-feedgen represented as string' -version_str = '.'.join([str(x) for x in version]) +"Version of feedgen2 represented as string" +version_str = ".".join([str(x) for x in version]) version_major = version[:1] version_minor = version[:2] 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_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]) diff --git a/python-feedgen.spec b/python-feedgen.spec index a137396..0a7da15 100644 --- a/python-feedgen.spec +++ b/python-feedgen.spec @@ -1,5 +1,5 @@ -%global pypi_name feedgen -%global pypi_version 1.0.0 +%global pypi_name feedgen2 +%global pypi_version 2.0.1 Name: python-%{pypi_name} Version: %{pypi_version} @@ -7,8 +7,7 @@ Release: 1%{?dist} Summary: Feed Generator (ATOM, RSS, Podcasts) License: BSD or LGPLv3 -URL: http://lkiesow.github.io/python-feedgen -#Source0: https://github.com/lkiesow/%{name}/archive/v%{version}.tar.gz +URL: https://github.com/blacklight/python-feedgen Source0: %{pypi_source} BuildArch: noarch @@ -56,6 +55,17 @@ rm -rf %{pypi_name}.egg-info %{python3_sitelib}/%{pypi_name}-%{version}-py?.?.egg-info %changelog +* Tue Feb 17 2026 Fabio Manganiello - 2.0.1-1 +- Update to 2.0.1 +- Updated README + +* Tue Feb 17 2026 Fabio Manganiello - 2.0.0-1 +- Update to 2.0.0 +- Fork from https://github.com/lkiesow/python-feedgen +- Applied https://github.com/lkiesow/python-feedgen/pull/139 +- Applied https://github.com/lkiesow/python-feedgen/pull/123 +- Applied https://github.com/lkiesow/python-feedgen/pull/137 + * Mon Dec 25 2023 Lars Kiesow - 1.0.0-1 - Update to 1.0.0 - Removing support for Python 2 diff --git a/readme.rst b/readme.rst index 3edeaff..5d2c200 100644 --- a/readme.rst +++ b/readme.rst @@ -2,6 +2,18 @@ Feedgenerator ============= +------------ + +**NOTE**: This is a fork of `python-feedgen +`_, as the original version is no +longer maintained. + +It is not going to implement many major features, but it implements fixes and +improvements over the original version, it's compatible with newer versions of +its dependencies and it is fully compatible with the original version. + +------------ + 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. @@ -21,19 +33,11 @@ More details about the project: Installation ------------ -**Prebuild packages** - -If your distribution includes this project as package, like Fedora Linux does, -you can simply use your package manager to install the package. For example:: - - $ dnf install python3-feedgen - - **Using pip** You can also use pip to install the feedgen module. Simply run:: - $ pip install feedgen + $ pip install feedgen2 ------------- diff --git a/setup.py b/setup.py index a5e2021..01089d3 100755 --- a/setup.py +++ b/setup.py @@ -5,39 +5,40 @@ import feedgen.version -packages = ['feedgen', 'feedgen/ext'] +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='https://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' - ], - test_suite="tests", - long_description='''\ +setup( + name="feedgen2", + packages=packages, + version=feedgen.version.version_full_str, + description="Feed Generator (ATOM, RSS, Podcasts)", + author="Fabio Manganiello", + author_email="fabio@manganiello.tech", + url="https://github.com/blacklight/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", + ], + test_suite="tests", + long_description="""\ Feedgenerator ============= @@ -48,4 +49,5 @@ 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. -''') +""", +) diff --git a/tests/test_entry.py b/tests/test_entry.py index adfd8b5..7966b48 100644 --- a/tests/test_entry.py +++ b/tests/test_entry.py @@ -15,29 +15,29 @@ class TestSequenceFunctions(unittest.TestCase): def setUp(self): fg = FeedGenerator() - self.feedId = 'http://example.com' - self.title = 'Some Testfeed' + self.feedId = "http://example.com" + self.title = "Some Testfeed" fg.id(self.feedId) fg.title(self.title) - fg.link(href='http://lkiesow.de', rel='alternate')[0] - fg.description('...') + fg.link(href="http://lkiesow.de", rel="alternate")[0] + fg.description("...") fe = fg.add_entry() - fe.id('http://lernfunk.de/media/654321/1') - fe.title('The First Episode') - fe.content(u'…') + fe.id("http://lernfunk.de/media/654321/1") + fe.title("The First Episode") + fe.content("…") # 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.content(u'…') + fe.id("http://lernfunk.de/media/654321/1") + fe.title("The Second Episode") + fe.content("…") fe = fg.add_entry() - fe.id('http://lernfunk.de/media/654321/1') - fe.title('The Third Episode') - fe.content(u'…') + fe.id("http://lernfunk.de/media/654321/1") + fe.title("The Third Episode") + fe.content("…") self.fg = fg @@ -49,10 +49,10 @@ def test_setEntries(self): def test_loadExtension(self): fe = self.fg.add_item() - fe.id('1') - fe.title(u'…') - fe.content(u'…') - fe.load_extension('base') + fe.id("1") + fe.title("…") + fe.content("…") + fe.load_extension("base") self.assertTrue(fe.base) self.assertTrue(self.fg.atom_str()) @@ -62,42 +62,41 @@ def test_checkEntryNumbers(self): def test_TestEntryItems(self): fe = self.fg.add_item() - fe.title('qwe') - self.assertEqual(fe.title(), 'qwe') - author = fe.author(email='ldoe@example.com')[0] - self.assertFalse(author.get('name')) - self.assertEqual(author.get('email'), 'ldoe@example.com') - author = fe.author(name='John Doe', email='jdoe@example.com', - replace=True)[0] - self.assertEqual(author.get('name'), 'John Doe') - self.assertEqual(author.get('email'), 'jdoe@example.com') - contributor = fe.contributor(name='John Doe', email='jdoe@ex.com')[0] + fe.title("qwe") + self.assertEqual(fe.title(), "qwe") + author = fe.author(email="ldoe@example.com")[0] + self.assertFalse(author.get("name")) + self.assertEqual(author.get("email"), "ldoe@example.com") + author = fe.author(name="John Doe", email="jdoe@example.com", replace=True)[0] + self.assertEqual(author.get("name"), "John Doe") + self.assertEqual(author.get("email"), "jdoe@example.com") + contributor = fe.contributor(name="John Doe", email="jdoe@ex.com")[0] self.assertEqual(contributor, fe.contributor()[0]) - self.assertEqual(contributor.get('name'), 'John Doe') - self.assertEqual(contributor.get('email'), 'jdoe@ex.com') - link = fe.link(href='http://lkiesow.de', rel='alternate')[0] + self.assertEqual(contributor.get("name"), "John Doe") + self.assertEqual(contributor.get("email"), "jdoe@ex.com") + link = fe.link(href="http://lkiesow.de", rel="alternate")[0] self.assertEqual(link, fe.link()[0]) - self.assertEqual(link.get('href'), 'http://lkiesow.de') - self.assertEqual(link.get('rel'), 'alternate') - fe.guid('123') - self.assertEqual(fe.guid().get('guid'), '123') - fe.updated('2017-02-05 13:26:58+01:00') + self.assertEqual(link.get("href"), "http://lkiesow.de") + self.assertEqual(link.get("rel"), "alternate") + fe.guid("123") + self.assertEqual(fe.guid().get("guid"), "123") + fe.updated("2017-02-05 13:26:58+01:00") self.assertEqual(fe.updated().year, 2017) - fe.summary('asdf') - self.assertEqual(fe.summary(), {'summary': 'asdf'}) - fe.description('asdfx') - self.assertEqual(fe.description(), 'asdfx') - fe.pubDate('2017-02-05 13:26:58+01:00') + fe.summary("asdf") + self.assertEqual(fe.summary(), {"summary": "asdf"}) + fe.description("asdfx") + self.assertEqual(fe.description(), "asdfx") + fe.pubDate("2017-02-05 13:26:58+01:00") self.assertEqual(fe.pubDate().year, 2017) - fe.rights('asdfx') - self.assertEqual(fe.rights(), 'asdfx') - source = fe.source(url='https://example.com', title='Test') - self.assertEqual(source.get('title'), 'Test') - self.assertEqual(source.get('url'), 'https://example.com') - fe.comments('asdfx') - self.assertEqual(fe.comments(), 'asdfx') - fe.enclosure(url='http://lkiesow.de', type='text/plain', length='1') - self.assertEqual(fe.enclosure().get('url'), 'http://lkiesow.de') + fe.rights("asdfx") + self.assertEqual(fe.rights(), "asdfx") + source = fe.source(url="https://example.com", title="Test") + self.assertEqual(source.get("title"), "Test") + self.assertEqual(source.get("url"), "https://example.com") + fe.comments("asdfx") + self.assertEqual(fe.comments(), "asdfx") + fe.enclosure(url="http://lkiesow.de", type="text/plain", length="1") + self.assertEqual(fe.enclosure().get("url"), "http://lkiesow.de") fe.ttl(8) self.assertEqual(fe.ttl(), 8) @@ -114,24 +113,24 @@ def test_checkEntryContent(self): def test_removeEntryByIndex(self): fg = FeedGenerator() - self.feedId = 'http://example.com' - self.title = 'Some Testfeed' + 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.id("http://lernfunk.de/media/654321/1") + fe.title("The Third Episode") self.assertEqual(len(fg.entry()), 1) fg.remove_entry(0) self.assertEqual(len(fg.entry()), 0) def test_removeEntryByEntry(self): fg = FeedGenerator() - self.feedId = 'http://example.com' - self.title = 'Some Testfeed' + 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.id("http://lernfunk.de/media/654321/1") + fe.title("The Third Episode") self.assertEqual(len(fg.entry()), 1) fg.remove_entry(fe) @@ -139,42 +138,46 @@ def test_removeEntryByEntry(self): def test_categoryHasDomain(self): fg = FeedGenerator() - fg.title('some title') - fg.link(href='http://www.dontcare.com', rel='alternate') - fg.description('description') + 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://somedomain.com/category', - 'label': 'Category', - }]) + fe.id("http://lernfunk.de/media/654321/1") + fe.title("some title") + fe.category( + [ + { + "term": "category", + "scheme": "http://somedomain.com/category", + "label": "Category", + } + ] + ) result = fg.rss_str() self.assertIn(b'domain="http://somedomain.com/category"', result) def test_content_cdata_type(self): fg = FeedGenerator() - fg.title('some title') - fg.id('http://lernfunk.de/media/654322/1') + 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') + fe.id("http://lernfunk.de/media/654322/1") + fe.title("some title") + fe.content("content", type="CDATA") result = fg.atom_str() expected = b'' self.assertIn(expected, result) def test_summary_html_type(self): fg = FeedGenerator() - fg.title('some title') - fg.id('http://lernfunk.de/media/654322/1') + 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.link(href='http://lernfunk.de/media/654322/1') - fe.summary('

summary

', type='html') + fe.id("http://lernfunk.de/media/654322/1") + fe.title("some title") + fe.link(href="http://lernfunk.de/media/654322/1") + fe.summary("

summary

", type="html") result = fg.atom_str() expected = b'<p>summary</p>' self.assertIn(expected, result) diff --git a/tests/test_feed.py b/tests/test_feed.py index d09014d..97ce363 100644 --- a/tests/test_feed.py +++ b/tests/test_feed.py @@ -75,6 +75,11 @@ def setUp(self): self.webMaster = 'webmaster@example.com' + self.entry1id = 'http://example.com/1' + self.link2Entry1length = 123456 + self.link2entry1type = 'audio/opus' + self.link2entry1url = 'http://example.com/enclosure.opus' + fg.id(self.feedId) fg.title(self.title) fg.author(self.author) @@ -112,6 +117,13 @@ def setUp(self): height='123', description='Example Inage') + fe = fg.add_entry() + fe.id(self.entry1id) + fe.title('Some Testfeed') + fe.updated('2024-02-05 13:26:58+01:00') + fe.link(href=self.entry1id, rel=self.linkRel) + fe.enclosure(url=self.link2entry1url, length=('%d' % self.link2Entry1length), type=self.link2entry1type) + self.fg = fg def test_baseFeed(self): @@ -284,6 +296,30 @@ def checkAtomString(self, atomString): ), ( feed.find(f"{nsAtom}rights").text, self.copyright + ), ( + feed.findall(f"{nsAtom}entry")[0].find(f"{nsAtom}id").text, + self.entry1id + ), ( + len(feed.findall(f"{nsAtom}entry")[0].findall(f"{nsAtom}link")), + 2 + ), ( + feed.findall(f"{nsAtom}entry")[0].findall(f"{nsAtom}link")[0].get("href"), + self.entry1id + ), ( + feed.findall(f"{nsAtom}entry")[0].findall(f"{nsAtom}link")[0].get("rel"), + self.linkRel + ), ( + feed.findall(f"{nsAtom}entry")[0].findall(f"{nsAtom}link")[1].get("href"), + self.link2entry1url + ), ( + feed.findall(f"{nsAtom}entry")[0].findall(f"{nsAtom}link")[1].get("rel"), + 'enclosure' + ), ( + feed.findall(f"{nsAtom}entry")[0].findall(f"{nsAtom}link")[1].get("type"), + self.link2entry1type + ), ( + int(feed.findall(f"{nsAtom}entry")[0].findall(f"{nsAtom}link")[1].get("length")), + self.link2Entry1length )] for actual, expected in testcases: self.assertEqual(actual, expected)