From bd0dd863ed1b8888b16c1a3ea32decd6947e6f0b Mon Sep 17 00:00:00 2001 From: Geir Arne Hjelle Date: Tue, 2 Oct 2018 22:17:41 +0200 Subject: [PATCH 01/24] Add version information in help text --- .bumpversion.cfg | 5 +++-- reader/__main__.py | 6 ++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 2f7c250..e4c0c3c 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -3,7 +3,8 @@ current_version = 0.1.0 commit = False tag = False -[bumpversion:file:reader/__init__.py] - [bumpversion:file:setup.py] +[bumpversion:file:reader/__init__.py] + +[bumpversion:file:reader/__main__.py] diff --git a/reader/__main__.py b/reader/__main__.py index 62b8ae0..ffefa3a 100644 --- a/reader/__main__.py +++ b/reader/__main__.py @@ -35,6 +35,12 @@ - https://pypi.org/project/realpython-reader/ - https://github.com/realpython/reader + + +Version: +-------- + +- realpython-reader v0.1.0 """ # Standard library imports import sys From cf1fec223fec07cedfa31ec841423dc191316e91 Mon Sep 17 00:00:00 2001 From: Geir Arne Hjelle Date: Tue, 2 Oct 2018 22:18:21 +0200 Subject: [PATCH 02/24] Also setup.py needs to run on Python 2 and 3 --- setup.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/setup.py b/setup.py index 8e17548..bc9c1a3 100644 --- a/setup.py +++ b/setup.py @@ -1,14 +1,14 @@ """Setup script for realpython-reader""" -import pathlib +import os.path from setuptools import setup # The directory containing this file -HERE = pathlib.Path(__file__).parent +HERE = os.path.abspath(os.path.dirname(__file__)) # The text of the README file -README = (HERE / "README.md").read_text() - +with open(os.path.join(HERE, "README.md")) as fid: + README = fid.read() # This call to setup() does all the work setup( From a62f920d5218a8d29a64682ca6994181bb05b14c Mon Sep 17 00:00:00 2001 From: Geir Arne Hjelle Date: Tue, 2 Oct 2018 22:19:01 +0200 Subject: [PATCH 03/24] Update to v0.1.1 --- .bumpversion.cfg | 3 ++- reader/__init__.py | 2 +- reader/__main__.py | 2 +- setup.py | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index e4c0c3c..59d6c09 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.1.0 +current_version = 0.1.1 commit = False tag = False @@ -8,3 +8,4 @@ tag = False [bumpversion:file:reader/__init__.py] [bumpversion:file:reader/__main__.py] + diff --git a/reader/__init__.py b/reader/__init__.py index 1e498c8..67cacf6 100644 --- a/reader/__init__.py +++ b/reader/__init__.py @@ -9,7 +9,7 @@ See https://github.com/realpython/reader/ for more information """ # Version of realpython-reader package -__version__ = "0.1.0" +__version__ = "0.1.1" # URL of Real Python feed URL = "https://realpython.com/atom.xml" diff --git a/reader/__main__.py b/reader/__main__.py index ffefa3a..7476415 100644 --- a/reader/__main__.py +++ b/reader/__main__.py @@ -40,7 +40,7 @@ Version: -------- -- realpython-reader v0.1.0 +- realpython-reader v0.1.1 """ # Standard library imports import sys diff --git a/setup.py b/setup.py index bc9c1a3..7207ecc 100644 --- a/setup.py +++ b/setup.py @@ -13,7 +13,7 @@ # This call to setup() does all the work setup( name="realpython-reader", - version="0.1.0", + version="0.1.1", description="Read Real Python tutorials", long_description=README, long_description_content_type="text/markdown", From 3b65ed1c2fbafa99a134a4bc0756d2b15de270ad Mon Sep 17 00:00:00 2001 From: Geir Arne Hjelle Date: Thu, 11 Oct 2018 21:35:53 +0200 Subject: [PATCH 04/24] Create config file with url, make url an optional parameter to feed functions --- MANIFEST.in | 1 + reader/__init__.py | 14 ++++++++++++-- reader/__main__.py | 10 +++++++--- reader/config.cfg | 2 ++ reader/feed.py | 23 ++++++++++++----------- reader/viewer.py | 4 ++-- setup.py | 6 +++++- tests/test_feed.py | 38 +++++++++++++++++--------------------- 8 files changed, 58 insertions(+), 40 deletions(-) create mode 100644 MANIFEST.in create mode 100644 reader/config.cfg diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..8d401be --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1 @@ +include reader/*.cfg diff --git a/reader/__init__.py b/reader/__init__.py index 67cacf6..6cf3890 100644 --- a/reader/__init__.py +++ b/reader/__init__.py @@ -8,8 +8,18 @@ See https://github.com/realpython/reader/ for more information """ +import importlib_resources as _resources +try: + from configparser import ConfigParser as _ConfigParser +except ImportError: # Python 2 + from ConfigParser import ConfigParser as _ConfigParser + + # Version of realpython-reader package __version__ = "0.1.1" -# URL of Real Python feed -URL = "https://realpython.com/atom.xml" +# Read URL of feed from config file +_cfg = _ConfigParser() +with _resources.path("reader", "config.cfg") as _path: + _cfg.read(str(_path)) +URL = _cfg.get("feed", "url") diff --git a/reader/__main__.py b/reader/__main__.py index 7476415..a9855b4 100644 --- a/reader/__main__.py +++ b/reader/__main__.py @@ -46,6 +46,7 @@ import sys # Reader imports +import reader from reader import feed from reader import viewer @@ -63,16 +64,19 @@ def main(): # type: () -> None # Should links be shown in the text show_links = "-l" in opts or "--show-links" in opts + # Get URL from config file + url = reader.URL + # An article ID is given, show article if args: for article_id in args: - article = feed.get_article(article_id, show_links) + article = feed.get_article(article_id, links=show_links, url=url) viewer.show(article) # No ID is given, show list of articles else: - site = feed.get_site() - titles = feed.get_titles() + site = feed.get_site(url=url) + titles = feed.get_titles(url=url) viewer.show_list(site, titles) diff --git a/reader/config.cfg b/reader/config.cfg new file mode 100644 index 0000000..3c6ea8a --- /dev/null +++ b/reader/config.cfg @@ -0,0 +1,2 @@ +[feed] +url = https://realpython.com/atom.xml diff --git a/reader/feed.py b/reader/feed.py index a566da1..7854637 100644 --- a/reader/feed.py +++ b/reader/feed.py @@ -12,22 +12,23 @@ _CACHED_FEEDS = dict() # type: Dict[str, feedparser.FeedParserDict] -def _feed(): # type: () -> feedparser.FeedParserDict +def _feed(url=URL): # type: (str) -> feedparser.FeedParserDict """Cache contents of the feed, so it's only read once""" - if URL not in _CACHED_FEEDS: - _CACHED_FEEDS[URL] = feedparser.parse(URL) - return _CACHED_FEEDS[URL] + if url not in _CACHED_FEEDS: + _CACHED_FEEDS[url] = feedparser.parse(url) + return _CACHED_FEEDS[url] -def get_site(): # type: () -> str +def get_site(url=URL): # type: (str) -> str """Get name and link to web site of the feed""" - info = _feed().feed - return "{info.title} ({info.link})".format(info=info) + info = _feed(url).feed + return u"{info.title} ({info.link})".format(info=info) -def get_article(article_id, links=False): # type: (str, bool) -> str +def get_article(article_id, links=False, url=URL): + # type: (str, bool, str) -> str """Get article from feed with the given ID""" - articles = _feed().entries + articles = _feed(url).entries try: article = articles[int(article_id)] except (IndexError, ValueError): @@ -49,7 +50,7 @@ def get_article(article_id, links=False): # type: (str, bool) -> str return u"# {}\n\n{}".format(article.title, text) -def get_titles(): # type: () -> List[str] +def get_titles(url=URL): # type: (str) -> List[str] """List titles in feed""" - articles = _feed().entries + articles = _feed(url).entries return [a.title for a in articles] diff --git a/reader/viewer.py b/reader/viewer.py index 33b578b..1cd6ab8 100644 --- a/reader/viewer.py +++ b/reader/viewer.py @@ -14,6 +14,6 @@ def show(article): # type: (str) -> None def show_list(site, titles): # type: (str, List[str]) -> None """Show list of articles""" - print("The latest tutorials from {}".format(site)) + print(u"The latest tutorials from {}".format(site)) for article_id, title in enumerate(titles): - print("{:>3} {}".format(article_id, title)) + print(u"{:>3} {}".format(article_id, title)) diff --git a/setup.py b/setup.py index 7207ecc..bb9b375 100644 --- a/setup.py +++ b/setup.py @@ -28,6 +28,10 @@ "Programming Language :: Python :: 3", ], packages=["reader"], - install_requires=["feedparser", "html2text", "typing"], + package_data={"reader": ["reader/config.cfg"]}, + include_package_data=True, + install_requires=[ + "feedparser", "html2text", "importlib_resources", "typing" + ], entry_points={"console_scripts": ["realpython=reader.__main__:main"]}, ) diff --git a/tests/test_feed.py b/tests/test_feed.py index 513359e..7c75bd7 100644 --- a/tests/test_feed.py +++ b/tests/test_feed.py @@ -13,40 +13,36 @@ @pytest.fixture -def monkeypatch_feed(monkeypatch): +def local_feed(): """Use local file instead of downloading feed from web""" - local_path = os.path.join(HERE, "realpython_20180919.xml") - monkeypatch.setattr(feed, "URL", local_path) - return local_path + return os.path.join(HERE, "realpython_20180919.xml") @pytest.fixture -def monkeypatch_summary_feed(monkeypatch): +def local_summary_feed(): """Use local file instead of downloading feed from web""" - local_path = os.path.join(HERE, "realpython_descriptions_20180919.xml") - monkeypatch.setattr(feed, "URL", local_path) - return local_path + return os.path.join(HERE, "realpython_descriptions_20180919.xml") # # Tests # -def test_site(monkeypatch_feed): +def test_site(local_feed): """Test that we can read the site title and link""" expected = "Real Python (https://realpython.com/)" - assert feed.get_site() == expected + assert feed.get_site(url=local_feed) == expected -def test_article_title(monkeypatch_feed): +def test_article_title(local_feed): """Test that title is added at top of article""" article_id = 0 - title = feed.get_titles()[article_id] - article = feed.get_article(article_id) + title = feed.get_titles(url=local_feed)[article_id] + article = feed.get_article(article_id, url=local_feed) assert article.strip("# ").startswith(title) -def test_article(monkeypatch_feed): +def test_article(local_feed): """Test that article is returned""" article_id = 2 article_phrases = [ @@ -54,36 +50,36 @@ def test_article(monkeypatch_feed): "By using the `level` parameter", " * `level`: The root logger", ] - article = feed.get_article(article_id) + article = feed.get_article(article_id, url=local_feed) for phrase in article_phrases: assert phrase in article -def test_titles(monkeypatch_feed): +def test_titles(local_feed): """Test that titles are found""" - titles = feed.get_titles() + titles = feed.get_titles(url=local_feed) assert len(titles) == 20 assert titles[0] == "Absolute vs Relative Imports in Python" assert titles[9] == "Primer on Python Decorators" -def test_summary(monkeypatch_summary_feed): +def test_summary(local_summary_feed): """Test that summary feeds can be read""" article_id = 1 summary_phrases = [ "Get the inside scoop", "this list of\ninformative videos", ] - summary = feed.get_article(article_id) + summary = feed.get_article(article_id, url=local_summary_feed) for phrase in summary_phrases: assert phrase in summary -def test_invalid_article_id(monkeypatch_feed): +def test_invalid_article_id(local_feed): """Test that invalid article ids are handled gracefully""" article_id = "wrong" with pytest.raises(SystemExit): - feed.get_article(article_id) + feed.get_article(article_id, url=local_feed) From 769d38271ca6152ab11c62353918d885b9ea994e Mon Sep 17 00:00:00 2001 From: Geir Arne Hjelle Date: Thu, 11 Oct 2018 21:38:55 +0200 Subject: [PATCH 05/24] Update to v0.2.0 --- .bumpversion.cfg | 2 +- reader/__init__.py | 2 +- reader/__main__.py | 2 +- setup.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 59d6c09..024c9bb 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.1.1 +current_version = 0.2.0 commit = False tag = False diff --git a/reader/__init__.py b/reader/__init__.py index 6cf3890..51b6c10 100644 --- a/reader/__init__.py +++ b/reader/__init__.py @@ -16,7 +16,7 @@ # Version of realpython-reader package -__version__ = "0.1.1" +__version__ = "0.2.0" # Read URL of feed from config file _cfg = _ConfigParser() diff --git a/reader/__main__.py b/reader/__main__.py index a9855b4..c5a03d9 100644 --- a/reader/__main__.py +++ b/reader/__main__.py @@ -40,7 +40,7 @@ Version: -------- -- realpython-reader v0.1.1 +- realpython-reader v0.2.0 """ # Standard library imports import sys diff --git a/setup.py b/setup.py index bb9b375..2ab8af4 100644 --- a/setup.py +++ b/setup.py @@ -13,7 +13,7 @@ # This call to setup() does all the work setup( name="realpython-reader", - version="0.1.1", + version="0.2.0", description="Read Real Python tutorials", long_description=README, long_description_content_type="text/markdown", From c0c601e50200a1b814800dacdf501a8ec823a8c8 Mon Sep 17 00:00:00 2001 From: Geir Arne Hjelle Date: Sat, 20 Oct 2018 19:44:21 +0200 Subject: [PATCH 06/24] Change description --- setup.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 2ab8af4..03683dd 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ setup( name="realpython-reader", version="0.2.0", - description="Read Real Python tutorials", + description="Read the latest Real Python tutorials", long_description=README, long_description_content_type="text/markdown", url="https://github.com/realpython/reader", @@ -28,7 +28,6 @@ "Programming Language :: Python :: 3", ], packages=["reader"], - package_data={"reader": ["reader/config.cfg"]}, include_package_data=True, install_requires=[ "feedparser", "html2text", "importlib_resources", "typing" From cbec6f6b0399f0833f67e0e58f4bdcc314916755 Mon Sep 17 00:00:00 2001 From: Geir Arne Hjelle Date: Mon, 12 Nov 2018 15:11:59 +0100 Subject: [PATCH 07/24] Update README to use the latest articles as example --- README.md | 65 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 34 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 90e8c88..9394aba 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ The Real Python Feed Reader is a basic [web feed](https://en.wikipedia.org/wiki/Web_feed) reader that can download the latest Real Python tutorials from the [Real Python feed](https://realpython.com/contact/#rss-atom-feed). +For more information see the tutorial [How to Publish an Open-Source Python Package to PyPI](https://realpython.com/pypi-publish-python-package/) on Real Python. + ## Installation You can install the Real Python Feed Reader from [PyPI](https://pypi.org/project/realpython-reader/): @@ -16,41 +18,42 @@ The Real Python Feed Reader is a command line application, named `realpython`. T $ realpython The latest tutorials from Real Python (https://realpython.com/) - 0 Splitting, Concatenating, and Joining Strings in Python - 1 Image Segmentation Using Color Spaces in OpenCV + Python - 2 Python Community Interview With Mahdi Yusuf - 3 Absolute vs Relative Imports in Python - 4 Top 10 Must-Watch PyCon Talks - 5 Logging in Python - 6 The Best Python Books - 7 Conditional Statements in Python - 8 Structuring Python Programs - 9 We're Celebrating 1 Million Page Views per Month! - 10 Python Pandas: Tricks & Features You May Not Know - 11 Python Community Interview With Mariatta Wijaya - 12 Primer on Python Decorators - 13 Sets in Python - 14 The Ultimate Guide to Django Redirects - 15 Advanced Git Tips for Python Developers - 16 Python Community Interview With Mike Driscoll - 17 Dictionaries in Python - 18 Socket Programming in Python (Guide) - 19 Python Code Quality: Tools & Best Practices + 0 How to Publish an Open-Source Python Package to PyPI + 1 Python "while" Loops (Indefinite Iteration) + 2 Writing Comments in Python (Guide) + 3 Setting Up Python for Machine Learning on Windows + 4 Python Community Interview With Michael Kennedy + 5 Practical Text Classification With Python and Keras + 6 Getting Started With Testing in Python + 7 Python, Boto3, and AWS S3: Demystified + 8 Python's range() Function (Guide) + 9 Python Community Interview With Mike Grouchy + 10 How to Round Numbers in Python + 11 Building and Documenting Python REST APIs With Flask and Connexion – Part 2 + 12 Splitting, Concatenating, and Joining Strings in Python + 13 Image Segmentation Using Color Spaces in OpenCV + Python + 14 Python Community Interview With Mahdi Yusuf + 15 Absolute vs Relative Imports in Python + 16 Top 10 Must-Watch PyCon Talks + 17 Logging in Python + 18 The Best Python Books + 19 Conditional Statements in Python To read one particular tutorial, call the program with the numerical ID of the tutorial as a parameter: $ realpython 0 - # Splitting, Concatenating, and Joining Strings in Python - - There are few guarantees in life: death, taxes, and programmers needing to - deal with strings. Strings can come in many forms. They could be unstructured - text, usernames, product descriptions, database column names, or really - anything else that we describe using language. - - With the near-ubiquity of string data, it's important to master the tools of - the trade when it comes to strings. Luckily, Python makes string manipulation - very simple, especially when compared to other languages and even older - versions of Python. + # How to Publish an Open-Source Python Package to PyPI + + Python is famous for coming with batteries included. Sophisticated + capabilities are available in the standard library. You can find modules for + working with sockets, parsing CSV, JSON, and XML files, and working with + files and file paths. + + However great the packages included with Python are, there are many + fantastic projects available outside the standard library. These are most + often hosted at the Python Packaging Index (PyPI), historically known as the + Cheese Shop. At PyPI, you can find everything from Hello World to advanced + deep learning libraries. [... The full text of the article ...] From bcef82076b92772ad2a862cc3e5daa678699e77f Mon Sep 17 00:00:00 2001 From: Geir Arne Hjelle Date: Mon, 12 Nov 2018 15:13:18 +0100 Subject: [PATCH 08/24] Update to version 1.0.0 --- .bumpversion.cfg | 2 +- reader/__init__.py | 2 +- reader/__main__.py | 2 +- setup.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 024c9bb..ef1ab63 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.2.0 +current_version = 1.0.0 commit = False tag = False diff --git a/reader/__init__.py b/reader/__init__.py index 51b6c10..0623b57 100644 --- a/reader/__init__.py +++ b/reader/__init__.py @@ -16,7 +16,7 @@ # Version of realpython-reader package -__version__ = "0.2.0" +__version__ = "1.0.0" # Read URL of feed from config file _cfg = _ConfigParser() diff --git a/reader/__main__.py b/reader/__main__.py index c5a03d9..a75e2a0 100644 --- a/reader/__main__.py +++ b/reader/__main__.py @@ -40,7 +40,7 @@ Version: -------- -- realpython-reader v0.2.0 +- realpython-reader v1.0.0 """ # Standard library imports import sys diff --git a/setup.py b/setup.py index 03683dd..323eb88 100644 --- a/setup.py +++ b/setup.py @@ -13,7 +13,7 @@ # This call to setup() does all the work setup( name="realpython-reader", - version="0.2.0", + version="1.0.0", description="Read the latest Real Python tutorials", long_description=README, long_description_content_type="text/markdown", From b923d4b6ca4747aaed9c486fb0646dfc738f1a55 Mon Sep 17 00:00:00 2001 From: Geir Arne Hjelle Date: Mon, 12 Nov 2018 15:20:11 +0100 Subject: [PATCH 09/24] Use correct header in last example in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9394aba..401cd45 100644 --- a/README.md +++ b/README.md @@ -61,5 +61,5 @@ You can also call the Real Python Feed Reader in your own Python code, by import >>> from reader import feed >>> feed.get_titles() - ['Splitting, Concatenating, and Joining Strings in Python', ...] + ['How to Publish an Open-Source Python Package to PyPI', ...] From 5364f9c361593f7de88d785d1414ce705a772e7e Mon Sep 17 00:00:00 2001 From: Dan Bader Date: Wed, 27 Nov 2019 11:52:45 -0800 Subject: [PATCH 10/24] Update setup.py --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 323eb88..50aa7b6 100644 --- a/setup.py +++ b/setup.py @@ -19,7 +19,7 @@ long_description_content_type="text/markdown", url="https://github.com/realpython/reader", author="Real Python", - author_email="office@realpython.com", + author_email="info@realpython.com", license="MIT", classifiers=[ "License :: OSI Approved :: MIT License", From 0387003b4eeb1afba4bb8357b46fe273dc725db8 Mon Sep 17 00:00:00 2001 From: Geir Arne Hjelle Date: Thu, 14 Oct 2021 12:46:12 +0200 Subject: [PATCH 11/24] Upgrade to Python 3.7 and above (#12) * Reformat with Black * Reformat with Isort * Add f-strings and proper type hints * Make docstrings PEP257 compatible --- README.md | 7 +++---- reader/__init__.py | 21 +++++++++------------ reader/__main__.py | 11 +++++------ reader/feed.py | 29 ++++++++++++++--------------- reader/viewer.py | 19 ++++++++----------- setup.cfg | 5 +++++ setup.py | 17 ++++++++--------- tests/test_feed.py | 26 +++++++++++++------------- tests/test_viewer.py | 9 +++------ 9 files changed, 68 insertions(+), 76 deletions(-) create mode 100644 setup.cfg diff --git a/README.md b/README.md index 401cd45..1a0111d 100644 --- a/README.md +++ b/README.md @@ -8,13 +8,13 @@ For more information see the tutorial [How to Publish an Open-Source Python Pack You can install the Real Python Feed Reader from [PyPI](https://pypi.org/project/realpython-reader/): - pip install realpython-reader + python -m pip install realpython-reader -The reader is supported on Python 2.7, as well as Python 3.4 and above. +The reader is supported on Python 3.7 and above. Older versions of Python, including Python 2.7, are supported by version 1.0.0 of the reader. ## How to use -The Real Python Feed Reader is a command line application, named `realpython`. To see a list of the [latest Real Python tutorials](https://realpython.com/) simply call the program: +The Real Python Feed Reader is a command line application, named `realpython`. To see a list of the [latest Real Python tutorials](https://realpython.com/), call the program without any arguments: $ realpython The latest tutorials from Real Python (https://realpython.com/) @@ -62,4 +62,3 @@ You can also call the Real Python Feed Reader in your own Python code, by import >>> from reader import feed >>> feed.get_titles() ['How to Publish an Open-Source Python Package to PyPI', ...] - diff --git a/reader/__init__.py b/reader/__init__.py index 0623b57..fa1933a 100644 --- a/reader/__init__.py +++ b/reader/__init__.py @@ -1,4 +1,4 @@ -"""Real Python feed reader +"""Real Python feed reader. Import the `feed` module to work with the Real Python feed: @@ -6,20 +6,17 @@ >>> feed.get_titles() ['Logging in Python', 'The Best Python Books', ...] -See https://github.com/realpython/reader/ for more information +See https://github.com/realpython/reader/ for more information. """ -import importlib_resources as _resources -try: - from configparser import ConfigParser as _ConfigParser -except ImportError: # Python 2 - from ConfigParser import ConfigParser as _ConfigParser - +from configparser import ConfigParser +from importlib import resources # Version of realpython-reader package __version__ = "1.0.0" # Read URL of feed from config file -_cfg = _ConfigParser() -with _resources.path("reader", "config.cfg") as _path: - _cfg.read(str(_path)) -URL = _cfg.get("feed", "url") +cfg = ConfigParser() +with resources.path("reader", "config.cfg") as path: + cfg.read(str(path)) + +URL = cfg.get("feed", "url") diff --git a/reader/__main__.py b/reader/__main__.py index a75e2a0..45a8925 100644 --- a/reader/__main__.py +++ b/reader/__main__.py @@ -1,4 +1,4 @@ -"""Read the latest Real Python tutorials +"""Read the latest Real Python tutorials. Usage: ------ @@ -47,19 +47,18 @@ # Reader imports import reader -from reader import feed -from reader import viewer +from reader import feed, viewer -def main(): # type: () -> None - """Read the Real Python article feed""" +def main() -> None: + """Read the Real Python article feed.""" args = [a for a in sys.argv[1:] if not a.startswith("-")] opts = [o for o in sys.argv[1:] if o.startswith("-")] # Show help message if "-h" in opts or "--help" in opts: viewer.show(__doc__) - return + raise SystemExit() # Should links be shown in the text show_links = "-l" in opts or "--show-links" in opts diff --git a/reader/feed.py b/reader/feed.py index 7854637..5dd0680 100644 --- a/reader/feed.py +++ b/reader/feed.py @@ -1,4 +1,4 @@ -"""Interact with the Real Python feed""" +"""Interact with the Real Python feed.""" # Standard library imports from typing import Dict, List # noqa @@ -9,32 +9,31 @@ # Reader imports from reader import URL -_CACHED_FEEDS = dict() # type: Dict[str, feedparser.FeedParserDict] +_CACHED_FEEDS: Dict[str, feedparser.FeedParserDict] = {} -def _feed(url=URL): # type: (str) -> feedparser.FeedParserDict - """Cache contents of the feed, so it's only read once""" +def _feed(url: str = URL) -> feedparser.FeedParserDict: + """Cache contents of the feed, so it's only read once.""" if url not in _CACHED_FEEDS: _CACHED_FEEDS[url] = feedparser.parse(url) return _CACHED_FEEDS[url] -def get_site(url=URL): # type: (str) -> str - """Get name and link to web site of the feed""" +def get_site(url: str = URL) -> str: + """Get name and link to web site of the feed.""" info = _feed(url).feed - return u"{info.title} ({info.link})".format(info=info) + return f"{info.title} ({info.link})" -def get_article(article_id, links=False, url=URL): - # type: (str, bool, str) -> str - """Get article from feed with the given ID""" +def get_article(article_id: str, links: bool = False, url: str = URL) -> str: + """Get article from feed with the given ID.""" articles = _feed(url).entries try: article = articles[int(article_id)] except (IndexError, ValueError): max_id = len(articles) - 1 - msg = "Unknown article ID, use ID from 0 to {}".format(max_id) - raise SystemExit("Error: {}".format(msg)) + msg = f"Unknown article ID, use ID from 0 to {max_id}" + raise SystemExit(f"Error: {msg}") # Get article as HTML try: @@ -47,10 +46,10 @@ def get_article(article_id, links=False, url=URL): to_text.ignore_links = not links text = to_text.handle(html) - return u"# {}\n\n{}".format(article.title, text) + return f"# {article.title}\n\n{text}" -def get_titles(url=URL): # type: (str) -> List[str] - """List titles in feed""" +def get_titles(url: str = URL) -> List[str]: + """List titles in feed.""" articles = _feed(url).entries return [a.title for a in articles] diff --git a/reader/viewer.py b/reader/viewer.py index 1cd6ab8..e2852df 100644 --- a/reader/viewer.py +++ b/reader/viewer.py @@ -1,19 +1,16 @@ -"""Functions for displaying the Real Python feed""" - -# Support Python 2 -from __future__ import print_function +"""Functions for displaying the Real Python feed.""" # Standard library imports -from typing import List # noqa +from typing import List -def show(article): # type: (str) -> None - """Show one article""" +def show(article: str) -> None: + """Show one article.""" print(article) -def show_list(site, titles): # type: (str, List[str]) -> None - """Show list of articles""" - print(u"The latest tutorials from {}".format(site)) +def show_list(site: str, titles: List[str]) -> None: + """Show list of articles.""" + print(f"The latest tutorials from {site}") for article_id, title in enumerate(titles): - print(u"{:>3} {}".format(article_id, title)) + print(f"{article_id:>3} {title}") diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..ee4e168 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,5 @@ +[mypy] +strict = True + +[mypy-feedparser.*] +ignore_missing_imports = True diff --git a/setup.py b/setup.py index 50aa7b6..d70f475 100644 --- a/setup.py +++ b/setup.py @@ -1,14 +1,16 @@ """Setup script for realpython-reader""" -import os.path +# Standard library imports +import pathlib + +# Third party imports from setuptools import setup # The directory containing this file -HERE = os.path.abspath(os.path.dirname(__file__)) +HERE = pathlib.Path(__file__).resolve().parent -# The text of the README file -with open(os.path.join(HERE, "README.md")) as fid: - README = fid.read() +# The text of the README file is used as a description +README = (HERE / "README.md").read_text() # This call to setup() does all the work setup( @@ -24,13 +26,10 @@ classifiers=[ "License :: OSI Approved :: MIT License", "Programming Language :: Python", - "Programming Language :: Python :: 2", "Programming Language :: Python :: 3", ], packages=["reader"], include_package_data=True, - install_requires=[ - "feedparser", "html2text", "importlib_resources", "typing" - ], + install_requires=["feedparser", "html2text"], entry_points={"console_scripts": ["realpython=reader.__main__:main"]}, ) diff --git a/tests/test_feed.py b/tests/test_feed.py index 7c75bd7..3c9697f 100644 --- a/tests/test_feed.py +++ b/tests/test_feed.py @@ -1,6 +1,6 @@ -"""Tests for the reader.feed module""" +"""Tests for the reader.feed module.""" # Standard library imports -import os.path +import pathlib # Third party imports import pytest @@ -9,32 +9,32 @@ from reader import feed # Current directory -HERE = os.path.dirname(__file__) +HERE = pathlib.Path(__file__).resolve().parent @pytest.fixture def local_feed(): - """Use local file instead of downloading feed from web""" - return os.path.join(HERE, "realpython_20180919.xml") + """Use local file instead of downloading feed from web.""" + return HERE / "realpython_20180919.xml" @pytest.fixture def local_summary_feed(): - """Use local file instead of downloading feed from web""" - return os.path.join(HERE, "realpython_descriptions_20180919.xml") + """Use local file instead of downloading feed from web.""" + return HERE / "realpython_descriptions_20180919.xml" # # Tests # def test_site(local_feed): - """Test that we can read the site title and link""" + """Test that we can read the site title and link.""" expected = "Real Python (https://realpython.com/)" assert feed.get_site(url=local_feed) == expected def test_article_title(local_feed): - """Test that title is added at top of article""" + """Test that title is added at top of article.""" article_id = 0 title = feed.get_titles(url=local_feed)[article_id] article = feed.get_article(article_id, url=local_feed) @@ -43,7 +43,7 @@ def test_article_title(local_feed): def test_article(local_feed): - """Test that article is returned""" + """Test that article is returned.""" article_id = 2 article_phrases = [ "logging.info('This is an info message')", @@ -57,7 +57,7 @@ def test_article(local_feed): def test_titles(local_feed): - """Test that titles are found""" + """Test that titles are found.""" titles = feed.get_titles(url=local_feed) assert len(titles) == 20 @@ -66,7 +66,7 @@ def test_titles(local_feed): def test_summary(local_summary_feed): - """Test that summary feeds can be read""" + """Test that summary feeds can be read.""" article_id = 1 summary_phrases = [ "Get the inside scoop", @@ -79,7 +79,7 @@ def test_summary(local_summary_feed): def test_invalid_article_id(local_feed): - """Test that invalid article ids are handled gracefully""" + """Test that invalid article ids are handled gracefully.""" article_id = "wrong" with pytest.raises(SystemExit): feed.get_article(article_id, url=local_feed) diff --git a/tests/test_viewer.py b/tests/test_viewer.py index 2bffbb2..b16af7a 100644 --- a/tests/test_viewer.py +++ b/tests/test_viewer.py @@ -1,7 +1,4 @@ -"""Tests for the reader.viewer module""" - -# Third party imports -import pytest +"""Tests for the reader.viewer module.""" # Reader imports from reader import viewer @@ -11,7 +8,7 @@ # Tests # def test_show(capsys): - """Test that show adds information to stdout""" + """Test that show adds information to stdout.""" text = "Lorem ipsum dolor sit amet" viewer.show(text) stdout, stderr = capsys.readouterr() @@ -22,7 +19,7 @@ def test_show(capsys): def test_show_list(capsys): - """Test that show_list shows a list of items with an ID""" + """Test that show_list shows a list of items with an ID.""" site = "Real Python" things = ["pathlib", "data classes", "python 3.7", "decorators"] viewer.show_list(site, things) From 8b310717ca7dbf6b46c2eda2342f7d152f00bee7 Mon Sep 17 00:00:00 2001 From: Geir Arne Hjelle Date: Thu, 19 May 2022 01:34:46 -0600 Subject: [PATCH 12/24] Switch from `setup.py` to `pyproject.toml` for configuration (#15) * Switch from setup.py to pyproject.toml * Switch bumpversion to bumpver * Move to a src/ based structure * Add explicit configuration of isort * Use TOML instead of INI for configuration --- .bumpversion.cfg | 11 ------ MANIFEST.in | 2 +- pyproject.toml | 60 ++++++++++++++++++++++++++++++ reader/config.cfg | 2 - setup.cfg | 5 --- setup.py | 34 +---------------- {reader => src/reader}/__init__.py | 18 +++++---- {reader => src/reader}/__main__.py | 0 src/reader/config.toml | 2 + {reader => src/reader}/feed.py | 0 {reader => src/reader}/viewer.py | 0 11 files changed, 75 insertions(+), 59 deletions(-) delete mode 100644 .bumpversion.cfg create mode 100644 pyproject.toml delete mode 100644 reader/config.cfg delete mode 100644 setup.cfg rename {reader => src/reader}/__init__.py (56%) rename {reader => src/reader}/__main__.py (100%) create mode 100644 src/reader/config.toml rename {reader => src/reader}/feed.py (100%) rename {reader => src/reader}/viewer.py (100%) diff --git a/.bumpversion.cfg b/.bumpversion.cfg deleted file mode 100644 index ef1ab63..0000000 --- a/.bumpversion.cfg +++ /dev/null @@ -1,11 +0,0 @@ -[bumpversion] -current_version = 1.0.0 -commit = False -tag = False - -[bumpversion:file:setup.py] - -[bumpversion:file:reader/__init__.py] - -[bumpversion:file:reader/__main__.py] - diff --git a/MANIFEST.in b/MANIFEST.in index 8d401be..f3d5d65 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1 +1 @@ -include reader/*.cfg +include src/reader/*.cfg diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..557c7b0 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,60 @@ +[build-system] +requires = ["setuptools>=61.0.0", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "realpython-reader" +version = "1.0.0" +description = "Read the latest Real Python tutorials" +readme = "README.md" +authors = [{ name = "Real Python", email = "info@realpython.com" }] +license = { file = "LICENSE" } +classifiers = [ + "License :: OSI Approved :: MIT License", + "Programming Language :: Python", + "Programming Language :: Python :: 3", +] +keywords = ["feed", "reader", "tutorial"] +dependencies = ["feedparser", "html2text", 'tomli; python_version < "3.11"'] +requires-python = ">=3.7" + + [project.optional-dependencies] + build = ["build", "twine"] + dev = ["black", "bumpver", "isort", "mypy", "pytest"] + + [project.scripts] + realpython = "reader.__main__:main" + + [project.urls] + repository = "https://github.com/realpython/reader" + documentation = "https://realpython.com/pypi-publish-python-package/" + + +[tool.bumpver] +current_version = "1.0.0" +version_pattern = "MAJOR.MINOR.PATCH" +commit_message = "bump version {old_version} -> {new_version}" +commit = true +tag = true +push = false + + [tool.bumpver.file_patterns] + "pyproject.toml" = [ + 'current_version = "{version}"', + 'version = "{version}"', + ] + "src/reader/__init__.py" = ["{version}"] + "src/reader/__main__.py" = ["- realpython-reader v{version}"] + +[tool.isort] +profile = "black" +import_heading_stdlib = "Standard library imports" +import_heading_thirdparty = "Third party imports" +import_heading_firstparty = "Reader imports" + +[tool.mypy] +strict = true + + [[tool.mypy.overrides]] + module = "feedparser" + ignore_missing_imports = true diff --git a/reader/config.cfg b/reader/config.cfg deleted file mode 100644 index 3c6ea8a..0000000 --- a/reader/config.cfg +++ /dev/null @@ -1,2 +0,0 @@ -[feed] -url = https://realpython.com/atom.xml diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index ee4e168..0000000 --- a/setup.cfg +++ /dev/null @@ -1,5 +0,0 @@ -[mypy] -strict = True - -[mypy-feedparser.*] -ignore_missing_imports = True diff --git a/setup.py b/setup.py index d70f475..6068493 100644 --- a/setup.py +++ b/setup.py @@ -1,35 +1,3 @@ -"""Setup script for realpython-reader""" - -# Standard library imports -import pathlib - -# Third party imports from setuptools import setup -# The directory containing this file -HERE = pathlib.Path(__file__).resolve().parent - -# The text of the README file is used as a description -README = (HERE / "README.md").read_text() - -# This call to setup() does all the work -setup( - name="realpython-reader", - version="1.0.0", - description="Read the latest Real Python tutorials", - long_description=README, - long_description_content_type="text/markdown", - url="https://github.com/realpython/reader", - author="Real Python", - author_email="info@realpython.com", - license="MIT", - classifiers=[ - "License :: OSI Approved :: MIT License", - "Programming Language :: Python", - "Programming Language :: Python :: 3", - ], - packages=["reader"], - include_package_data=True, - install_requires=["feedparser", "html2text"], - entry_points={"console_scripts": ["realpython=reader.__main__:main"]}, -) +setup() diff --git a/reader/__init__.py b/src/reader/__init__.py similarity index 56% rename from reader/__init__.py rename to src/reader/__init__.py index fa1933a..df23985 100644 --- a/reader/__init__.py +++ b/src/reader/__init__.py @@ -8,15 +8,19 @@ See https://github.com/realpython/reader/ for more information. """ -from configparser import ConfigParser +# Standard library imports from importlib import resources +try: + import tomllib +except ModuleNotFoundError: + # Third party imports + import tomli as tomllib + + # Version of realpython-reader package __version__ = "1.0.0" -# Read URL of feed from config file -cfg = ConfigParser() -with resources.path("reader", "config.cfg") as path: - cfg.read(str(path)) - -URL = cfg.get("feed", "url") +# Read URL of the Real Python feed from config file +_cfg = tomllib.loads(resources.read_text("reader", "config.toml")) +URL = _cfg["feed"]["url"] diff --git a/reader/__main__.py b/src/reader/__main__.py similarity index 100% rename from reader/__main__.py rename to src/reader/__main__.py diff --git a/src/reader/config.toml b/src/reader/config.toml new file mode 100644 index 0000000..b77aa11 --- /dev/null +++ b/src/reader/config.toml @@ -0,0 +1,2 @@ +[feed] +url = "https://realpython.com/atom.xml" diff --git a/reader/feed.py b/src/reader/feed.py similarity index 100% rename from reader/feed.py rename to src/reader/feed.py diff --git a/reader/viewer.py b/src/reader/viewer.py similarity index 100% rename from reader/viewer.py rename to src/reader/viewer.py From ceec1edcaedd6c1d5007c07309de740400d4c166 Mon Sep 17 00:00:00 2001 From: Geir Arne Hjelle Date: Thu, 19 May 2022 09:38:39 +0200 Subject: [PATCH 13/24] bump version 1.0.0 -> 1.1.0 --- pyproject.toml | 4 ++-- src/reader/__init__.py | 2 +- src/reader/__main__.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 557c7b0..702e4d3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "realpython-reader" -version = "1.0.0" +version = "1.1.0" description = "Read the latest Real Python tutorials" readme = "README.md" authors = [{ name = "Real Python", email = "info@realpython.com" }] @@ -31,7 +31,7 @@ requires-python = ">=3.7" [tool.bumpver] -current_version = "1.0.0" +current_version = "1.1.0" version_pattern = "MAJOR.MINOR.PATCH" commit_message = "bump version {old_version} -> {new_version}" commit = true diff --git a/src/reader/__init__.py b/src/reader/__init__.py index df23985..5506dd6 100644 --- a/src/reader/__init__.py +++ b/src/reader/__init__.py @@ -19,7 +19,7 @@ # Version of realpython-reader package -__version__ = "1.0.0" +__version__ = "1.1.0" # Read URL of the Real Python feed from config file _cfg = tomllib.loads(resources.read_text("reader", "config.toml")) diff --git a/src/reader/__main__.py b/src/reader/__main__.py index 45a8925..f8e32f8 100644 --- a/src/reader/__main__.py +++ b/src/reader/__main__.py @@ -40,7 +40,7 @@ Version: -------- -- realpython-reader v1.0.0 +- realpython-reader v1.1.0 """ # Standard library imports import sys From 0fb69c9e03ab9f4bcd55bd2b122930937425c212 Mon Sep 17 00:00:00 2001 From: Geir Arne Hjelle Date: Thu, 19 May 2022 11:43:19 +0200 Subject: [PATCH 14/24] Update MANIFEST to include TOML file --- MANIFEST.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MANIFEST.in b/MANIFEST.in index f3d5d65..83ce77d 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1 +1 @@ -include src/reader/*.cfg +include src/reader/*.toml From 0009bac290846d56dfe64a510789912c13c90605 Mon Sep 17 00:00:00 2001 From: Geir Arne Hjelle Date: Thu, 19 May 2022 11:43:33 +0200 Subject: [PATCH 15/24] bump version 1.1.0 -> 1.1.1 --- pyproject.toml | 4 ++-- src/reader/__init__.py | 2 +- src/reader/__main__.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 702e4d3..63c2d4d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "realpython-reader" -version = "1.1.0" +version = "1.1.1" description = "Read the latest Real Python tutorials" readme = "README.md" authors = [{ name = "Real Python", email = "info@realpython.com" }] @@ -31,7 +31,7 @@ requires-python = ">=3.7" [tool.bumpver] -current_version = "1.1.0" +current_version = "1.1.1" version_pattern = "MAJOR.MINOR.PATCH" commit_message = "bump version {old_version} -> {new_version}" commit = true diff --git a/src/reader/__init__.py b/src/reader/__init__.py index 5506dd6..d5520d9 100644 --- a/src/reader/__init__.py +++ b/src/reader/__init__.py @@ -19,7 +19,7 @@ # Version of realpython-reader package -__version__ = "1.1.0" +__version__ = "1.1.1" # Read URL of the Real Python feed from config file _cfg = tomllib.loads(resources.read_text("reader", "config.toml")) diff --git a/src/reader/__main__.py b/src/reader/__main__.py index f8e32f8..8aa7cac 100644 --- a/src/reader/__main__.py +++ b/src/reader/__main__.py @@ -40,7 +40,7 @@ Version: -------- -- realpython-reader v1.1.0 +- realpython-reader v1.1.1 """ # Standard library imports import sys From 9f3a18dc2e168a253020213248efb8c61c8efc97 Mon Sep 17 00:00:00 2001 From: Geir Arne Hjelle Date: Fri, 20 May 2022 17:06:37 +0200 Subject: [PATCH 16/24] Fix typo --- src/reader/feed.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/reader/feed.py b/src/reader/feed.py index 5dd0680..f8b761c 100644 --- a/src/reader/feed.py +++ b/src/reader/feed.py @@ -20,7 +20,7 @@ def _feed(url: str = URL) -> feedparser.FeedParserDict: def get_site(url: str = URL) -> str: - """Get name and link to web site of the feed.""" + """Get name and link to website of the feed.""" info = _feed(url).feed return f"{info.title} ({info.link})" From bbc8e8f39c47a0ddec1778d4ac12ebd8d9d9b36b Mon Sep 17 00:00:00 2001 From: Geir Arne Hjelle Date: Wed, 29 Mar 2023 11:13:34 +0200 Subject: [PATCH 17/24] Try to detect when users need to Install Certificates on Mac (#16) * Point users to Install Certificates on Mac * Use error message to detect certificate issue * Remove outdated noqa comment --- src/reader/feed.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/reader/feed.py b/src/reader/feed.py index f8b761c..16ad7be 100644 --- a/src/reader/feed.py +++ b/src/reader/feed.py @@ -1,6 +1,6 @@ """Interact with the Real Python feed.""" # Standard library imports -from typing import Dict, List # noqa +from typing import Dict, List # Third party imports import feedparser @@ -21,8 +21,17 @@ def _feed(url: str = URL) -> feedparser.FeedParserDict: def get_site(url: str = URL) -> str: """Get name and link to website of the feed.""" - info = _feed(url).feed - return f"{info.title} ({info.link})" + info = _feed(url) + if exception := info.get("bozo_exception"): + message = f"Could not read feed at {url}" + if "CERTIFICATE_VERIFY_FAILED" in str(exception): + message += ( + ".\n\nYou may need to manually install certificates by running " + "`Install Certificates` in your Python installation folder. " + "See https://realpython.com/installing-python/" + ) + raise SystemExit(message) + return f"{info.feed.title} ({info.feed.link})" def get_article(article_id: str, links: bool = False, url: str = URL) -> str: From 14d8dac1246f4f95d43c96cf20714d4a194678e1 Mon Sep 17 00:00:00 2001 From: Geir Arne Hjelle Date: Wed, 29 Mar 2023 12:11:10 +0200 Subject: [PATCH 18/24] Remove setup.py as it's no longer needed for editable installs (#17) --- setup.py | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 setup.py diff --git a/setup.py b/setup.py deleted file mode 100644 index 6068493..0000000 --- a/setup.py +++ /dev/null @@ -1,3 +0,0 @@ -from setuptools import setup - -setup() From b8398a3e039f672bceabccc80976bd06990ec740 Mon Sep 17 00:00:00 2001 From: Geir Arne Hjelle Date: Wed, 29 Mar 2023 12:15:56 +0200 Subject: [PATCH 19/24] bump version 1.1.1 -> 1.1.2 --- pyproject.toml | 4 ++-- src/reader/__init__.py | 2 +- src/reader/__main__.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 63c2d4d..bb37264 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "realpython-reader" -version = "1.1.1" +version = "1.1.2" description = "Read the latest Real Python tutorials" readme = "README.md" authors = [{ name = "Real Python", email = "info@realpython.com" }] @@ -31,7 +31,7 @@ requires-python = ">=3.7" [tool.bumpver] -current_version = "1.1.1" +current_version = "1.1.2" version_pattern = "MAJOR.MINOR.PATCH" commit_message = "bump version {old_version} -> {new_version}" commit = true diff --git a/src/reader/__init__.py b/src/reader/__init__.py index d5520d9..9c5c2be 100644 --- a/src/reader/__init__.py +++ b/src/reader/__init__.py @@ -19,7 +19,7 @@ # Version of realpython-reader package -__version__ = "1.1.1" +__version__ = "1.1.2" # Read URL of the Real Python feed from config file _cfg = tomllib.loads(resources.read_text("reader", "config.toml")) diff --git a/src/reader/__main__.py b/src/reader/__main__.py index 8aa7cac..57d5284 100644 --- a/src/reader/__main__.py +++ b/src/reader/__main__.py @@ -40,7 +40,7 @@ Version: -------- -- realpython-reader v1.1.1 +- realpython-reader v1.1.2 """ # Standard library imports import sys From 4238ba495629a3233b809f22fc21a072835c5f9b Mon Sep 17 00:00:00 2001 From: Ricky White Date: Mon, 30 Sep 2024 06:22:04 -0400 Subject: [PATCH 20/24] Add GitHub workflow files for CI/CD (#18) --- .github/dependabot.yaml | 12 ++++++++++++ .github/workflows/lint.yaml | 26 +++++++++++++++++++++++++ .github/workflows/publish.yaml | 35 ++++++++++++++++++++++++++++++++++ .github/workflows/test.yaml | 33 ++++++++++++++++++++++++++++++++ pyproject.toml | 2 +- 5 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 .github/dependabot.yaml create mode 100644 .github/workflows/lint.yaml create mode 100644 .github/workflows/publish.yaml create mode 100644 .github/workflows/test.yaml diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml new file mode 100644 index 0000000..4cc0a22 --- /dev/null +++ b/.github/dependabot.yaml @@ -0,0 +1,12 @@ +version: 2 +updates: + - package-ecosystem: "pip" + directory: "/" + schedule: + interval: "weekly" + + + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml new file mode 100644 index 0000000..33f5a29 --- /dev/null +++ b/.github/workflows/lint.yaml @@ -0,0 +1,26 @@ +name: Lint Python Code + +on: + pull_request: + branches: [ master ] + push: + branches: [ master ] + workflow_dispatch: + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + cache: 'pip' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install ruff + + - name: Run Ruff + run: ruff check --output-format=github diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml new file mode 100644 index 0000000..06ea368 --- /dev/null +++ b/.github/workflows/publish.yaml @@ -0,0 +1,35 @@ +name: Publish to PyPI +on: + push: + tags: + - '*.*.*' + +jobs: + publish: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install .[build] + + - name: Build package + run: python -m build + + - name: Publish package + uses: pypa/gh-action-pypi-publish@release/v1 + with: + user: __token__ + password: ${{ secrets.PYPI_API_TOKEN }} + + - name: Create GitHub Release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh release create ${{ github.ref_name }} ./dist/* --generate-notes diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 0000000..9c8b239 --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,33 @@ +name: Run Tests + +on: + push: + branches: [master] + pull_request: + branches: [master] + workflow_call: + workflow_dispatch: + +jobs: + testing: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + + steps: + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + cache: "pip" + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install .[dev] + + - name: Run Pytest + run: | + pytest diff --git a/pyproject.toml b/pyproject.toml index bb37264..90f7c3e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -57,4 +57,4 @@ strict = true [[tool.mypy.overrides]] module = "feedparser" - ignore_missing_imports = true + ignore_missing_imports = true \ No newline at end of file From ac72a3495e9d05d6b1839aac1ae3d9ee96b33ebf Mon Sep 17 00:00:00 2001 From: Ricky White Date: Mon, 14 Oct 2024 16:52:41 -0400 Subject: [PATCH 21/24] CI/CD updates + Bump version (#19) * Update CI/CD to reflect article changes * Version bump --- .github/dependabot.yaml | 1 + .github/workflows/publish.yaml | 7 +++++++ pyproject.toml | 4 ++-- src/reader/__init__.py | 2 +- src/reader/__main__.py | 2 +- 5 files changed, 12 insertions(+), 4 deletions(-) diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml index 4cc0a22..b783175 100644 --- a/.github/dependabot.yaml +++ b/.github/dependabot.yaml @@ -1,3 +1,4 @@ +--- version: 2 updates: - package-ecosystem: "pip" diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index 06ea368..cea9dae 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -28,6 +28,13 @@ jobs: user: __token__ password: ${{ secrets.PYPI_API_TOKEN }} + - name: Test publish package + uses: pypa/gh-action-pypi-publish@release/v1 + with: + user: __token__ + password: ${{ secrets.PYPI_API_TOKEN }} + repository-url: https://test.pypi.org/legacy/ + - name: Create GitHub Release env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/pyproject.toml b/pyproject.toml index 90f7c3e..42e2049 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "realpython-reader" -version = "1.1.2" +version = "1.1.3" description = "Read the latest Real Python tutorials" readme = "README.md" authors = [{ name = "Real Python", email = "info@realpython.com" }] @@ -31,7 +31,7 @@ requires-python = ">=3.7" [tool.bumpver] -current_version = "1.1.2" +current_version = "1.1.3" version_pattern = "MAJOR.MINOR.PATCH" commit_message = "bump version {old_version} -> {new_version}" commit = true diff --git a/src/reader/__init__.py b/src/reader/__init__.py index 9c5c2be..c7a1d21 100644 --- a/src/reader/__init__.py +++ b/src/reader/__init__.py @@ -19,7 +19,7 @@ # Version of realpython-reader package -__version__ = "1.1.2" +__version__ = "1.1.3" # Read URL of the Real Python feed from config file _cfg = tomllib.loads(resources.read_text("reader", "config.toml")) diff --git a/src/reader/__main__.py b/src/reader/__main__.py index 57d5284..8dce069 100644 --- a/src/reader/__main__.py +++ b/src/reader/__main__.py @@ -40,7 +40,7 @@ Version: -------- -- realpython-reader v1.1.2 +- realpython-reader v1.1.3 """ # Standard library imports import sys From 63620e0ac1b3f8f428d316511fe873c60804806d Mon Sep 17 00:00:00 2001 From: Geir Arne Hjelle Date: Mon, 14 Oct 2024 23:22:35 +0200 Subject: [PATCH 22/24] Fix token for TestPyPI (#20) --- .github/workflows/publish.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index cea9dae..688ab4e 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -32,7 +32,7 @@ jobs: uses: pypa/gh-action-pypi-publish@release/v1 with: user: __token__ - password: ${{ secrets.PYPI_API_TOKEN }} + password: ${{ secrets.TESTPYPI_API_TOKEN }} repository-url: https://test.pypi.org/legacy/ - name: Create GitHub Release From ad951f724fc14e41cef71c86c4cf45a80251e8b4 Mon Sep 17 00:00:00 2001 From: Ricky White Date: Mon, 14 Oct 2024 17:53:38 -0400 Subject: [PATCH 23/24] Add 3.13 to testing workflow and make 3.9 the min version (#21) --- .github/workflows/lint.yaml | 2 +- .github/workflows/publish.yaml | 2 +- .github/workflows/test.yaml | 2 +- pyproject.toml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 33f5a29..2f647cc 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -14,7 +14,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: - python-version: '3.12' + python-version: '3.13' cache: 'pip' - name: Install dependencies diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index 688ab4e..512e334 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -12,7 +12,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: '3.12' + python-version: '3.13' - name: Install dependencies run: | diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 9c8b239..2f6ec97 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] steps: - uses: actions/checkout@v4 diff --git a/pyproject.toml b/pyproject.toml index 42e2049..4a06025 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,7 +16,7 @@ classifiers = [ ] keywords = ["feed", "reader", "tutorial"] dependencies = ["feedparser", "html2text", 'tomli; python_version < "3.11"'] -requires-python = ">=3.7" +requires-python = ">=3.9" [project.optional-dependencies] build = ["build", "twine"] From fe3712693f6220587d76c71f777a0d71b6ba14c6 Mon Sep 17 00:00:00 2001 From: Geir Arne Hjelle Date: Mon, 14 Oct 2024 23:58:08 +0200 Subject: [PATCH 24/24] bump version 1.1.3 -> 1.1.4 --- pyproject.toml | 4 ++-- src/reader/__init__.py | 2 +- src/reader/__main__.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 4a06025..b2be1ab 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "realpython-reader" -version = "1.1.3" +version = "1.1.4" description = "Read the latest Real Python tutorials" readme = "README.md" authors = [{ name = "Real Python", email = "info@realpython.com" }] @@ -31,7 +31,7 @@ requires-python = ">=3.9" [tool.bumpver] -current_version = "1.1.3" +current_version = "1.1.4" version_pattern = "MAJOR.MINOR.PATCH" commit_message = "bump version {old_version} -> {new_version}" commit = true diff --git a/src/reader/__init__.py b/src/reader/__init__.py index c7a1d21..383f287 100644 --- a/src/reader/__init__.py +++ b/src/reader/__init__.py @@ -19,7 +19,7 @@ # Version of realpython-reader package -__version__ = "1.1.3" +__version__ = "1.1.4" # Read URL of the Real Python feed from config file _cfg = tomllib.loads(resources.read_text("reader", "config.toml")) diff --git a/src/reader/__main__.py b/src/reader/__main__.py index 8dce069..56e5fa7 100644 --- a/src/reader/__main__.py +++ b/src/reader/__main__.py @@ -40,7 +40,7 @@ Version: -------- -- realpython-reader v1.1.3 +- realpython-reader v1.1.4 """ # Standard library imports import sys