reader 3.22__tar.gz → 3.24__tar.gz

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.
Files changed (258) hide show
  1. {reader-3.22 → reader-3.24}/CHANGES.rst +62 -0
  2. {reader-3.22/src/reader.egg-info → reader-3.24}/PKG-INFO +4 -3
  3. {reader-3.22 → reader-3.24}/README.rst +1 -0
  4. {reader-3.22 → reader-3.24}/docs/api.rst +29 -0
  5. {reader-3.22 → reader-3.24}/docs/app.rst +23 -0
  6. {reader-3.22 → reader-3.24}/docs/conf.py +3 -2
  7. {reader-3.22 → reader-3.24}/docs/dev.rst +0 -1
  8. {reader-3.22 → reader-3.24}/docs/guide.rst +83 -25
  9. {reader-3.22 → reader-3.24}/docs/install.rst +2 -1
  10. reader-3.24/docs/screenshots/empty.png +0 -0
  11. reader-3.24/docs/screenshots/feeds.png +0 -0
  12. reader-3.24/docs/screenshots/help.png +0 -0
  13. reader-3.24/docs/screenshots/import-result.png +0 -0
  14. reader-3.24/docs/screenshots/import-select.png +0 -0
  15. {reader-3.22 → reader-3.24}/examples/podcast.py +0 -1
  16. {reader-3.22 → reader-3.24}/pyproject.toml +7 -4
  17. {reader-3.22 → reader-3.24}/scripts/bench.py +0 -1
  18. {reader-3.22 → reader-3.24}/scripts/debug_storage_stats.py +0 -1
  19. {reader-3.22 → reader-3.24}/scripts/entry_dedupe_backtest.py +0 -1
  20. {reader-3.22 → reader-3.24}/scripts/jscontrols.py +0 -2
  21. {reader-3.22 → reader-3.24}/scripts/vendor_app_stuff.py +0 -1
  22. {reader-3.22 → reader-3.24}/src/reader/__init__.py +10 -3
  23. {reader-3.22 → reader-3.24}/src/reader/__main__.py +0 -1
  24. {reader-3.22 → reader-3.24}/src/reader/_app/__init__.py +50 -1
  25. {reader-3.22 → reader-3.24}/src/reader/_app/ext.py +0 -1
  26. {reader-3.22 → reader-3.24}/src/reader/_app/legacy/__init__.py +0 -1
  27. {reader-3.22 → reader-3.24}/src/reader/_app/legacy/templates/entry.html +9 -1
  28. {reader-3.22 → reader-3.24}/src/reader/_app/legacy/wsgi.py +0 -1
  29. {reader-3.22 → reader-3.24}/src/reader/_app/static/style.css +6 -0
  30. {reader-3.22 → reader-3.24}/src/reader/_app/templates/add_feed.html +10 -1
  31. {reader-3.22 → reader-3.24}/src/reader/_app/templates/change_feed_title.html +1 -0
  32. {reader-3.22 → reader-3.24}/src/reader/_app/templates/delete_feed.html +1 -0
  33. {reader-3.22 → reader-3.24}/src/reader/_app/templates/entries.html +16 -1
  34. {reader-3.22 → reader-3.24}/src/reader/_app/templates/entry.html +1 -0
  35. {reader-3.22 → reader-3.24}/src/reader/_app/templates/feeds.html +13 -2
  36. reader-3.24/src/reader/_app/templates/help.html +131 -0
  37. reader-3.24/src/reader/_app/templates/import_feeds.html +131 -0
  38. {reader-3.22 → reader-3.24}/src/reader/_app/templates/layout_base.html +14 -1
  39. {reader-3.22 → reader-3.24}/src/reader/_app/templates/macros.html +62 -9
  40. {reader-3.22 → reader-3.24}/src/reader/_app/wsgi.py +0 -1
  41. {reader-3.22 → reader-3.24}/src/reader/_cli.py +17 -7
  42. {reader-3.22 → reader-3.24}/src/reader/_config_utils.py +7 -3
  43. {reader-3.22 → reader-3.24}/src/reader/_hash_utils.py +0 -1
  44. reader-3.24/src/reader/_logging.py +80 -0
  45. {reader-3.22 → reader-3.24}/src/reader/_parser/__init__.py +17 -10
  46. {reader-3.22 → reader-3.24}/src/reader/_parser/_http_utils.py +0 -1
  47. {reader-3.22 → reader-3.24}/src/reader/_parser/_lazy.py +50 -72
  48. {reader-3.22 → reader-3.24}/src/reader/_parser/feedparser.py +93 -13
  49. {reader-3.22 → reader-3.24}/src/reader/_parser/jsonfeed.py +29 -32
  50. {reader-3.22 → reader-3.24}/src/reader/_parser/requests/__init__.py +0 -1
  51. {reader-3.22 → reader-3.24}/src/reader/_parser/requests/_lazy.py +0 -1
  52. {reader-3.22 → reader-3.24}/src/reader/_plugins/legacy/enclosure_tags.py +1 -2
  53. {reader-3.22 → reader-3.24}/src/reader/_plugins/legacy/preview_feed_list.py +0 -1
  54. {reader-3.22 → reader-3.24}/src/reader/_plugins/legacy/share.py +0 -1
  55. {reader-3.22 → reader-3.24}/src/reader/_plugins/sqlite_releases.py +0 -1
  56. {reader-3.22 → reader-3.24}/src/reader/_storage/__init__.py +0 -1
  57. {reader-3.22 → reader-3.24}/src/reader/_storage/_base.py +0 -1
  58. {reader-3.22 → reader-3.24}/src/reader/_storage/_changes.py +2 -5
  59. {reader-3.22 → reader-3.24}/src/reader/_storage/_entries.py +62 -31
  60. {reader-3.22 → reader-3.24}/src/reader/_storage/_feeds.py +66 -41
  61. {reader-3.22 → reader-3.24}/src/reader/_storage/_html_utils.py +0 -1
  62. {reader-3.22 → reader-3.24}/src/reader/_storage/_schema.py +49 -10
  63. {reader-3.22 → reader-3.24}/src/reader/_storage/_search.py +16 -35
  64. {reader-3.22 → reader-3.24}/src/reader/_storage/_sql_utils.py +0 -1
  65. {reader-3.22 → reader-3.24}/src/reader/_storage/_sqlite_utils.py +2 -1
  66. {reader-3.22 → reader-3.24}/src/reader/_storage/_tags.py +0 -1
  67. {reader-3.22 → reader-3.24}/src/reader/_types.py +15 -100
  68. reader-3.22/src/reader/_update.py → reader-3.24/src/reader/_update/__init__.py +95 -197
  69. reader-3.24/src/reader/_update/base.py +205 -0
  70. reader-3.24/src/reader/_update/hooks.py +105 -0
  71. {reader-3.22 → reader-3.24}/src/reader/_utils.py +25 -62
  72. {reader-3.22 → reader-3.24}/src/reader/core.py +86 -37
  73. {reader-3.22 → reader-3.24}/src/reader/exceptions.py +8 -0
  74. reader-3.24/src/reader/opml.py +178 -0
  75. {reader-3.22 → reader-3.24}/src/reader/plugins/__init__.py +0 -1
  76. {reader-3.22 → reader-3.24}/src/reader/plugins/_loader.py +0 -1
  77. {reader-3.22 → reader-3.24}/src/reader/plugins/entry_dedupe.py +31 -45
  78. {reader-3.22 → reader-3.24}/src/reader/plugins/mark_as_read.py +5 -8
  79. {reader-3.22 → reader-3.24}/src/reader/plugins/readtime.py +8 -11
  80. {reader-3.22 → reader-3.24}/src/reader/plugins/ua_fallback.py +3 -11
  81. {reader-3.22 → reader-3.24}/src/reader/types.py +134 -10
  82. {reader-3.22 → reader-3.24}/src/reader/utils.py +16 -2
  83. {reader-3.22 → reader-3.24/src/reader.egg-info}/PKG-INFO +4 -3
  84. {reader-3.22 → reader-3.24}/src/reader.egg-info/SOURCES.txt +14 -1
  85. {reader-3.22 → reader-3.24}/src/reader.egg-info/requires.txt +2 -2
  86. {reader-3.22 → reader-3.24}/tests/conftest.py +3 -39
  87. {reader-3.22 → reader-3.24}/tests/data/10.json.py +0 -1
  88. {reader-3.22 → reader-3.24}/tests/data/empty.atom.py +1 -1
  89. {reader-3.22 → reader-3.24}/tests/data/empty.json.py +0 -1
  90. {reader-3.22 → reader-3.24}/tests/data/empty.rss.py +0 -1
  91. {reader-3.22 → reader-3.24}/tests/data/full.atom.py +4 -4
  92. {reader-3.22 → reader-3.24}/tests/data/full.json.py +7 -4
  93. {reader-3.22 → reader-3.24}/tests/data/full.rss.py +3 -3
  94. {reader-3.22 → reader-3.24}/tests/data/invalid.json.py +3 -3
  95. {reader-3.22 → reader-3.24}/tests/data/relative.atom.py +0 -1
  96. {reader-3.22 → reader-3.24}/tests/data/relative.rss.py +0 -1
  97. {reader-3.22 → reader-3.24}/tests/data/unknown.json.py +0 -1
  98. reader-3.24/tests/fixtures.py +48 -0
  99. {reader-3.22 → reader-3.24}/tests/test__types.py +14 -6
  100. {reader-3.22 → reader-3.24}/tests/test__utils.py +0 -20
  101. {reader-3.22 → reader-3.24}/tests/test_app_legacy.py +0 -1
  102. {reader-3.22 → reader-3.24}/tests/test_bench.py +0 -2
  103. {reader-3.22 → reader-3.24}/tests/test_changes.py +0 -1
  104. {reader-3.22 → reader-3.24}/tests/test_cli.py +18 -4
  105. {reader-3.22 → reader-3.24}/tests/test_config_utils.py +10 -4
  106. {reader-3.22 → reader-3.24}/tests/test_html_utils.py +0 -1
  107. {reader-3.22 → reader-3.24}/tests/test_lazy_imports.py +2 -7
  108. reader-3.24/tests/test_logging.py +41 -0
  109. reader-3.24/tests/test_opml.py +153 -0
  110. {reader-3.22 → reader-3.24}/tests/test_parser.py +201 -13
  111. {reader-3.22 → reader-3.24}/tests/test_plugins_entry_dedupe.py +0 -1
  112. {reader-3.22 → reader-3.24}/tests/test_plugins_mark_as_read.py +0 -1
  113. {reader-3.22 → reader-3.24}/tests/test_plugins_preview_feed_list.py +0 -1
  114. {reader-3.22 → reader-3.24}/tests/test_plugins_readtime.py +0 -1
  115. {reader-3.22 → reader-3.24}/tests/test_plugins_ua_fallback.py +1 -1
  116. {reader-3.22 → reader-3.24}/tests/test_reader.py +16 -13
  117. {reader-3.22 → reader-3.24}/tests/test_reader_deprecations.py +0 -1
  118. {reader-3.22 → reader-3.24}/tests/test_reader_filter.py +25 -0
  119. {reader-3.22 → reader-3.24}/tests/test_reader_hooks.py +0 -1
  120. {reader-3.22 → reader-3.24}/tests/test_reader_lifecycle.py +0 -1
  121. {reader-3.22 → reader-3.24}/tests/test_reader_private.py +0 -1
  122. {reader-3.22 → reader-3.24}/tests/test_reader_search.py +9 -9
  123. {reader-3.22 → reader-3.24}/tests/test_reader_sort.py +0 -1
  124. {reader-3.22 → reader-3.24}/tests/test_reader_update.py +11 -10
  125. {reader-3.22 → reader-3.24}/tests/test_reader_utils.py +14 -2
  126. {reader-3.22 → reader-3.24}/tests/test_search.py +0 -1
  127. {reader-3.22 → reader-3.24}/tests/test_sql_utils.py +12 -22
  128. {reader-3.22 → reader-3.24}/tests/test_sqlite_utils.py +0 -1
  129. {reader-3.22 → reader-3.24}/tests/test_storage.py +28 -0
  130. {reader-3.22 → reader-3.24}/tests/test_types.py +33 -0
  131. reader-3.22/docs/screenshots/feeds.png +0 -0
  132. {reader-3.22 → reader-3.24}/LICENSE +0 -0
  133. {reader-3.22 → reader-3.24}/MANIFEST.in +0 -0
  134. {reader-3.22 → reader-3.24}/docs/changelog.rst +0 -0
  135. {reader-3.22 → reader-3.24}/docs/cli.rst +0 -0
  136. {reader-3.22 → reader-3.24}/docs/config.rst +0 -0
  137. {reader-3.22 → reader-3.24}/docs/contributing.rst +0 -0
  138. {reader-3.22 → reader-3.24}/docs/index.rst +0 -0
  139. {reader-3.22 → reader-3.24}/docs/internal.rst +0 -0
  140. {reader-3.22 → reader-3.24}/docs/plugins.rst +0 -0
  141. {reader-3.22 → reader-3.24}/docs/screenshots/entries-filters-light.png +0 -0
  142. {reader-3.22 → reader-3.24}/docs/screenshots/entries.png +0 -0
  143. {reader-3.22 → reader-3.24}/docs/screenshots/entry-image-light.png +0 -0
  144. {reader-3.22 → reader-3.24}/docs/screenshots/entry.png +0 -0
  145. {reader-3.22 → reader-3.24}/docs/screenshots/feed.png +0 -0
  146. {reader-3.22 → reader-3.24}/docs/tutorial.rst +0 -0
  147. {reader-3.22 → reader-3.24}/docs/why.rst +0 -0
  148. {reader-3.22 → reader-3.24}/examples/config.toml +0 -0
  149. {reader-3.22 → reader-3.24}/examples/custom_headers.py +0 -0
  150. {reader-3.22 → reader-3.24}/examples/feed_slugs.py +0 -0
  151. {reader-3.22 → reader-3.24}/examples/parser_only.py +0 -0
  152. {reader-3.22 → reader-3.24}/examples/terminal.py +0 -0
  153. {reader-3.22 → reader-3.24}/run.sh +0 -0
  154. {reader-3.22 → reader-3.24}/scripts/backup.sh +0 -0
  155. {reader-3.22 → reader-3.24}/scripts/jscontrols.html +0 -0
  156. {reader-3.22 → reader-3.24}/scripts/lines.sh +0 -0
  157. {reader-3.22 → reader-3.24}/scripts/release.py +0 -0
  158. {reader-3.22 → reader-3.24}/setup.cfg +0 -0
  159. {reader-3.22 → reader-3.24}/src/reader/_app/cli.py +0 -0
  160. {reader-3.22 → reader-3.24}/src/reader/_app/forms.py +0 -0
  161. {reader-3.22 → reader-3.24}/src/reader/_app/legacy/api_thing.py +0 -0
  162. {reader-3.22 → reader-3.24}/src/reader/_app/legacy/static/controls.js +0 -0
  163. {reader-3.22 → reader-3.24}/src/reader/_app/legacy/static/style.css +0 -0
  164. {reader-3.22 → reader-3.24}/src/reader/_app/legacy/templates/add_entry.html +0 -0
  165. {reader-3.22 → reader-3.24}/src/reader/_app/legacy/templates/entries.html +0 -0
  166. {reader-3.22 → reader-3.24}/src/reader/_app/legacy/templates/feeds.html +0 -0
  167. {reader-3.22 → reader-3.24}/src/reader/_app/legacy/templates/layout.html +0 -0
  168. {reader-3.22 → reader-3.24}/src/reader/_app/legacy/templates/macros.html +0 -0
  169. {reader-3.22 → reader-3.24}/src/reader/_app/legacy/templates/metadata.html +0 -0
  170. {reader-3.22 → reader-3.24}/src/reader/_app/legacy/templates/tags.html +0 -0
  171. {reader-3.22 → reader-3.24}/src/reader/_app/static/theme.js +0 -0
  172. {reader-3.22 → reader-3.24}/src/reader/_app/static/vendor/README +0 -0
  173. {reader-3.22 → reader-3.24}/src/reader/_app/static/vendor/bootstrap-icons@1.13.1/font/bootstrap-icons.min.css +0 -0
  174. {reader-3.22 → reader-3.24}/src/reader/_app/static/vendor/bootstrap-icons@1.13.1/font/fonts/bootstrap-icons.woff +0 -0
  175. {reader-3.22 → reader-3.24}/src/reader/_app/static/vendor/bootstrap-icons@1.13.1/font/fonts/bootstrap-icons.woff2 +0 -0
  176. {reader-3.22 → reader-3.24}/src/reader/_app/static/vendor/bootstrap@5.3.8/dist/css/bootstrap.min.css +0 -0
  177. {reader-3.22 → reader-3.24}/src/reader/_app/static/vendor/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js +0 -0
  178. {reader-3.22 → reader-3.24}/src/reader/_app/static/vendor/htmx-ext-response-targets@2.0.4/dist/response-targets.min.js +0 -0
  179. {reader-3.22 → reader-3.24}/src/reader/_app/static/vendor/htmx.org@2.0.8/dist/htmx.min.js +0 -0
  180. {reader-3.22 → reader-3.24}/src/reader/_app/templates/layout.html +0 -0
  181. {reader-3.22 → reader-3.24}/src/reader/_parser/_url_utils.py +0 -0
  182. {reader-3.22 → reader-3.24}/src/reader/_parser/file.py +0 -0
  183. {reader-3.22 → reader-3.24}/src/reader/_parser/http.py +0 -0
  184. {reader-3.22 → reader-3.24}/src/reader/_plugins/__init__.py +0 -0
  185. {reader-3.22 → reader-3.24}/src/reader/_plugins/cli_status.py +0 -0
  186. {reader-3.22 → reader-3.24}/src/reader/_plugins/legacy/__init__.py +0 -0
  187. {reader-3.22 → reader-3.24}/src/reader/_plugins/legacy/templates/preview_feed_list.html +0 -0
  188. {reader-3.22 → reader-3.24}/src/reader/_plugins/timer.py +0 -0
  189. {reader-3.22 → reader-3.24}/src/reader/_vendor/__init__.py +0 -0
  190. {reader-3.22 → reader-3.24}/src/reader/_vendor/feedparser/__init__.py +0 -0
  191. {reader-3.22 → reader-3.24}/src/reader/_vendor/feedparser/api.py +0 -0
  192. {reader-3.22 → reader-3.24}/src/reader/_vendor/feedparser/datetimes/__init__.py +0 -0
  193. {reader-3.22 → reader-3.24}/src/reader/_vendor/feedparser/datetimes/asctime.py +0 -0
  194. {reader-3.22 → reader-3.24}/src/reader/_vendor/feedparser/datetimes/greek.py +0 -0
  195. {reader-3.22 → reader-3.24}/src/reader/_vendor/feedparser/datetimes/hungarian.py +0 -0
  196. {reader-3.22 → reader-3.24}/src/reader/_vendor/feedparser/datetimes/iso8601.py +0 -0
  197. {reader-3.22 → reader-3.24}/src/reader/_vendor/feedparser/datetimes/korean.py +0 -0
  198. {reader-3.22 → reader-3.24}/src/reader/_vendor/feedparser/datetimes/perforce.py +0 -0
  199. {reader-3.22 → reader-3.24}/src/reader/_vendor/feedparser/datetimes/rfc822.py +0 -0
  200. {reader-3.22 → reader-3.24}/src/reader/_vendor/feedparser/datetimes/w3dtf.py +0 -0
  201. {reader-3.22 → reader-3.24}/src/reader/_vendor/feedparser/encodings.py +0 -0
  202. {reader-3.22 → reader-3.24}/src/reader/_vendor/feedparser/exceptions.py +0 -0
  203. {reader-3.22 → reader-3.24}/src/reader/_vendor/feedparser/html.py +0 -0
  204. {reader-3.22 → reader-3.24}/src/reader/_vendor/feedparser/http.py +0 -0
  205. {reader-3.22 → reader-3.24}/src/reader/_vendor/feedparser/mixin.py +0 -0
  206. {reader-3.22 → reader-3.24}/src/reader/_vendor/feedparser/namespaces/__init__.py +0 -0
  207. {reader-3.22 → reader-3.24}/src/reader/_vendor/feedparser/namespaces/_base.py +0 -0
  208. {reader-3.22 → reader-3.24}/src/reader/_vendor/feedparser/namespaces/admin.py +0 -0
  209. {reader-3.22 → reader-3.24}/src/reader/_vendor/feedparser/namespaces/cc.py +0 -0
  210. {reader-3.22 → reader-3.24}/src/reader/_vendor/feedparser/namespaces/dc.py +0 -0
  211. {reader-3.22 → reader-3.24}/src/reader/_vendor/feedparser/namespaces/georss.py +0 -0
  212. {reader-3.22 → reader-3.24}/src/reader/_vendor/feedparser/namespaces/itunes.py +0 -0
  213. {reader-3.22 → reader-3.24}/src/reader/_vendor/feedparser/namespaces/mediarss.py +0 -0
  214. {reader-3.22 → reader-3.24}/src/reader/_vendor/feedparser/namespaces/psc.py +0 -0
  215. {reader-3.22 → reader-3.24}/src/reader/_vendor/feedparser/parsers/__init__.py +0 -0
  216. {reader-3.22 → reader-3.24}/src/reader/_vendor/feedparser/parsers/json.py +0 -0
  217. {reader-3.22 → reader-3.24}/src/reader/_vendor/feedparser/parsers/loose.py +0 -0
  218. {reader-3.22 → reader-3.24}/src/reader/_vendor/feedparser/parsers/strict.py +0 -0
  219. {reader-3.22 → reader-3.24}/src/reader/_vendor/feedparser/py.typed +0 -0
  220. {reader-3.22 → reader-3.24}/src/reader/_vendor/feedparser/sanitizer.py +0 -0
  221. {reader-3.22 → reader-3.24}/src/reader/_vendor/feedparser/sgml.py +0 -0
  222. {reader-3.22 → reader-3.24}/src/reader/_vendor/feedparser/urls.py +0 -0
  223. {reader-3.22 → reader-3.24}/src/reader/_vendor/feedparser/util.py +0 -0
  224. {reader-3.22 → reader-3.24}/src/reader/plugins/enclosure_dedupe.py +0 -0
  225. {reader-3.22 → reader-3.24}/src/reader/py.typed +0 -0
  226. {reader-3.22 → reader-3.24}/src/reader.egg-info/dependency_links.txt +0 -0
  227. {reader-3.22 → reader-3.24}/src/reader.egg-info/top_level.txt +0 -0
  228. {reader-3.22 → reader-3.24}/tests/_plugins/__init__.py +0 -0
  229. {reader-3.22 → reader-3.24}/tests/_plugins/builtin.py +0 -0
  230. {reader-3.22 → reader-3.24}/tests/_plugins/error_during_import.py +0 -0
  231. {reader-3.22 → reader-3.24}/tests/_plugins/error_import_error.py +0 -0
  232. {reader-3.22 → reader-3.24}/tests/data/10.json +0 -0
  233. {reader-3.22 → reader-3.24}/tests/data/custom +0 -0
  234. {reader-3.22 → reader-3.24}/tests/data/empty.atom +0 -0
  235. {reader-3.22 → reader-3.24}/tests/data/empty.json +0 -0
  236. {reader-3.22 → reader-3.24}/tests/data/empty.rss +0 -0
  237. {reader-3.22 → reader-3.24}/tests/data/full.atom +0 -0
  238. {reader-3.22 → reader-3.24}/tests/data/full.json +0 -0
  239. {reader-3.22 → reader-3.24}/tests/data/full.rss +0 -0
  240. {reader-3.22 → reader-3.24}/tests/data/invalid.json +0 -0
  241. {reader-3.22 → reader-3.24}/tests/data/relative.atom +0 -0
  242. {reader-3.22 → reader-3.24}/tests/data/relative.rss +0 -0
  243. {reader-3.22 → reader-3.24}/tests/data/sqlite_releases.html +0 -0
  244. {reader-3.22 → reader-3.24}/tests/data/unknown.json +0 -0
  245. {reader-3.22 → reader-3.24}/tests/fakeparser.py +0 -0
  246. {reader-3.22 → reader-3.24}/tests/reader_methods.py +0 -0
  247. {reader-3.22 → reader-3.24}/tests/test_app_wsgi.py +0 -0
  248. {reader-3.22 → reader-3.24}/tests/test_exceptions.py +0 -0
  249. {reader-3.22 → reader-3.24}/tests/test_hash_utils.py +0 -0
  250. {reader-3.22 → reader-3.24}/tests/test_plugins_cli_status.py +0 -0
  251. {reader-3.22 → reader-3.24}/tests/test_plugins_enclosure_dedupe.py +0 -0
  252. {reader-3.22 → reader-3.24}/tests/test_plugins_loader.py +0 -0
  253. {reader-3.22 → reader-3.24}/tests/test_plugins_sqlite_releases.py +0 -0
  254. {reader-3.22 → reader-3.24}/tests/test_reader_counts.py +0 -0
  255. {reader-3.22 → reader-3.24}/tests/test_reader_integration.py +0 -0
  256. {reader-3.22 → reader-3.24}/tests/test_tags.py +0 -0
  257. {reader-3.22 → reader-3.24}/tests/utils.py +0 -0
  258. {reader-3.22 → reader-3.24}/tox.ini +0 -0
