redturtle.rssservice 2.2.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,334 @@
1
+ # -*- coding: utf-8 -*-
2
+ from plone import api
3
+ from plone.app.testing import setRoles
4
+ from plone.app.testing import SITE_OWNER_NAME
5
+ from plone.app.testing import SITE_OWNER_PASSWORD
6
+ from plone.app.testing import TEST_USER_ID
7
+ from plone.restapi.testing import RelativeSession
8
+ from redturtle.rssservice.rss_mixer import FEED_DATA
9
+ from redturtle.rssservice.testing import REDTURTLE_RSSSERVICE_API_FUNCTIONAL_TESTING
10
+ from requests.exceptions import Timeout
11
+ from transaction import commit
12
+ from unittest import mock
13
+
14
+ import unittest
15
+
16
+ EXAMPLE_FEED_FOO = """
17
+ <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
18
+ <channel>
19
+ <atom:link rel="self" type="application/rss+xml" href="http://test.com/RSS"></atom:link>
20
+ <title>RSS FOO</title>
21
+ <link>http://test.com/RSS</link>
22
+ <language>en</language>
23
+ <item>
24
+ <title><![CDATA[Foo News 1]]></title>
25
+ <description><![CDATA[some description]]></description>
26
+ <link>http://test.com/foo-news-1</link>
27
+ <pubDate>Thu, 2 Apr 2020 10:44:01 +0200</pubDate>
28
+ <guid>http://test.com/foo-news-1</guid>
29
+ </item>
30
+ <item>
31
+ <title><![CDATA[Foo News 2]]></title>
32
+ <description><![CDATA[some description 2]]></description>
33
+ <link>http://test.com/foo-news-2</link>
34
+ <pubDate>Thu, 1 Apr 2020 10:44:01 +0200</pubDate>
35
+ <guid>http://test.com/foo-news-2</guid>
36
+ </item>
37
+ </channel>
38
+ </rss>
39
+ """
40
+
41
+ EXAMPLE_FEED_BAR = """
42
+ <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
43
+ <channel>
44
+ <atom:link rel="self" type="application/rss+xml" href="http://test.com/RSS"></atom:link>
45
+ <title>RSS BAR</title>
46
+ <link>http://test.com/RSS</link>
47
+ <language>en</language>
48
+ <item>
49
+ <title><![CDATA[Bar News 1]]></title>
50
+ <description><![CDATA[some description]]></description>
51
+ <link>http://test.com/bar-news-1</link>
52
+ <pubDate>Thu, 2 Apr 2020 10:44:01 +0200</pubDate>
53
+ <guid>http://test.com/bar-news-1</guid>
54
+ </item>
55
+ <item>
56
+ <title><![CDATA[Bar News 2]]></title>
57
+ <description><![CDATA[some description 2]]></description>
58
+ <link>http://test.com/bar-news-2</link>
59
+ <pubDate>Thu, 1 Apr 2020 10:44:01 +0200</pubDate>
60
+ <guid>http://test.com/bar-news-2</guid>
61
+ </item>
62
+ </channel>
63
+ </rss>
64
+ """
65
+
66
+ EXAMPLE_FEED_FOO_UPDATED = """
67
+ <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
68
+ <channel>
69
+ <atom:link rel="self" type="application/rss+xml" href="http://test.com/RSS"></atom:link>
70
+ <title>RSS FOO</title>
71
+ <link>http://test.com/RSS</link>
72
+ <language>en</language>
73
+ <item>
74
+ <title><![CDATA[Foo News 1 UPDATED]]></title>
75
+ <description><![CDATA[some description]]></description>
76
+ <link>http://test.com/foo-news-1</link>
77
+ <pubDate>Thu, 2 Apr 2020 10:44:01 +0200</pubDate>
78
+ <guid>http://test.com/foo-news-1</guid>
79
+ </item>
80
+ <item>
81
+ <title><![CDATA[Foo News 2 UPDATED]]></title>
82
+ <description><![CDATA[some description 2]]></description>
83
+ <link>http://test.com/foo-news-2</link>
84
+ <pubDate>Thu, 1 Apr 2020 10:44:01 +0200</pubDate>
85
+ <guid>http://test.com/foo-news-2</guid>
86
+ </item>
87
+ </channel>
88
+ </rss>
89
+ """
90
+
91
+ EXAMPLE_FEED_WRONG_DATE_FORMAT = """
92
+ <rss version="2.0">
93
+ <channel xmlns:atom="http://www.w3.org/2005/Atom">
94
+ <title>Eventi del comune di XYZ</title>
95
+ <link>https://www.xyz.it/</link>
96
+ <description>RSS Eventi del comune di XYZ</description>
97
+ <image>
98
+ <url>https://www.XYZ.it/logo.png</url>
99
+ <title>Eventi del comune di XYZ</title>
100
+ <link>https://www.xyz.it/</link>
101
+ </image>
102
+ <atom:link href="https://www.xyz.it" rel="self" type="application/rss+xml"/>
103
+ <item>
104
+ <guid isPermaLink="true">https://www.xyz.it/event.html</guid>
105
+ <link>https://www.xyz.it/event.html</link>
106
+ <title>Event Title</title>
107
+ <description>Event Description</description>
108
+ <enclosure type="image/jpg" url="https://www.xyz.it/image" width="700" height="470"/>
109
+ <pubDate>07/03/2022 17.30 - 07/03/2022 19.00</pubDate>
110
+ </item>
111
+ </channel>
112
+ </rss>
113
+ """
114
+
115
+ EXAMPLE_FEED_WITH_CATEGORIES = """
116
+ <rss version="2.0">
117
+ <channel xmlns:atom="http://www.w3.org/2005/Atom">
118
+ <title>Eventi del comune di XYZ</title>
119
+ <link>https://www.xyz.it/</link>
120
+ <description>RSS Eventi del comune di XYZ</description>
121
+ <image>
122
+ <url>https://www.XYZ.it/logo.png</url>
123
+ <title>Eventi del comune di XYZ</title>
124
+ <link>https://www.xyz.it/</link>
125
+ </image>
126
+ <atom:link href="https://www.xyz.it" rel="self" type="application/rss+xml"/>
127
+ <item>
128
+ <guid isPermaLink="true">https://www.xyz.it/event.html</guid>
129
+ <link>https://www.xyz.it/event.html</link>
130
+ <title>Event Title</title>
131
+ <category>Category C</category>
132
+ <description>Event Description</description>
133
+ <enclosure type="image/jpg" url="https://www.xyz.it/image" width="700" height="470"/>
134
+ <pubDate>07/03/2022 17.30 - 07/03/2022 19.00</pubDate>
135
+ </item>
136
+ <item>
137
+ <guid isPermaLink="true">https://www.xyz.it/event.html</guid>
138
+ <link>https://www.xyz.it/event.html</link>
139
+ <title>Event Title</title>
140
+ <category>Category A</category>
141
+ <category>Category B</category>
142
+ <description>Event Description</description>
143
+ <enclosure type="image/jpg" url="https://www.xyz.it/image" width="700" height="470"/>
144
+ <pubDate>07/03/2022 17.30 - 07/03/2022 19.00</pubDate>
145
+ </item>
146
+
147
+ </channel>
148
+ </rss>
149
+ """
150
+
151
+
152
+ def mocked_requests_get(*args, **kwargs):
153
+ class MockResponse:
154
+ def __init__(self, text, status_code, reason=""):
155
+ self.text = text
156
+ self.content = text
157
+ self.status_code = status_code
158
+ self.reason = reason
159
+
160
+ def text(self):
161
+ return self.text
162
+
163
+ def content(self):
164
+ return self.content
165
+
166
+ if args[0] == "http://foo.com/RSS":
167
+ return MockResponse(text=EXAMPLE_FEED_FOO, status_code=200)
168
+ if args[0] == "http://bar.com/RSS":
169
+ return MockResponse(text=EXAMPLE_FEED_BAR, status_code=200)
170
+ if args[0] == "http://test.com/timeout/RSS":
171
+ raise Timeout
172
+ if args[0] == "http://wrongdate.com/RSS":
173
+ return MockResponse(text=EXAMPLE_FEED_WRONG_DATE_FORMAT, status_code=200)
174
+ if args[0] == "http://categories.com/RSS":
175
+ return MockResponse(text=EXAMPLE_FEED_WITH_CATEGORIES, status_code=200)
176
+ return MockResponse(text="Not Found", status_code=404)
177
+
178
+
179
+ class RSSSMixerTest(unittest.TestCase):
180
+ layer = REDTURTLE_RSSSERVICE_API_FUNCTIONAL_TESTING
181
+
182
+ def setUp(self):
183
+ self.app = self.layer["app"]
184
+ self.portal = self.layer["portal"]
185
+ self.portal_url = self.portal.absolute_url()
186
+ setRoles(self.portal, TEST_USER_ID, ["Manager"])
187
+
188
+ self.api_session = RelativeSession(self.portal_url)
189
+ self.api_session.headers.update({"Accept": "application/json"})
190
+ self.api_session.auth = (SITE_OWNER_NAME, SITE_OWNER_PASSWORD)
191
+
192
+ self.page = api.content.create(
193
+ type="Document",
194
+ title="Page",
195
+ container=self.portal,
196
+ blocks={
197
+ "foo": {"@type": "foo"},
198
+ "rss-block-id-single": {
199
+ "@type": "rssBlock",
200
+ "limit": 10,
201
+ "feeds": [
202
+ {"url": "http://foo.com/RSS"},
203
+ ],
204
+ },
205
+ "rss-block-id": {
206
+ "@type": "rssBlock",
207
+ "limit": 10,
208
+ "feeds": [
209
+ {"url": "http://foo.com/RSS"},
210
+ {"url": "http://bar.com/RSS"},
211
+ ],
212
+ },
213
+ "rss-block-id-with-source": {
214
+ "@type": "rssBlock",
215
+ "limit": 10,
216
+ "feeds": [
217
+ {"url": "http://foo.com/RSS", "source": "Foo site"},
218
+ {"url": "http://bar.com/RSS"},
219
+ ],
220
+ },
221
+ "rss-block-id-wrong-date": {
222
+ "@type": "rssBlock",
223
+ "limit": 10,
224
+ "feeds": [
225
+ {"url": "http://wrongdate.com/RSS"},
226
+ ],
227
+ },
228
+ "rss-block-id-catagories": {
229
+ "@type": "rssBlock",
230
+ "limit": 10,
231
+ "feeds": [
232
+ {"url": "http://categories.com/RSS"},
233
+ ],
234
+ },
235
+ },
236
+ )
237
+
238
+ commit()
239
+
240
+ def tearDown(self):
241
+ # invalidate cache
242
+ for feed in FEED_DATA.values():
243
+ feed._last_update_time_in_minutes = 0
244
+ self.api_session.close()
245
+
246
+ def get_feed_data(self, block_id):
247
+ response = self.api_session.get(
248
+ "{}/@rss_mixer_data?block={}".format(self.page.absolute_url(), block_id)
249
+ )
250
+ return response.json()
251
+
252
+ def test_block_parameter_is_required(self):
253
+ response = self.api_session.get("/@rss_mixer_data")
254
+ self.assertEqual(response.status_code, 400)
255
+ self.assertEqual(
256
+ response.json()["message"], "Missing required parameter: block"
257
+ )
258
+
259
+ response = self.api_session.get("/@rss_mixer_data?foo=bar")
260
+ self.assertEqual(response.status_code, 400)
261
+ self.assertEqual(
262
+ response.json()["message"], "Missing required parameter: block"
263
+ )
264
+
265
+ def test_block_parameter_should_be_a_valid_block_in_context(self):
266
+ response = self.api_session.get("/@rss_mixer_data?block=xxx")
267
+ self.assertEqual(response.status_code, 404)
268
+
269
+ response = self.api_session.get("/@rss_mixer_data?block=rss-block-id")
270
+ self.assertEqual(response.status_code, 404)
271
+
272
+ response = self.api_session.get(
273
+ "{}/@rss_mixer_data?block=xxx".format(self.page.absolute_url())
274
+ )
275
+ self.assertEqual(response.status_code, 404)
276
+
277
+ response = self.api_session.get(
278
+ "{}/@rss_mixer_data?block=rss-block-id".format(self.page.absolute_url())
279
+ )
280
+ self.assertEqual(response.status_code, 200)
281
+
282
+ def test_block_parameter_should_be_an_rss_block_in_context(self):
283
+ response = self.api_session.get(
284
+ "{}/@rss_mixer_data?block=foo".format(self.page.absolute_url())
285
+ )
286
+ self.assertEqual(response.status_code, 400)
287
+ self.assertEqual(
288
+ response.json()["message"],
289
+ 'Block with id "foo" is not an RSS block.',
290
+ )
291
+
292
+ response = self.api_session.get(
293
+ "{}/@rss_mixer_data?block=rss-block-id".format(self.page.absolute_url())
294
+ )
295
+ self.assertEqual(response.status_code, 200)
296
+
297
+ @mock.patch("requests.get", side_effect=mocked_requests_get)
298
+ def test_feed_single_result(self, mock_get):
299
+ res = self.get_feed_data(block_id="rss-block-id-single")
300
+ self.assertEqual(len(res), 2)
301
+
302
+ @mock.patch("requests.get", side_effect=mocked_requests_get)
303
+ def test_feed_mixed_result(self, mock_get):
304
+ res = self.get_feed_data(block_id="rss-block-id")
305
+ self.assertEqual(len(res), 4)
306
+
307
+ @mock.patch("requests.get", side_effect=mocked_requests_get)
308
+ def test_feed_results_are_sorted_by_date_descending(self, mock_get):
309
+ res = self.get_feed_data(block_id="rss-block-id")
310
+
311
+ self.assertEqual(res[0]["title"], "Foo News 1")
312
+ self.assertEqual(res[1]["title"], "Bar News 1")
313
+ self.assertEqual(res[2]["title"], "Foo News 2")
314
+ self.assertEqual(res[3]["title"], "Bar News 2")
315
+
316
+ @mock.patch("requests.get", side_effect=mocked_requests_get)
317
+ def test_return_source_info_in_feeds(self, mock_get):
318
+ res = self.get_feed_data(block_id="rss-block-id-with-source")
319
+
320
+ self.assertEqual(res[0]["source"], "Foo site")
321
+ self.assertEqual(res[1]["source"], "")
322
+ self.assertEqual(res[2]["source"], "Foo site")
323
+ self.assertEqual(res[3]["source"], "")
324
+
325
+ @mock.patch("requests.get", side_effect=mocked_requests_get)
326
+ def test_feed_wrong_date_format(self, mock_get):
327
+ res = self.get_feed_data(block_id="rss-block-id-wrong-date")
328
+ self.assertEqual(res[0]["date"], "07/03/2022 17.30 - 07/03/2022 19.00")
329
+
330
+ @mock.patch("requests.get", side_effect=mocked_requests_get)
331
+ def test_feed_categories(self, mock_get):
332
+ res = self.get_feed_data(block_id="rss-block-id-catagories")
333
+ self.assertEqual(res[0]["categories"], ["Category C"])
334
+ self.assertEqual(res[1]["categories"], ["Category A", "Category B"])
@@ -0,0 +1 @@
1
+ import sys, types, os;p = os.path.join(sys._getframe(1).f_locals['sitedir'], *('redturtle',));importlib = __import__('importlib.util');__import__('importlib.machinery');m = sys.modules.setdefault('redturtle', importlib.util.module_from_spec(importlib.machinery.PathFinder.find_spec('redturtle', [os.path.dirname(p)])));m = m or sys.modules.setdefault('redturtle', types.ModuleType('redturtle'));mp = (m or []) and m.__dict__.setdefault('__path__',[]);(p not in mp) and mp.append(p)
@@ -0,0 +1,293 @@
1
+ Metadata-Version: 2.4
2
+ Name: redturtle.rssservice
3
+ Version: 2.2.2
4
+ Summary: An add-on for Plone
5
+ Home-page: https://github.com/collective/redturtle.rssservice
6
+ Author: RedTurtle
7
+ Author-email: sviluppo@redturtle.it
8
+ License: GPL version 2
9
+ Project-URL: PyPI, https://pypi.python.org/pypi/redturtle.rssservice
10
+ Project-URL: Source, https://github.com/collective/redturtle.rssservice
11
+ Project-URL: Tracker, https://github.com/collective/redturtle.rssservice/issues
12
+ Keywords: Python Plone
13
+ Classifier: Development Status :: 5 - Production/Stable
14
+ Classifier: Environment :: Web Environment
15
+ Classifier: Framework :: Plone
16
+ Classifier: Framework :: Plone :: Addon
17
+ Classifier: Framework :: Plone :: 5.2
18
+ Classifier: Programming Language :: Python
19
+ Classifier: Programming Language :: Python :: 2.7
20
+ Classifier: Programming Language :: Python :: 3.6
21
+ Classifier: Programming Language :: Python :: 3.7
22
+ Classifier: Operating System :: OS Independent
23
+ Classifier: License :: OSI Approved :: GNU General Public License v2 (GPLv2)
24
+ Requires-Python: >3.6
25
+ License-File: LICENSE.GPL
26
+ License-File: LICENSE.rst
27
+ Requires-Dist: setuptools
28
+ Requires-Dist: z3c.jbot
29
+ Requires-Dist: plone.api>=1.8.4
30
+ Requires-Dist: plone.restapi
31
+ Requires-Dist: plone.app.dexterity
32
+ Requires-Dist: feedparser
33
+ Provides-Extra: test
34
+ Requires-Dist: plone.app.testing; extra == "test"
35
+ Requires-Dist: plone.testing>=5.0.0; extra == "test"
36
+ Requires-Dist: plone.app.contenttypes; extra == "test"
37
+ Requires-Dist: plone.app.robotframework[debug]; extra == "test"
38
+ Requires-Dist: collective.MockMailHost; extra == "test"
39
+ Requires-Dist: requests_mock; extra == "test"
40
+ Dynamic: author
41
+ Dynamic: author-email
42
+ Dynamic: classifier
43
+ Dynamic: description
44
+ Dynamic: home-page
45
+ Dynamic: keywords
46
+ Dynamic: license
47
+ Dynamic: license-file
48
+ Dynamic: project-url
49
+ Dynamic: provides-extra
50
+ Dynamic: requires-dist
51
+ Dynamic: requires-python
52
+ Dynamic: summary
53
+
54
+ .. This README is meant for consumption by humans and pypi. Pypi can render rst files so please do not use Sphinx features.
55
+ If you want to learn more about writing documentation, please check out: http://docs.plone.org/about/documentation_styleguide.html
56
+ This text does not appear on pypi or github. It is a comment.
57
+
58
+ .. image:: https://img.shields.io/pypi/v/redturtle.rssservice.svg
59
+ :target: https://pypi.org/project/redturtle.rssservice/
60
+ :alt: Latest Version
61
+
62
+ .. image:: https://img.shields.io/pypi/pyversions/redturtle.rssservice.svg?style=plastic
63
+ :target: https://pypi.org/project/redturtle.rssservice/
64
+ :alt: Supported - Python Versions
65
+
66
+ .. image:: https://img.shields.io/pypi/dm/redturtle.rssservice.svg
67
+ :target: https://pypi.org/project/redturtle.rssservice/
68
+ :alt: Number of PyPI downloads
69
+
70
+ .. image:: https://img.shields.io/pypi/l/redturtle.rssservice.svg
71
+ :target: https://pypi.org/project/redturtle.rssservice/
72
+ :alt: License
73
+
74
+ .. image:: https://github.com/RedTurtle/redturtle.rssservice/actions/workflows/test.yml/badge.svg
75
+ :target: https://github.com/RedTurtle/redturtle.rssservice/actions
76
+ :alt: Tests
77
+
78
+ .. image:: https://coveralls.io/repos/github/RedTurtle/redturtle.rssservice/badge.svg?branch=master
79
+ :target: https://coveralls.io/github/RedTurtle/redturtle.rssservice?branch=master
80
+ :alt: Coverage
81
+
82
+ =====================
83
+ Redturtle RSS Service
84
+ =====================
85
+
86
+ This package add support to retrieve RSS feeds from external sources.
87
+
88
+ Usage
89
+ =====
90
+
91
+
92
+ RSS block
93
+ ---------
94
+
95
+ There is a service "**@rss_mixer_data**" that accept a block id, and return a list of sorted feeds by date.
96
+
97
+ This service only accept GET calls and accept following parameters:
98
+
99
+ - block: the id of the rssBlock with the feeds
100
+
101
+ The endpoint should be called on the context that has the rssBlock that you want to show.
102
+
103
+
104
+ For example::
105
+
106
+ > curl -i -X GET http://localhost:8080/Plone/example-page/@rss_mixer_data?block=123456789 -H 'Accept: application/json' -H 'Content-Type: application/json'
107
+
108
+
109
+ Will reply with something like this::
110
+
111
+ [
112
+ {
113
+ "source": "Foo site",
114
+ "contentSnippet": "some description 2",
115
+ "title": "Foo News 2",
116
+ "date": "Thu, 1 Apr 2020 10:44:01 +0200",
117
+ "url": "http://test.com/foo-news-2"
118
+ },
119
+ {
120
+ "source": "",
121
+ "contentSnippet": "some description 2",
122
+ "title": "Bar News 2",
123
+ "date": "Thu, 1 Apr 2020 10:44:01 +0200",
124
+ "url": "http://test.com/bar-news-2"
125
+ },
126
+ {
127
+ "source": "Foo site",
128
+ "contentSnippet": "some description",
129
+ "title": "Foo News 1",
130
+ "date": "Thu, 2 Apr 2020 10:44:01 +0200",
131
+ "url": "http://test.com/foo-news-1"
132
+ },
133
+ {
134
+ "source": "",
135
+ "contentSnippet": "some description",
136
+ "title": "Bar News 1",
137
+ "date": "Thu, 2 Apr 2020 10:44:01 +0200",
138
+ "url": "http://test.com/bar-news-1"
139
+ }
140
+ ]
141
+
142
+ This endpoint works with `volto-rss-block <https://github.com/RedTurtle/volto-rss-block/>`_ plugin.
143
+
144
+ Retrieve timeout
145
+ ----------------
146
+
147
+ There is a 5s default timeout for retrieve RSS feeds.
148
+
149
+ You can override it with an environment variable: **RSS_SERVICE_TIMEOUT**
150
+
151
+ Set User-Agent
152
+ --------------
153
+
154
+ You can override the default ``User-Agent`` for the requests to get feeds by setting an environment variable: **RSS_USER_AGENT**
155
+
156
+ Installation
157
+ ============
158
+
159
+ Install redturtle.rssservice by adding it to your buildout::
160
+
161
+ [buildout]
162
+
163
+ ...
164
+
165
+ eggs =
166
+ redturtle.rssservice
167
+
168
+
169
+ and then running ``bin/buildout``
170
+
171
+ Resilience
172
+ ==========
173
+
174
+ To make the product more resilient against external feed disruptions, it is possible to use an external proxy/cache service. The service is utilized in the following format:
175
+
176
+ ```
177
+ http://proxyservice/http://feedurl
178
+ ```
179
+
180
+ The product includes a built-in proxy/cache implementation that can be used. After installing the product, you can use it by running the following process:
181
+
182
+ ```
183
+ bin/rssmixer-proxy --port 8000 --ttl 1200
184
+ ```
185
+
186
+ And eventually set the environment variable `RSSMIXER_PROXY` to `http://127.0.0.1:8000` according to the port used for the proxy.
187
+
188
+ Contribute
189
+ ==========
190
+
191
+ - Issue Tracker: https://github.com/RedTurtle/redturtle.rssservice/issues
192
+ - Source Code: https://github.com/RedTurtle/redturtle.rssservice
193
+
194
+
195
+ Support
196
+ =======
197
+
198
+ If you are having issues, please let us know.
199
+ We have a mailing list located at: sviluppo@redturtle.it
200
+
201
+
202
+ License
203
+ =======
204
+
205
+ The project is licensed under the GPLv2.
206
+
207
+
208
+ Contributors
209
+ ============
210
+
211
+ - RedTurtle, sviluppo@redturtle.it
212
+
213
+
214
+ Changelog
215
+ =========
216
+
217
+ 2.2.2 (2026-10-05)
218
+ ------------------
219
+
220
+ - Fix proxycache url replace
221
+ [mamico]
222
+ - Refactor proxycache server: thread-safety, atomic writes, background thread deduplication, ThreadingHTTPServer, and anti-SSRF URL validation.
223
+ [mamico]
224
+
225
+
226
+ 2.2.1 (2023-07-12)
227
+ ------------------
228
+
229
+ - Handle case when feed url is an internal url with resolveuid.
230
+ [cekk]
231
+ - Do not print exception on log, but use warning because it's handled.
232
+ [cekk]
233
+
234
+ 2.2.0 (2023-03-21)
235
+ ------------------
236
+
237
+ - Allow configuring the User-Agent for the requests to get feeds,
238
+ via the REQUESTS_USER_AGENT environment variable.
239
+ [davisagli]
240
+
241
+
242
+ 2.1.0 (2023-03-10)
243
+ ------------------
244
+
245
+ - Customizable timeout.
246
+ [cekk]
247
+ - Return 404 if block not found instead BadRequest.
248
+ [cekk]
249
+ - Handle site root blocks in plone 6.
250
+ [cekk]
251
+
252
+
253
+ 2.0.0 (2022-04-07)
254
+ ------------------
255
+
256
+ - Remove unused and unsafe endpoint.
257
+ [cekk]
258
+ - Now @rss_mixer_data accept GET calls (see README for more infos).
259
+ [cekk]
260
+
261
+
262
+ 1.0.3 (2022-03-22)
263
+ ------------------
264
+
265
+ - Allow to use cateogry in rss feed.
266
+ [lucabel]
267
+
268
+
269
+ 1.0.2 (2022-03-04)
270
+ ------------------
271
+
272
+ - Allow dates with wrong date format (eg. a date range)
273
+ [lucabel]
274
+
275
+
276
+ 1.0.1 (2021-12-02)
277
+ ------------------
278
+
279
+ - Fix python version in setup.py
280
+ [cekk]
281
+
282
+ 1.0.0 (2021-10-13)
283
+ ------------------
284
+
285
+ - Add @rss_mixer_data endpoint.
286
+ [cekk]
287
+
288
+
289
+ 0.1.0 (2020-04-08)
290
+ ------------------
291
+
292
+ - Initial release.
293
+ [cekk]
@@ -0,0 +1,19 @@
1
+ redturtle.rssservice-2.2.2-py3.11-nspkg.pth,sha256=OhaLUnttzo54q3OCIkoQMUuEBk5ChV2Obzunmx05BCE,482
2
+ redturtle/rssservice/__init__.py,sha256=k4HGIT2k3l6QUAgrWSe26gG4vq-4PmjHg64JbZu9P6I,140
3
+ redturtle/rssservice/configure.zcml,sha256=AkvPhfI9oXRePWn79-fqjFF8OoGy-G68aDtprdH4q_s,408
4
+ redturtle/rssservice/interfaces.py,sha256=F1O_XA86yzXZoWM4NIvL4hdje2bsfmDLhflDlqEJyV4,1242
5
+ redturtle/rssservice/rss_mixer.py,sha256=LcHFa7re-ej9i5-4mfYmUqKsXnaJyQyPF2XE0yPKB4c,12297
6
+ redturtle/rssservice/testing.py,sha256=LzGTSGbQ6Eyh6pcW1BAccRThYkkX75Jku6X56diejwg,2535
7
+ redturtle/rssservice/proxycacheserver/__init__.py,sha256=xHZfViCOstyXMaglW19wYTW9nnekl2poYQYlK0DVxGM,31
8
+ redturtle/rssservice/proxycacheserver/main.py,sha256=Fd4hkYhCX-x19e2hZ3hDrmjynWe4OXM07caMOwvR4RY,10521
9
+ redturtle/rssservice/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
+ redturtle/rssservice/tests/test_proxycache.py,sha256=nM4t2crzhw8uy4DaKcb5rCPYsRqGNumnX_ElvUH-Ph4,3687
11
+ redturtle/rssservice/tests/test_rss_mixer.py,sha256=eTqk5eLhpsXMNQyOfmzV7AcAoE4Pm-i5tdA55eaJzoQ,11967
12
+ redturtle_rssservice-2.2.2.dist-info/licenses/LICENSE.GPL,sha256=gXf5dRMhNSbfLPYYTY_5hsZ1r7UU1OaKQEAQUhuIBkM,18092
13
+ redturtle_rssservice-2.2.2.dist-info/licenses/LICENSE.rst,sha256=JNZ19JDgHTLouDRam8Ofo9ZleWzRwsRJmJmp4T5OMaY,660
14
+ redturtle_rssservice-2.2.2.dist-info/METADATA,sha256=zN1UrFF-FUi_2uPuD_aa58nxsV9MT6PTULPlKQJHhtw,7905
15
+ redturtle_rssservice-2.2.2.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
16
+ redturtle_rssservice-2.2.2.dist-info/entry_points.txt,sha256=EfhUg5_rtnTS-SS-mpbZcc_pzCFFm_-IkeyYQkP11kA,119
17
+ redturtle_rssservice-2.2.2.dist-info/namespace_packages.txt,sha256=fiCw88RkzE24BO_34ZSZPzj62hbVdD8xEHTZ81VMsis,10
18
+ redturtle_rssservice-2.2.2.dist-info/top_level.txt,sha256=fiCw88RkzE24BO_34ZSZPzj62hbVdD8xEHTZ81VMsis,10
19
+ redturtle_rssservice-2.2.2.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,5 @@
1
+ [console_scripts]
2
+ rssmixer-proxy = redturtle.rssservice.proxycacheserver:main
3
+
4
+ [z3c.autoinclude.plugin]
5
+ target = plone