Skip to content

Commit 0387003

Browse files
authored
Upgrade to Python 3.7 and above (realpython#12)
* Reformat with Black * Reformat with Isort * Add f-strings and proper type hints * Make docstrings PEP257 compatible
1 parent 5364f9c commit 0387003

File tree

9 files changed

+68
-76
lines changed

9 files changed

+68
-76
lines changed

README.md

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@ For more information see the tutorial [How to Publish an Open-Source Python Pack
88

99
You can install the Real Python Feed Reader from [PyPI](https://pypi.org/project/realpython-reader/):
1010

11-
pip install realpython-reader
11+
python -m pip install realpython-reader
1212

13-
The reader is supported on Python 2.7, as well as Python 3.4 and above.
13+
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.
1414

1515
## How to use
1616

17-
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:
17+
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:
1818

1919
$ realpython
2020
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
6262
>>> from reader import feed
6363
>>> feed.get_titles()
6464
['How to Publish an Open-Source Python Package to PyPI', ...]
65-

reader/__init__.py

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,22 @@
1-
"""Real Python feed reader
1+
"""Real Python feed reader.
22
33
Import the `feed` module to work with the Real Python feed:
44
55
>>> from reader import feed
66
>>> feed.get_titles()
77
['Logging in Python', 'The Best Python Books', ...]
88
9-
See https://github.com/realpython/reader/ for more information
9+
See https://github.com/realpython/reader/ for more information.
1010
"""
11-
import importlib_resources as _resources
12-
try:
13-
from configparser import ConfigParser as _ConfigParser
14-
except ImportError: # Python 2
15-
from ConfigParser import ConfigParser as _ConfigParser
16-
11+
from configparser import ConfigParser
12+
from importlib import resources
1713

1814
# Version of realpython-reader package
1915
__version__ = "1.0.0"
2016

2117
# Read URL of feed from config file
22-
_cfg = _ConfigParser()
23-
with _resources.path("reader", "config.cfg") as _path:
24-
_cfg.read(str(_path))
25-
URL = _cfg.get("feed", "url")
18+
cfg = ConfigParser()
19+
with resources.path("reader", "config.cfg") as path:
20+
cfg.read(str(path))
21+
22+
URL = cfg.get("feed", "url")

reader/__main__.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
"""Read the latest Real Python tutorials
1+
"""Read the latest Real Python tutorials.
22
33
Usage:
44
------
@@ -47,19 +47,18 @@
4747

4848
# Reader imports
4949
import reader
50-
from reader import feed
51-
from reader import viewer
50+
from reader import feed, viewer
5251

5352

54-
def main(): # type: () -> None
55-
"""Read the Real Python article feed"""
53+
def main() -> None:
54+
"""Read the Real Python article feed."""
5655
args = [a for a in sys.argv[1:] if not a.startswith("-")]
5756
opts = [o for o in sys.argv[1:] if o.startswith("-")]
5857

5958
# Show help message
6059
if "-h" in opts or "--help" in opts:
6160
viewer.show(__doc__)
62-
return
61+
raise SystemExit()
6362

6463
# Should links be shown in the text
6564
show_links = "-l" in opts or "--show-links" in opts

reader/feed.py

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
"""Interact with the Real Python feed"""
1+
"""Interact with the Real Python feed."""
22
# Standard library imports
33
from typing import Dict, List # noqa
44

@@ -9,32 +9,31 @@
99
# Reader imports
1010
from reader import URL
1111

12-
_CACHED_FEEDS = dict() # type: Dict[str, feedparser.FeedParserDict]
12+
_CACHED_FEEDS: Dict[str, feedparser.FeedParserDict] = {}
1313

1414

15-
def _feed(url=URL): # type: (str) -> feedparser.FeedParserDict
16-
"""Cache contents of the feed, so it's only read once"""
15+
def _feed(url: str = URL) -> feedparser.FeedParserDict:
16+
"""Cache contents of the feed, so it's only read once."""
1717
if url not in _CACHED_FEEDS:
1818
_CACHED_FEEDS[url] = feedparser.parse(url)
1919
return _CACHED_FEEDS[url]
2020

2121

22-
def get_site(url=URL): # type: (str) -> str
23-
"""Get name and link to web site of the feed"""
22+
def get_site(url: str = URL) -> str:
23+
"""Get name and link to web site of the feed."""
2424
info = _feed(url).feed
25-
return u"{info.title} ({info.link})".format(info=info)
25+
return f"{info.title} ({info.link})"
2626

2727

28-
def get_article(article_id, links=False, url=URL):
29-
# type: (str, bool, str) -> str
30-
"""Get article from feed with the given ID"""
28+
def get_article(article_id: str, links: bool = False, url: str = URL) -> str:
29+
"""Get article from feed with the given ID."""
3130
articles = _feed(url).entries
3231
try:
3332
article = articles[int(article_id)]
3433
except (IndexError, ValueError):
3534
max_id = len(articles) - 1
36-
msg = "Unknown article ID, use ID from 0 to {}".format(max_id)
37-
raise SystemExit("Error: {}".format(msg))
35+
msg = f"Unknown article ID, use ID from 0 to {max_id}"
36+
raise SystemExit(f"Error: {msg}")
3837

3938
# Get article as HTML
4039
try:
@@ -47,10 +46,10 @@ def get_article(article_id, links=False, url=URL):
4746
to_text.ignore_links = not links
4847
text = to_text.handle(html)
4948

50-
return u"# {}\n\n{}".format(article.title, text)
49+
return f"# {article.title}\n\n{text}"
5150

5251

53-
def get_titles(url=URL): # type: (str) -> List[str]
54-
"""List titles in feed"""
52+
def get_titles(url: str = URL) -> List[str]:
53+
"""List titles in feed."""
5554
articles = _feed(url).entries
5655
return [a.title for a in articles]

reader/viewer.py

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,16 @@
1-
"""Functions for displaying the Real Python feed"""
2-
3-
# Support Python 2
4-
from __future__ import print_function
1+
"""Functions for displaying the Real Python feed."""
52

63
# Standard library imports
7-
from typing import List # noqa
4+
from typing import List
85

96

10-
def show(article): # type: (str) -> None
11-
"""Show one article"""
7+
def show(article: str) -> None:
8+
"""Show one article."""
129
print(article)
1310

1411

15-
def show_list(site, titles): # type: (str, List[str]) -> None
16-
"""Show list of articles"""
17-
print(u"The latest tutorials from {}".format(site))
12+
def show_list(site: str, titles: List[str]) -> None:
13+
"""Show list of articles."""
14+
print(f"The latest tutorials from {site}")
1815
for article_id, title in enumerate(titles):
19-
print(u"{:>3} {}".format(article_id, title))
16+
print(f"{article_id:>3} {title}")

setup.cfg

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
[mypy]
2+
strict = True
3+
4+
[mypy-feedparser.*]
5+
ignore_missing_imports = True

setup.py

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
"""Setup script for realpython-reader"""
22

3-
import os.path
3+
# Standard library imports
4+
import pathlib
5+
6+
# Third party imports
47
from setuptools import setup
58

69
# The directory containing this file
7-
HERE = os.path.abspath(os.path.dirname(__file__))
10+
HERE = pathlib.Path(__file__).resolve().parent
811

9-
# The text of the README file
10-
with open(os.path.join(HERE, "README.md")) as fid:
11-
README = fid.read()
12+
# The text of the README file is used as a description
13+
README = (HERE / "README.md").read_text()
1214

1315
# This call to setup() does all the work
1416
setup(
@@ -24,13 +26,10 @@
2426
classifiers=[
2527
"License :: OSI Approved :: MIT License",
2628
"Programming Language :: Python",
27-
"Programming Language :: Python :: 2",
2829
"Programming Language :: Python :: 3",
2930
],
3031
packages=["reader"],
3132
include_package_data=True,
32-
install_requires=[
33-
"feedparser", "html2text", "importlib_resources", "typing"
34-
],
33+
install_requires=["feedparser", "html2text"],
3534
entry_points={"console_scripts": ["realpython=reader.__main__:main"]},
3635
)

tests/test_feed.py

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
"""Tests for the reader.feed module"""
1+
"""Tests for the reader.feed module."""
22
# Standard library imports
3-
import os.path
3+
import pathlib
44

55
# Third party imports
66
import pytest
@@ -9,32 +9,32 @@
99
from reader import feed
1010

1111
# Current directory
12-
HERE = os.path.dirname(__file__)
12+
HERE = pathlib.Path(__file__).resolve().parent
1313

1414

1515
@pytest.fixture
1616
def local_feed():
17-
"""Use local file instead of downloading feed from web"""
18-
return os.path.join(HERE, "realpython_20180919.xml")
17+
"""Use local file instead of downloading feed from web."""
18+
return HERE / "realpython_20180919.xml"
1919

2020

2121
@pytest.fixture
2222
def local_summary_feed():
23-
"""Use local file instead of downloading feed from web"""
24-
return os.path.join(HERE, "realpython_descriptions_20180919.xml")
23+
"""Use local file instead of downloading feed from web."""
24+
return HERE / "realpython_descriptions_20180919.xml"
2525

2626

2727
#
2828
# Tests
2929
#
3030
def test_site(local_feed):
31-
"""Test that we can read the site title and link"""
31+
"""Test that we can read the site title and link."""
3232
expected = "Real Python (https://realpython.com/)"
3333
assert feed.get_site(url=local_feed) == expected
3434

3535

3636
def test_article_title(local_feed):
37-
"""Test that title is added at top of article"""
37+
"""Test that title is added at top of article."""
3838
article_id = 0
3939
title = feed.get_titles(url=local_feed)[article_id]
4040
article = feed.get_article(article_id, url=local_feed)
@@ -43,7 +43,7 @@ def test_article_title(local_feed):
4343

4444

4545
def test_article(local_feed):
46-
"""Test that article is returned"""
46+
"""Test that article is returned."""
4747
article_id = 2
4848
article_phrases = [
4949
"logging.info('This is an info message')",
@@ -57,7 +57,7 @@ def test_article(local_feed):
5757

5858

5959
def test_titles(local_feed):
60-
"""Test that titles are found"""
60+
"""Test that titles are found."""
6161
titles = feed.get_titles(url=local_feed)
6262

6363
assert len(titles) == 20
@@ -66,7 +66,7 @@ def test_titles(local_feed):
6666

6767

6868
def test_summary(local_summary_feed):
69-
"""Test that summary feeds can be read"""
69+
"""Test that summary feeds can be read."""
7070
article_id = 1
7171
summary_phrases = [
7272
"Get the inside scoop",
@@ -79,7 +79,7 @@ def test_summary(local_summary_feed):
7979

8080

8181
def test_invalid_article_id(local_feed):
82-
"""Test that invalid article ids are handled gracefully"""
82+
"""Test that invalid article ids are handled gracefully."""
8383
article_id = "wrong"
8484
with pytest.raises(SystemExit):
8585
feed.get_article(article_id, url=local_feed)

tests/test_viewer.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
1-
"""Tests for the reader.viewer module"""
2-
3-
# Third party imports
4-
import pytest
1+
"""Tests for the reader.viewer module."""
52

63
# Reader imports
74
from reader import viewer
@@ -11,7 +8,7 @@
118
# Tests
129
#
1310
def test_show(capsys):
14-
"""Test that show adds information to stdout"""
11+
"""Test that show adds information to stdout."""
1512
text = "Lorem ipsum dolor sit amet"
1613
viewer.show(text)
1714
stdout, stderr = capsys.readouterr()
@@ -22,7 +19,7 @@ def test_show(capsys):
2219

2320

2421
def test_show_list(capsys):
25-
"""Test that show_list shows a list of items with an ID"""
22+
"""Test that show_list shows a list of items with an ID."""
2623
site = "Real Python"
2724
things = ["pathlib", "data classes", "python 3.7", "decorators"]
2825
viewer.show_list(site, things)

0 commit comments

Comments
 (0)