@@ -6,6 +6,68 @@ Changelog
6
6
  :no-index:
7
7
 
8
8
 
9
+ Version 3.24
10
+ ------------
11
+
12
+ Released 2026-05-20
13
+
14
+ * Support multiple authors with details (link, email).
15
+ Thanks to `Anshul Mittal`_ for the PR.
16
+ (:issue:`391`)
17
+
18
+ * Add :attr:`Feed.authors`, :attr:`Entry.authors`,
19
+ and :attr:`EntrySource.authors` attributes.
20
+ * Add matching :attr:`~Feed.authors_str` property.
21
+ * Deprecate :attr:`~Feed.author` attribute.
22
+
23
+ * Fix bug introduced in `version 3.15 <Version 3.15_>`__
24
+ causing feeds to not be retrieved in parallel
25
+ even if :meth:`~Reader.update_feeds` was called with ``workers`` > 1.
26
+
27
+ * Use :ref:`structured logging <logging>` internally,
28
+ with the possibility to opt into native `structlog`_ logging.
29
+ (:issue:`147`)
30
+
31
+ * Use :meth:`datetime.datetime.fromisoformat` instead of external dependency ``iso8601``.
32
+ Thanks to `Sebastian Bradley`_ for the PR.
33
+ (:issue:`398`)
34
+
35
+ * Refactor updater internals to allow reusing logic in alternate implementations.
36
+
37
+ * :doc:`app` improvements.
38
+
39
+ * Add empty state and contextual help; :ref:`screenshots <screenshots-help>`.
40
+ * Import feeds improvements (select all/none, accept only XML / OPML files).
41
+
42
+ * Add ``--json`` option to the ``list`` CLI commands.
43
+ Thanks to `Puneet Dixit`_ for the PR.
44
+ (:issue:`394`)
45
+
46
+
47
+
48
+ .. _Puneet Dixit: https://github.com/puneetdixit200
49
+ .. _Anshul Mittal: https://github.com/anderson688
50
+ .. _Sebastian Bradley: https://github.com/bazbrad765
51
+
52
+ .. _structlog: https://www.structlog.org/
53
+
54
+
55
+ Version 3.23
56
+ ------------
57
+
58
+ Released 2026-04-24
59
+
60
+ * Add support for :ref:`opml`. (:issue:`165`)
61
+
62
+ * Add support for import / export in the web app; :ref:`screenshots <screenshots-import>`.
63
+
64
+ * Deprecate the :func:`~reader.utils.archive_entries` ``feed_user_title`` argument.
65
+ * Fix :meth:`get_feeds(feed='') <Reader.get_feeds>` and
66
+ :meth:`get_entries(entry=('', '')) <Reader.get_entries>`
67
+ returning wrong entries.
68
+ (:issue:`392`)
69
+
70
+
9
71
  Version 3.22
