├── .github ├── dependabot.yaml └── workflows │ ├── lint.yaml │ ├── publish.yaml │ └── test.yaml ├── .gitignore ├── LICENSE ├── MANIFEST.in ├── README.md ├── pyproject.toml ├── src └── reader │ ├── __init__.py │ ├── __main__.py │ ├── config.toml │ ├── feed.py │ └── viewer.py └── tests ├── realpython_20180919.xml ├── realpython_descriptions_20180919.xml ├── test_feed.py └── test_viewer.py /.github/dependabot.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | version: 2 3 | updates: 4 | - package-ecosystem: "pip" 5 | directory: "/" 6 | schedule: 7 | interval: "weekly" 8 | 9 | 10 | - package-ecosystem: "github-actions" 11 | directory: "/" 12 | schedule: 13 | interval: "weekly" 14 | -------------------------------------------------------------------------------- /.github/workflows/lint.yaml: -------------------------------------------------------------------------------- 1 | name: Lint Python Code 2 | 3 | on: 4 | pull_request: 5 | branches: [ master ] 6 | push: 7 | branches: [ master ] 8 | workflow_dispatch: 9 | 10 | jobs: 11 | lint: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: actions/setup-python@v5 16 | with: 17 | python-version: '3.13' 18 | cache: 'pip' 19 | 20 | - name: Install dependencies 21 | run: | 22 | python -m pip install --upgrade pip 23 | python -m pip install ruff 24 | 25 | - name: Run Ruff 26 | run: ruff check --output-format=github 27 | -------------------------------------------------------------------------------- /.github/workflows/publish.yaml: -------------------------------------------------------------------------------- 1 | name: Publish to PyPI 2 | on: 3 | push: 4 | tags: 5 | - '*.*.*' 6 | 7 | jobs: 8 | publish: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v4 12 | - name: Set up Python 13 | uses: actions/setup-python@v5 14 | with: 15 | python-version: '3.13' 16 | 17 | - name: Install dependencies 18 | run: | 19 | python -m pip install --upgrade pip 20 | python -m pip install .[build] 21 | 22 | - name: Build package 23 | run: python -m build 24 | 25 | - name: Publish package 26 | uses: pypa/gh-action-pypi-publish@release/v1 27 | with: 28 | user: __token__ 29 | password: ${{ secrets.PYPI_API_TOKEN }} 30 | 31 | - name: Test publish package 32 | uses: pypa/gh-action-pypi-publish@release/v1 33 | with: 34 | user: __token__ 35 | password: ${{ secrets.TESTPYPI_API_TOKEN }} 36 | repository-url: https://test.pypi.org/legacy/ 37 | 38 | - name: Create GitHub Release 39 | env: 40 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 41 | run: | 42 | gh release create ${{ github.ref_name }} ./dist/* --generate-notes 43 | -------------------------------------------------------------------------------- /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | name: Run Tests 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | workflow_call: 9 | workflow_dispatch: 10 | 11 | jobs: 12 | testing: 13 | runs-on: ubuntu-latest 14 | strategy: 15 | matrix: 16 | python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] 17 | 18 | steps: 19 | - uses: actions/checkout@v4 20 | - name: Set up Python ${{ matrix.python-version }} 21 | uses: actions/setup-python@v5 22 | with: 23 | python-version: ${{ matrix.python-version }} 24 | cache: "pip" 25 | 26 | - name: Install dependencies 27 | run: | 28 | python -m pip install --upgrade pip 29 | python -m pip install .[dev] 30 | 31 | - name: Run Pytest 32 | run: | 33 | pytest 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Real Python 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include src/reader/*.toml 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Real Python Feed Reader 2 | 3 | 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). 4 | 5 | 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. 6 | 7 | ## Installation 8 | 9 | You can install the Real Python Feed Reader from [PyPI](https://pypi.org/project/realpython-reader/): 10 | 11 | python -m pip install realpython-reader 12 | 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. 14 | 15 | ## How to use 16 | 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: 18 | 19 | $ realpython 20 | The latest tutorials from Real Python (https://realpython.com/) 21 | 0 How to Publish an Open-Source Python Package to PyPI 22 | 1 Python "while" Loops (Indefinite Iteration) 23 | 2 Writing Comments in Python (Guide) 24 | 3 Setting Up Python for Machine Learning on Windows 25 | 4 Python Community Interview With Michael Kennedy 26 | 5 Practical Text Classification With Python and Keras 27 | 6 Getting Started With Testing in Python 28 | 7 Python, Boto3, and AWS S3: Demystified 29 | 8 Python's range() Function (Guide) 30 | 9 Python Community Interview With Mike Grouchy 31 | 10 How to Round Numbers in Python 32 | 11 Building and Documenting Python REST APIs With Flask and Connexion – Part 2 33 | 12 Splitting, Concatenating, and Joining Strings in Python 34 | 13 Image Segmentation Using Color Spaces in OpenCV + Python 35 | 14 Python Community Interview With Mahdi Yusuf 36 | 15 Absolute vs Relative Imports in Python 37 | 16 Top 10 Must-Watch PyCon Talks 38 | 17 Logging in Python 39 | 18 The Best Python Books 40 | 19 Conditional Statements in Python 41 | 42 | To read one particular tutorial, call the program with the numerical ID of the tutorial as a parameter: 43 | 44 | $ realpython 0 45 | # How to Publish an Open-Source Python Package to PyPI 46 | 47 | Python is famous for coming with batteries included. Sophisticated 48 | capabilities are available in the standard library. You can find modules for 49 | working with sockets, parsing CSV, JSON, and XML files, and working with 50 | files and file paths. 51 | 52 | However great the packages included with Python are, there are many 53 | fantastic projects available outside the standard library. These are most 54 | often hosted at the Python Packaging Index (PyPI), historically known as the 55 | Cheese Shop. At PyPI, you can find everything from Hello World to advanced 56 | deep learning libraries. 57 | 58 | [... The full text of the article ...] 59 | 60 | You can also call the Real Python Feed Reader in your own Python code, by importing from the `reader` package: 61 | 62 | >>> from reader import feed 63 | >>> feed.get_titles() 64 | ['How to Publish an Open-Source Python Package to PyPI', ...] 65 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools>=61.0.0", "wheel"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = "realpython-reader" 7 | version = "1.1.4" 8 | description = "Read the latest Real Python tutorials" 9 | readme = "README.md" 10 | authors = [{ name = "Real Python", email = "info@realpython.com" }] 11 | license = { file = "LICENSE" } 12 | classifiers = [ 13 | "License :: OSI Approved :: MIT License", 14 | "Programming Language :: Python", 15 | "Programming Language :: Python :: 3", 16 | ] 17 | keywords = ["feed", "reader", "tutorial"] 18 | dependencies = ["feedparser", "html2text", 'tomli; python_version < "3.11"'] 19 | requires-python = ">=3.9" 20 | 21 | [project.optional-dependencies] 22 | build = ["build", "twine"] 23 | dev = ["black", "bumpver", "isort", "mypy", "pytest"] 24 | 25 | [project.scripts] 26 | realpython = "reader.__main__:main" 27 | 28 | [project.urls] 29 | repository = "https://github.com/realpython/reader" 30 | documentation = "https://realpython.com/pypi-publish-python-package/" 31 | 32 | 33 | [tool.bumpver] 34 | current_version = "1.1.4" 35 | version_pattern = "MAJOR.MINOR.PATCH" 36 | commit_message = "bump version {old_version} -> {new_version}" 37 | commit = true 38 | tag = true 39 | push = false 40 | 41 | [tool.bumpver.file_patterns] 42 | "pyproject.toml" = [ 43 | 'current_version = "{version}"', 44 | 'version = "{version}"', 45 | ] 46 | "src/reader/__init__.py" = ["{version}"] 47 | "src/reader/__main__.py" = ["- realpython-reader v{version}"] 48 | 49 | [tool.isort] 50 | profile = "black" 51 | import_heading_stdlib = "Standard library imports" 52 | import_heading_thirdparty = "Third party imports" 53 | import_heading_firstparty = "Reader imports" 54 | 55 | [tool.mypy] 56 | strict = true 57 | 58 | [[tool.mypy.overrides]] 59 | module = "feedparser" 60 | ignore_missing_imports = true -------------------------------------------------------------------------------- /src/reader/__init__.py: -------------------------------------------------------------------------------- 1 | """Real Python feed reader. 2 | 3 | Import the `feed` module to work with the Real Python feed: 4 | 5 | >>> from reader import feed 6 | >>> feed.get_titles() 7 | ['Logging in Python', 'The Best Python Books', ...] 8 | 9 | See https://github.com/realpython/reader/ for more information. 10 | """ 11 | # Standard library imports 12 | from importlib import resources 13 | 14 | try: 15 | import tomllib 16 | except ModuleNotFoundError: 17 | # Third party imports 18 | import tomli as tomllib 19 | 20 | 21 | # Version of realpython-reader package 22 | __version__ = "1.1.4" 23 | 24 | # Read URL of the Real Python feed from config file 25 | _cfg = tomllib.loads(resources.read_text("reader", "config.toml")) 26 | URL = _cfg["feed"]["url"] 27 | -------------------------------------------------------------------------------- /src/reader/__main__.py: -------------------------------------------------------------------------------- 1 | """Read the latest Real Python tutorials. 2 | 3 | Usage: 4 | ------ 5 | 6 | $ realpython [options] [id] [id ...] 7 | 8 | List the latest tutorials: 9 | 10 | $ realpython 11 | 12 | Read one tutorial: 13 | 14 | $ realpython 15 | 16 | where is the number shown when listing tutorials. 17 | 18 | Read the latest tutorial: 19 | 20 | $ realpython 0 21 | 22 | 23 | Available options are: 24 | 25 | -h, --help Show this help 26 | -l, --show-links Show links in text 27 | 28 | 29 | Contact: 30 | -------- 31 | 32 | - https://realpython.com/contact/ 33 | 34 | More information is available at: 35 | 36 | - https://pypi.org/project/realpython-reader/ 37 | - https://github.com/realpython/reader 38 | 39 | 40 | Version: 41 | -------- 42 | 43 | - realpython-reader v1.1.4 44 | """ 45 | # Standard library imports 46 | import sys 47 | 48 | # Reader imports 49 | import reader 50 | from reader import feed, viewer 51 | 52 | 53 | def main() -> None: 54 | """Read the Real Python article feed.""" 55 | args = [a for a in sys.argv[1:] if not a.startswith("-")] 56 | opts = [o for o in sys.argv[1:] if o.startswith("-")] 57 | 58 | # Show help message 59 | if "-h" in opts or "--help" in opts: 60 | viewer.show(__doc__) 61 | raise SystemExit() 62 | 63 | # Should links be shown in the text 64 | show_links = "-l" in opts or "--show-links" in opts 65 | 66 | # Get URL from config file 67 | url = reader.URL 68 | 69 | # An article ID is given, show article 70 | if args: 71 | for article_id in args: 72 | article = feed.get_article(article_id, links=show_links, url=url) 73 | viewer.show(article) 74 | 75 | # No ID is given, show list of articles 76 | else: 77 | site = feed.get_site(url=url) 78 | titles = feed.get_titles(url=url) 79 | viewer.show_list(site, titles) 80 | 81 | 82 | if __name__ == "__main__": 83 | main() 84 | -------------------------------------------------------------------------------- /src/reader/config.toml: -------------------------------------------------------------------------------- 1 | [feed] 2 | url = "https://realpython.com/atom.xml" 3 | -------------------------------------------------------------------------------- /src/reader/feed.py: -------------------------------------------------------------------------------- 1 | """Interact with the Real Python feed.""" 2 | # Standard library imports 3 | from typing import Dict, List 4 | 5 | # Third party imports 6 | import feedparser 7 | import html2text 8 | 9 | # Reader imports 10 | from reader import URL 11 | 12 | _CACHED_FEEDS: Dict[str, feedparser.FeedParserDict] = {} 13 | 14 | 15 | def _feed(url: str = URL) -> feedparser.FeedParserDict: 16 | """Cache contents of the feed, so it's only read once.""" 17 | if url not in _CACHED_FEEDS: 18 | _CACHED_FEEDS[url] = feedparser.parse(url) 19 | return _CACHED_FEEDS[url] 20 | 21 | 22 | def get_site(url: str = URL) -> str: 23 | """Get name and link to website of the feed.""" 24 | info = _feed(url) 25 | if exception := info.get("bozo_exception"): 26 | message = f"Could not read feed at {url}" 27 | if "CERTIFICATE_VERIFY_FAILED" in str(exception): 28 | message += ( 29 | ".\n\nYou may need to manually install certificates by running " 30 | "`Install Certificates` in your Python installation folder. " 31 | "See https://realpython.com/installing-python/" 32 | ) 33 | raise SystemExit(message) 34 | return f"{info.feed.title} ({info.feed.link})" 35 | 36 | 37 | def get_article(article_id: str, links: bool = False, url: str = URL) -> str: 38 | """Get article from feed with the given ID.""" 39 | articles = _feed(url).entries 40 | try: 41 | article = articles[int(article_id)] 42 | except (IndexError, ValueError): 43 | max_id = len(articles) - 1 44 | msg = f"Unknown article ID, use ID from 0 to {max_id}" 45 | raise SystemExit(f"Error: {msg}") 46 | 47 | # Get article as HTML 48 | try: 49 | html = article.content[0].value 50 | except AttributeError: 51 | html = article.summary 52 | 53 | # Convert HTML to plain text 54 | to_text = html2text.HTML2Text() 55 | to_text.ignore_links = not links 56 | text = to_text.handle(html) 57 | 58 | return f"# {article.title}\n\n{text}" 59 | 60 | 61 | def get_titles(url: str = URL) -> List[str]: 62 | """List titles in feed.""" 63 | articles = _feed(url).entries 64 | return [a.title for a in articles] 65 | -------------------------------------------------------------------------------- /src/reader/viewer.py: -------------------------------------------------------------------------------- 1 | """Functions for displaying the Real Python feed.""" 2 | 3 | # Standard library imports 4 | from typing import List 5 | 6 | 7 | def show(article: str) -> None: 8 | """Show one article.""" 9 | print(article) 10 | 11 | 12 | def show_list(site: str, titles: List[str]) -> None: 13 | """Show list of articles.""" 14 | print(f"The latest tutorials from {site}") 15 | for article_id, title in enumerate(titles): 16 | print(f"{article_id:>3} {title}") 17 | -------------------------------------------------------------------------------- /tests/realpython_descriptions_20180919.xml: -------------------------------------------------------------------------------- 1 | 2 | Real Pythonhttps://realpython.com/atom-descriptions-only.xml2018-09-19T00:00:00+00:00Real PythonAbsolute vs Relative Imports in Python2018-09-19T00:00:00+00:00https://realpython.com/absolute-vs-relative-python-imports/If you’ve worked on a Python project that has more than one file, chances are you’ve had to use an import statement before. In this tutorial, you’ll not only cover the pros and cons of absolute and relative imports but also learn about the best practices for writing import statementsTop 10 Must-Watch PyCon Talks2018-09-17T00:00:00+00:00https://realpython.com/must-watch-pycon-talks/Get the inside scoop on the top 10 must-watch PyCon talks for both beginners and advanced Python developers. There's something for everyone in this list of informative videosLogging in Python2018-09-12T00:00:00+00:00https://realpython.com/python-logging/Learn why and how to get started with Python's powerful logging module to meet the needs of beginners and enterprise teams alikeThe Best Python Books2018-09-10T00:00:00+00:00https://realpython.com/best-python-books/Find the right books to help you get started with Python or take your coding to the next level with this detailed guide to the best Python books out thereConditional Statements in Python2018-09-05T00:00:00+00:00https://realpython.com/python-conditional-statements/In this step-by-step tutorial you'll learn how to work with conditional statements in Python. Master if-statements and see how to write complex decision making code in your programsStructuring Python Programs2018-09-03T00:00:00+00:00https://realpython.com/python-program-structure/In this tutorial you'll dig deeper into Python's lexical structure and start arranging code into more complex groupings. You'll learn about the syntactic elements that comprise statements, the basic units that make up a Python programWe&#39;re Celebrating 1 Million Page Views per Month!2018-09-01T00:00:00+00:00https://realpython.com/one-million-pageviews-celebration/Today we're celebrating reaching 1,000,000 monthly page views on realpython.com. We are so thankful to you and the rest of the Python community for helping us reach this milestonePython Pandas: Tricks &amp; Features You May Not Know2018-08-29T00:00:00+00:00https://realpython.com/python-pandas-tricks/Lesser-known but idiomatic Pandas features for those already comfortable with Pandas' basic functionality and conceptsPython Community Interview With Mariatta Wijaya2018-08-27T00:00:00+00:00https://realpython.com/interview-mariatta-wijaya/Mariatta is a web developer at Zapier and volunteers much of her time to helping maintain Python as a core developer. In this interview we talk about her role as a Python core developer, as well as her love of GitHub bots and #icecreamselfiesPrimer on Python Decorators2018-08-22T00:00:00+00:00https://realpython.com/primer-on-python-decorators/In this introductory tutorial, we'll look at what Python decorators are and how to create and use themSets in Python2018-08-20T00:00:00+00:00https://realpython.com/python-sets/In this tutorial you'll learn how to work effectively with Python's set data type. You'll see how to define set objects in Python and discover the operations that they support and by the end of the tutorial you'll have a good feel for when a set is an appropriate choice in your own programsThe Ultimate Guide to Django Redirects2018-08-15T00:00:00+00:00https://realpython.com/django-redirects/In this detailed guide, you'll learn everything you need to know about HTTP redirects in Django. All the way from the low-level details of the HTTP protocol to the high-level way of dealing with them in DjangoAdvanced Git Tips for Python Developers2018-08-13T00:00:00+00:00https://realpython.com/advanced-git-for-pythonistas/In this Git tutorial for Python developers, we'll talk about how to address specific commits and entire ranges of commits, using the stash to save temporary work, comparing different commits, changing history, and how to clean up the mess if something doesn't work outPython Community Interview With Mike Driscoll2018-08-08T00:00:00+00:00https://realpython.com/interview-mike-driscoll/A Python community interview with Mike Driscoll of Mouse Vs Python fame. As a long-time Python advocate and teacher, Mike shares his story of how he came to be a Python developer and an authorDictionaries in Python2018-08-06T00:00:00+00:00https://realpython.com/python-dicts/In this Python dictionaries tutorial you'll cover the basic characteristics and learn how to access and manage dictionary data. Once you have finished this tutorial, you should have a good sense of when a dictionary is the appropriate data type to use, and how to do soSocket Programming in Python (Guide)2018-08-01T00:00:00+00:00https://realpython.com/python-sockets/In this in-depth tutorial you'll learn how to build a socket server and client with Python. By the end of this tutorial, you'll understand how to use the main functions and methods in Python's socket module to write your own networked client-server applicationsPython Code Quality: Tools &amp; Best Practices2018-07-30T00:00:00+00:00https://realpython.com/python-code-quality/In this article, you'll see how to improve the quality of your Python code. We'll analyze and compare tools you can use to take your code to the next level and make it more Pythonic. Whether you've been using Python for a while, or just beginning, you can benefit from the practices and tools talked about hereDocumenting Python Code: A Complete Guide2018-07-25T00:00:00+00:00https://realpython.com/documenting-python-code/A complete guide to documenting Python code. Whether you're documenting a small script or a large project, whether you're a beginner or seasoned Pythonista, this guide will cover everything you need to knowFast, Flexible, Easy and Intuitive: How to Speed Up Your Pandas Projects2018-07-23T00:00:00+00:00https://realpython.com/fast-flexible-pandas/What is it about Pandas that has data scientists, analysts, and engineers raving? This is a guide to using Pandas Pythonically to get the most out of its powerful and easy-to-use built-in features. Additionally, you will learn a couple of practical time-saving tipsLists and Tuples in Python2018-07-18T00:00:00+00:00https://realpython.com/python-lists-tuples/You'll cover the important characteristics of lists and tuples in Python 3. You'll learn how to define them and how to manipulate them. When you're finished, you should have a good feel for when and how to use these object types in a Python program -------------------------------------------------------------------------------- /tests/test_feed.py: -------------------------------------------------------------------------------- 1 | """Tests for the reader.feed module.""" 2 | # Standard library imports 3 | import pathlib 4 | 5 | # Third party imports 6 | import pytest 7 | 8 | # Reader imports 9 | from reader import feed 10 | 11 | # Current directory 12 | HERE = pathlib.Path(__file__).resolve().parent 13 | 14 | 15 | @pytest.fixture 16 | def local_feed(): 17 | """Use local file instead of downloading feed from web.""" 18 | return HERE / "realpython_20180919.xml" 19 | 20 | 21 | @pytest.fixture 22 | def local_summary_feed(): 23 | """Use local file instead of downloading feed from web.""" 24 | return HERE / "realpython_descriptions_20180919.xml" 25 | 26 | 27 | # 28 | # Tests 29 | # 30 | def test_site(local_feed): 31 | """Test that we can read the site title and link.""" 32 | expected = "Real Python (https://realpython.com/)" 33 | assert feed.get_site(url=local_feed) == expected 34 | 35 | 36 | def test_article_title(local_feed): 37 | """Test that title is added at top of article.""" 38 | article_id = 0 39 | title = feed.get_titles(url=local_feed)[article_id] 40 | article = feed.get_article(article_id, url=local_feed) 41 | 42 | assert article.strip("# ").startswith(title) 43 | 44 | 45 | def test_article(local_feed): 46 | """Test that article is returned.""" 47 | article_id = 2 48 | article_phrases = [ 49 | "logging.info('This is an info message')", 50 | "By using the `level` parameter", 51 | " * `level`: The root logger", 52 | ] 53 | article = feed.get_article(article_id, url=local_feed) 54 | 55 | for phrase in article_phrases: 56 | assert phrase in article 57 | 58 | 59 | def test_titles(local_feed): 60 | """Test that titles are found.""" 61 | titles = feed.get_titles(url=local_feed) 62 | 63 | assert len(titles) == 20 64 | assert titles[0] == "Absolute vs Relative Imports in Python" 65 | assert titles[9] == "Primer on Python Decorators" 66 | 67 | 68 | def test_summary(local_summary_feed): 69 | """Test that summary feeds can be read.""" 70 | article_id = 1 71 | summary_phrases = [ 72 | "Get the inside scoop", 73 | "this list of\ninformative videos", 74 | ] 75 | summary = feed.get_article(article_id, url=local_summary_feed) 76 | 77 | for phrase in summary_phrases: 78 | assert phrase in summary 79 | 80 | 81 | def test_invalid_article_id(local_feed): 82 | """Test that invalid article ids are handled gracefully.""" 83 | article_id = "wrong" 84 | with pytest.raises(SystemExit): 85 | feed.get_article(article_id, url=local_feed) 86 | -------------------------------------------------------------------------------- /tests/test_viewer.py: -------------------------------------------------------------------------------- 1 | """Tests for the reader.viewer module.""" 2 | 3 | # Reader imports 4 | from reader import viewer 5 | 6 | 7 | # 8 | # Tests 9 | # 10 | def test_show(capsys): 11 | """Test that show adds information to stdout.""" 12 | text = "Lorem ipsum dolor sit amet" 13 | viewer.show(text) 14 | stdout, stderr = capsys.readouterr() 15 | assert stderr == "" 16 | 17 | # It's ok if the viewer adds some information 18 | assert text in stdout 19 | 20 | 21 | def test_show_list(capsys): 22 | """Test that show_list shows a list of items with an ID.""" 23 | site = "Real Python" 24 | things = ["pathlib", "data classes", "python 3.7", "decorators"] 25 | viewer.show_list(site, things) 26 | stdout, stderr = capsys.readouterr() 27 | assert stderr == "" 28 | 29 | # Site name is shown in header 30 | lines = stdout.split("\n") 31 | assert site in lines[0] 32 | 33 | # Each thing is listed preceded by a number 34 | for thing, line in zip(things, lines[1:]): 35 | line_parts = line.split() 36 | assert line_parts[0].isnumeric() 37 | assert thing in line 38 | --------------------------------------------------------------------------------