10
72
  ------------
11
73
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: reader
3
- Version: 3.22
3
+ Version: 3.24
4
4
  Summary: A Python feed reader library.
5
5
  Author: lemon24
6
6
  License: BSD-3-Clause
@@ -39,12 +39,12 @@ Requires-Dist: typing-extensions>=4
39
39
  Requires-Dist: feedparser>=6
40
40
  Requires-Dist: requests>=2.18
41
41
  Requires-Dist: werkzeug>2
42
- Requires-Dist: iso8601>=1
43
42
  Requires-Dist: beautifulsoup4>=4.5
43
+ Requires-Dist: structlog
44
44
  Provides-Extra: all
45
45
  Requires-Dist: reader[app,cli,unstable-plugins]; extra == "all"
46
46
  Provides-Extra: cli
47
- Requires-Dist: click>=7; extra == "cli"
47
+ Requires-Dist: click!=8.4.0,>=7; extra == "cli"
48
48
  Provides-Extra: app
49
49
  Requires-Dist: flask>=0.10; extra == "app"
50
50
  Requires-Dist: humanize>=4; extra == "app"
@@ -113,6 +113,7 @@ you'd probably call it a fat model.
113
113
  * filter feeds and articles
114
114
  * full-text search articles
115
115
  * get statistics on feed and user activity
116
+ * import / export feeds as OPML
116
117
  * write plugins to extend its functionality
117
118
  * skip all the low level stuff and focus on what makes your feed reader different
118
119
 
@@ -49,6 +49,7 @@ you'd probably call it a fat model.
49
49
  * filter feeds and articles
50
50
  * full-text search articles
51
51
  * get statistics on feed and user activity
52
+ * import / export feeds as OPML
52
53
  * write plugins to extend its functionality
53
54
  * skip all the low level stuff and focus on what makes your feed reader different
54
55
 
@@ -27,12 +27,17 @@ Data objects
27
27
 
28
28
  .. autoclass:: Feed
29
29
  :members:
30
+ :inherited-members:
31
+
32
+ .. autoclass:: Author
33
+ :members:
30
34
 
31
35
  .. autoclass:: ExceptionInfo
32
36
  :members:
33
37
 
34
38
  .. autoclass:: Entry
35
39
  :members:
40
+ :inherited-members:
36
41
 
37
42
  .. autoclass:: Content
38
43
  :members:
@@ -42,6 +47,7 @@ Data objects
42
47
 
43
48
  .. autoclass:: EntrySource
44
49
  :members:
50
+ :inherited-members:
45
51
 
46
52
  .. autoclass:: EntrySearchResult
47
53
  :members:
@@ -67,6 +73,17 @@ Data objects
67
73
  .. autoclass:: EntryUpdateStatus
68
74
  :members:
69
75
 
76
+ .. autoclass:: FeedToImport
77
+ :members:
78
+ :undoc-members:
79
+
80
+ .. autoclass:: FeedImportResult
81
+ :members:
82
+
83
+ .. autoclass:: FeedExport
84
+ :members:
85
+
86
+
70
87
 
71
88
  Exceptions
72
89
  ----------
@@ -155,6 +172,9 @@ Exceptions
155
172
  .. autoexception:: PluginInitError
156
173
  :show-inheritance:
157
174
 
175
+ .. autoexception:: FeedImportError
176
+ :show-inheritance:
177
+
158
178
 
159
179
  .. autoexception:: ReaderWarning
160
180
  :show-inheritance:
@@ -213,4 +233,13 @@ Constants
213
233
  Utilities
214
234
  ---------
215
235
 
236
+ .. autofunction:: reader.enable_structlog
237
+
216
238
  .. autofunction:: reader.utils.archive_entries
239
+
240
+
241
+
242
+ ``reader.opml``
243
+ ---------------
244
+
245
+ .. automodule:: reader.opml
@@ -62,6 +62,29 @@ Screenshots
62
62
 
63
63
  article view (light mode)
64
64
 
65
+ .. _screenshots-import:
66
+
67
+ .. figure:: screenshots/import-select.png
68
+ :width: 240px
69
+
70
+ import feeds (select)
71
+
72
+ .. figure:: screenshots/import-result.png
73
+ :width: 240px
74
+
75
+ import feeds (result)
76
+
77
+ .. _screenshots-help:
78
+
79
+ .. figure:: screenshots/empty.png
80
+ :width: 240px
81
+
82
+ new user / empty state
83
+
84
+ .. figure:: screenshots/help.png
85
+ :width: 240px
86
+
87
+ contextual help
65
88
 
66
89
 
67
90
  Serving the web app
@@ -6,7 +6,6 @@ import unittest.mock
6
6
  import packaging.version
7
7
  from setuptools.config.pyprojecttoml import read_configuration
8
8
 
9
-
10
9
  sys.path.insert(0, os.path.abspath('../src'))
11
10
 
12
11
  # mock some things "by hand", so we can import reader below without any dependencies
@@ -20,12 +19,14 @@ for name in [
20
19
  'werkzeug.exceptions',
21
20
  'werkzeug.http',
22
21
  'yaml',
22
+ 'structlog',
23
+ 'structlog.contextvars',
24
+ 'structlog.stdlib',
23
25
  ]:
24
26
  sys.modules[name] = unittest.mock.Mock()
25
27
 
26
28
  import reader
27
29
 
28
-
29
30
  extensions = [
30
31
  'sphinx_rtd_theme',
31
32
  'sphinx.ext.autodoc',
@@ -32,7 +32,6 @@ in my spare time,
32
32
  but I will prioritize supporting :doc:`contributors <contributing>`
33
33
  (discussions, reviews and so on).
34
34
 
35
- * OPML support, :issue:`165`
36
35
  * :ref:`deleting entries <deleting entries>`
37
36
 
38
37
  * archiving important entries of deleted feeds, :issue:`290`
@@ -374,7 +374,7 @@ As seen in the previous sections,
374
374
  updated=datetime.datetime(2020, 2, 28, 9, 34, 2, tzinfo=datetime.timezone.utc),
375
375
  title='Hello Internet',
376
376
  link='http://www.hellointernet.fm/',
377
- author='CGP Grey',
377
+ authors=(Author(name='CGP Grey'),),
378
378
  subtitle='CGP Grey and Brady Haran talk about YouTube, life, work, whatever.',
379
379
  version='rss20',
380
380
  user_title=None,
@@ -386,9 +386,10 @@ As seen in the previous sections,
386
386
  To get all the feeds, use the :meth:`~Reader.get_feeds` method::
387
387
 
388
388
  >>> for feed in reader.get_feeds():
389
+ ... authors = ", ".join(a.name for a in feed.authors if a.name) or 'unknown author'
389
390
  ... print(
390
391
  ... feed.title or feed.url,
391
- ... f"by {feed.author or 'unknown author'},",
392
+ ... f"by {authors},",
392
393
  ... f"updated on {feed.updated or 'never'}",
393
394
  ... )
394
395
  ...
@@ -705,35 +706,32 @@ you can delete entries using the low-level
705
706
 
706
707
 
707
708
 
708
- .. _pagination:
709
+ .. _opml:
709
710
 
710
- Pagination
711
- ----------
712
-
713
- :meth:`~Reader.get_feeds`, :meth:`~Reader.get_entries`,
714
- and :meth:`~Reader.search_entries`
715
- can be used in a paginated fashion.
716
-
717
- The ``limit`` argument allows limiting the number of results returned;
718
- the ``starting_after`` argument allows skipping results until after
719
- a specific one.
711
+ OPML import / export
712
+ --------------------
720
713
 
721
- To get the first page, use only ``limit``::
714
+ You can import and export feeds from and to an `OPML subscription list`_
715
+ by using the :meth:`~Reader.import_feeds`
716
+ and :meth:`~Reader.export_feeds` methods::
722
717
 
723
- >>> for entry in reader.get_entries(limit=2):
724
- ... print(entry.title)
718
+ >>> export = reader.export_feeds()
719
+ >>> export.filename
720
+ 'reader-feeds-2026-04-24-10-00-00.opml'
721
+ >>> with open(export.filename, 'wb') as f:
722
+ ... f.write(export.content)
725
723
  ...
726
- H.I. #136: Dog Bingo
727
- H.I. #135: Place Your Bets
724
+ >>> # with another reader instance
725
+ >>> reader = make_reader(':memory:')
726
+ >>> with open(export.filename, 'rb') as f:
727
+ ... reader.import_feeds(f)
728
728
 
729
- To get the next page, use the last result from a call as
730
- ``starting_after`` in the next call::
729
+ For more detailed control over which feeds are imported,
730
+ see :meth:`~Reader.import_feeds_iter` and the :mod:`reader.opml` module.
731
731
 
732
- >>> for entry in reader.get_entries(limit=2, starting_after=entry):
733
- ... print(entry.title)
734
- ...
735
- # H.I. 134: Boxing Day
736
- Star Wars: The Rise of Skywalker, Hello Internet Christmas Special
732
+ .. _OPML subscription list: https://opml.org/spec2.opml#subscriptionLists
733
+
734
+ .. versionadded:: 3.23
737
735
 
738
736
 
739
737
 
@@ -831,6 +829,38 @@ Some examples of how this is useful:
831
829
 
832
830
 
833
831
 
832
+ .. _pagination:
833
+
834
+ Pagination
835
+ ----------
836
+
837
+ :meth:`~Reader.get_feeds`, :meth:`~Reader.get_entries`,
838
+ and :meth:`~Reader.search_entries`
839
+ can be used in a paginated fashion.
840
+
841
+ The ``limit`` argument allows limiting the number of results returned;
842
+ the ``starting_after`` argument allows skipping results until after
843
+ a specific one.
844
+
845
+ To get the first page, use only ``limit``::
846
+
847
+ >>> for entry in reader.get_entries(limit=2):
848
+ ... print(entry.title)
849
+ ...
850
+ H.I. #136: Dog Bingo
851
+ H.I. #135: Place Your Bets
852
+
853
+ To get the next page, use the last result from a call as
854
+ ``starting_after`` in the next call::
855
+
856
+ >>> for entry in reader.get_entries(limit=2, starting_after=entry):
857
+ ... print(entry.title)
858
+ ...
859
+ # H.I. 134: Boxing Day
860
+ Star Wars: The Rise of Skywalker, Hello Internet Christmas Special
861
+
862
+
863
+
834
864
  .. _reserved names:
835
865
 
836
866
  Reserved names
@@ -972,6 +1002,34 @@ depending on their type attribute or feedparser defaults:
972
1002
 
973
1003
 
974
1004
 
1005
+ .. _logging:
1006
+
1007
+ Logging
1008
+ -------
1009
+
1010
+ *reader* uses structured logging internally, through `structlog`_.
1011
+
1012
+ By default, output goes to the standard library :mod:`logging` module;
1013
+ if you don't care about structlog,
1014
+ configure :mod:`logging` as you normally would.
1015
+ The *reader* root logger is called ``reader``;
1016
+ the names of its children are an implementation detail,
1017
+ and may change without any notice.
1018
+
1019
+ To enable native structlog logging,
1020
+ call :func:`enable_structlog` **before** configuring structlog::
1021
+
1022
+ import reader, structlog
1023
+
1024
+ reader.enable_structlog()
1025
+ structlog.configure(...)
1026
+
1027
+ ... # do stuff with reader
1028
+
1029
+ .. _structlog: https://www.structlog.org/
1030
+
1031
+
1032
+
975
1033
  .. _errors:
976
1034
 
977
1035
  Errors and exceptions
@@ -18,9 +18,9 @@ These packages will be installed automatically when installing *reader*:
18
18
  it replaces feedparser's default use of :mod:`urllib`
19
19
  to make it easier to write plugins.
20
20
  * `werkzeug`_ provides HTTP utilities.
21
- * `iso8601`_ parses dates in ISO 8601 / RFC 3339; used for JSON Feed parsing.
22
21
  * `beautifulsoup4`_ is used to strip HTML tags before adding entries
23
22
  to the search index.
23
+ * `structlog`_ is used for structured logging.
24
24
  * `typing-extensions`_ is used for :mod:`typing` backports.
25
25
 
26
26
  *reader* also depends on the :mod:`sqlite3` standard library module
@@ -64,6 +64,7 @@ As of version |version|, *reader* has the following extras:
64
64
  .. _requests: https://requests.readthedocs.io/
65
65
  .. _werkzeug: https://werkzeug.palletsprojects.com/
66
66
  .. _iso8601: http://pyiso8601.readthedocs.org/
67
+ .. _structlog: https://www.structlog.org/
67
68
  .. _typing-extensions: https://pypi.org/project/typing-extensions/
68
69
  .. _JSON1: https://www.sqlite.org/json1.html
69
70
  .. _FTS5: https://www.sqlite.org/fts5.html
Binary file
Binary file
Binary file
@@ -14,7 +14,6 @@ import requests
14
14
 
15
15
  from reader import make_reader
16
16
 
17
-
18
17
  feed_url = "http://www.hellointernet.fm/podcast?format=rss"
19
18
  podcasts_dir = "podcasts"
20
19
 
@@ -40,10 +40,9 @@ dependencies = [
40
40
  "requests>=2.18",
41
41
  # for _http_utils
42
42
  "werkzeug>2",
43
- # for JSON Feed date parsing
44
- "iso8601>=1",
45
43
  # search
46
44
  "beautifulsoup4>=4.5",
45
+ "structlog",
47
46
  ]
48
47
  dynamic = ["version"]
49
48
 
@@ -60,7 +59,8 @@ Changelog = "https://reader.readthedocs.io/en/latest/changelog.html"
60
59
  all = ["reader[cli,app,unstable-plugins]"]
61
60
 
62
61
  cli = [
63
- "click>=7",
62
+ # != 8.4.0 because of https://github.com/pallets/click/issues/3458
63
+ "click>=7,!=8.4.0",
64
64
  ]
65
65
  app = [
66
66
  "flask>=0.10",
@@ -190,6 +190,10 @@ module = [
190
190
  ]
191
191
  ignore_errors = true
192
192
 
193
+ [tool.black]
194
+ skip-string-normalization = true
195
+ extend-exclude = "src/reader/_vendor/.*"
196
+
193
197
  [tool.isort]
194
198
  profile = "black"
195
199
  py_version = 311
@@ -199,4 +203,3 @@ skip_gitignore = true
199
203
  filter_files = true
200
204
  force_single_line = true
201
205
  order_by_type = false
202
- lines_after_imports = 2
@@ -14,7 +14,6 @@ from functools import partial
14
14
 
15
15
  import click
16
16
 
17
-
18
17
  root_dir = os.path.dirname(__file__)
19
18
  sys.path.insert(0, os.path.join(root_dir, '../src'))
20
19
  sys.path.insert(0, os.path.join(root_dir, '../tests'))
@@ -29,7 +29,6 @@ import click
29
29
  import numpy as np
30
30
  from tabulate import tabulate
31
31
 
32
-
33
32
  # assumes dict()s are ordered
34
33
 
35
34
  value_funcs = {
@@ -21,7 +21,6 @@ import time
21
21
  from reader import make_reader
22
22
  from reader.plugins import entry_dedupe
23
23
 
24
-
25
24
  DB_ARCHIVE = "_backups/reader.sqlite.2025-10-04.gz"
26
25
 
27
26
  file = sys.argv[1]
@@ -8,14 +8,12 @@ from flask import jsonify
8
8
  from flask import redirect
9
9
  from flask import request
10
10
 
11
-
12
11
  root_dir = os.path.dirname(__file__)
13
12
  sys.path.insert(0, os.path.join(root_dir, '../src'))
14
13
 
15
14
  from reader._app.api_thing import APIError
16
15
  from reader._app.api_thing import APIThing
17
16
 
18
-
19
17
  app = Flask(
20
18
  __name__,
21
19
  template_folder='../src/reader/_app/templates',
@@ -3,7 +3,6 @@ import shutil
3
3
 
4
4
  import requests
5
5
 
6
-
7
6
  STUFF = {
8
7
  "htmx.org@2.0.8": ["dist/htmx.min.js"],
9
8
  "htmx-ext-response-targets@2.0.4": ["dist/response-targets.min.js"],
@@ -37,7 +37,7 @@ Print the titles of the unread entries::
37
37
 
38
38
  """
39
39
 
40
- __version__ = '3.22'
40
+ __version__ = '3.24'
41
41
 
42
42
  # isort: off
43
43
 
@@ -64,6 +64,10 @@ from .types import (
64
64
  UpdateResult as UpdateResult,
65
65
  UpdatedFeed as UpdatedFeed,
66
66
  EntryUpdateStatus as EntryUpdateStatus,
67
+ FeedToImport as FeedToImport,
68
+ FeedImportResult as FeedImportResult,
69
+ FeedExport as FeedExport,
70
+ Author as Author,
67
71
  )
68
72
 
69
73
  from .exceptions import (
@@ -91,17 +95,20 @@ from .exceptions import (
91
95
  InvalidPluginError as InvalidPluginError,
92
96
  PluginInitError as PluginInitError,
93
97
  ReaderWarning as ReaderWarning,
98
+ FeedImportError as FeedImportError,
94
99
  )
95
100
 
96
-
97
101
  # Constants.
98
102
 
99
- USER_AGENT = f'python-reader/{__version__} (+https://github.com/lemon24/reader)'
103
+ SOURCE_URL = 'https://github.com/lemon24/reader'
104
+ USER_AGENT = f'python-reader/{__version__} (+{SOURCE_URL})'
100
105
 
101
106
 
102
107
  # Prevent any logging output by default. If no handler is set,
103
108
  # the messages bubble up to the root logger and get printed on stderr.
104
109
  # https://docs.python.org/3/howto/logging.html#library-config
110
+
105
111
  import logging # noqa: E402
112
+ from ._logging import enable_structlog as enable_structlog # noqa: E402
106
113
 
107
114
  logging.getLogger('reader').addHandler(logging.NullHandler())
@@ -1,6 +1,5 @@
1
1
  import sys
2
2
 
3
-
4
3
  CANNOT_IMPORT = """\
5
4
  Error: cannot import reader._cli
6
5
 
@@ -1,4 +1,6 @@
1
+ import json
1
2
  import pathlib
3
+ from dataclasses import asdict
2
4
  from datetime import datetime
3
5
  from datetime import timedelta
4
6
  from datetime import timezone
@@ -28,7 +30,9 @@ from werkzeug.exceptions import NotFound
28
30
  from reader import EntryNotFoundError
29
31
  from reader import FeedExistsError
30
32
  from reader import FeedNotFoundError
33
+ from reader import FeedToImport
31
34
  from reader import InvalidFeedURLError
35
+ from reader import opml
32
36
  from reader import UpdateError
33
37
 
34
38
  from .ext import get_reader
@@ -38,7 +42,6 @@ from .forms import ChangeFeedTitle
38
42
  from .forms import EntryFilter
39
43
  from .forms import FeedFilter
40
44
 
41
-
42
45
  # for a prototype with tags and search support, see
43
46
  # https://github.com/lemon24/reader/tree/3.21/src/reader/_app/v2
44
47
 
@@ -164,6 +167,10 @@ def feeds():
164
167
  if form.validate():
165
168
  feeds = reader.get_feeds(**kwargs)
166
169
 
170
+ if request.args.get('format', '').lower() == 'opml':
171
+ export = reader.export_feeds(feeds)
172
+ return export.content, export.headers
173
+
167
174
  return stream_template(
168
175
  'feeds.html',
169
176
  form=form,
@@ -270,6 +277,38 @@ def add_feed():
270
277
  return render_template('add_feed.html', form=form)
271
278
 
272
279
 
280
+ @blueprint.route('/feeds/import', methods=['GET', 'POST'])
281
+ def import_feeds():
282
+ reader = get_reader()
283
+ parsed_feeds = None
284
+ error = None
285
+ imported_feeds = None
286
+
287
+ if request.method == 'POST':
288
+ if file := request.files.get('file'):
289
+ try:
290
+ parsed_feeds = [asdict(f) for f in opml.parse(file.stream)]
291
+ except opml.OPMLError as e:
292
+ error = str(e)
293
+ else:
294
+ if not parsed_feeds:
295
+ parsed_feeds = None
296
+ error = "file contains no feeds"
297
+ else:
298
+ feeds = (
299
+ FeedToImport(**json.loads(f)) for f in request.form.getlist('feed')
300
+ )
301
+ imported_feeds = list(reader.import_feeds_iter(feeds))
302
+ # TODO: out of band update (unlike add, there's too many to do here)
303
+
304
+ return render_template(
305
+ 'import_feeds.html',
306
+ parsed_feeds=parsed_feeds,
307
+ error=error,
308
+ imported_feeds=imported_feeds,
309
+ )
310
+
311
+
273
312
  @blueprint.route('/entry')
274
313
  def entry():
275
314
  reader = get_reader()
@@ -277,6 +316,11 @@ def entry():
277
316
  return render_template('entry.html', entry=entry)
278
317
 
279
318
 
319
+ @blueprint.route('/help')
320
+ def help():
321
+ return render_template('help.html')
322
+
323
+
280
324
  def stream_template(template_name_or_list, **kwargs):
281
325
  # Ensure flashed messages get removed from the session,
282
326
  # otherwise they keep adding up and never disappear.
@@ -350,6 +394,11 @@ def _find_static(folder, filename):
350
394
  return matches[0]
351
395
 
352
396
 
397
+ @blueprint.app_template_global()
398
+ def is_new_user():
399
+ return not list(get_reader().get_feeds(limit=1))
400
+
401
+
353
402
  def create_app(reader_config):
354
403
  app = Flask(__name__)
355
404
  app.config['SECRET_KEY'] = 'secret'
@@ -10,7 +10,6 @@ from flask import current_app
10
10
  from reader import make_reader
11
11
  from reader.plugins._loader import PluginLoader
12
12
 
13
-
14
13
  _plugin_loader = PluginLoader('init_app')
15
14
 
16
15
 
@@ -44,7 +44,6 @@ from ..ext import ReaderExtension
44
44
  from .api_thing import APIError
45
45
  from .api_thing import APIThing
46
46
 
47
-
48
47
  blueprint = Blueprint(
49
48
  'reader', __name__, static_folder='static', template_folder='templates'
50
49
  )