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/.github/dependabot.yaml b/.github/dependabot.yaml new file mode 100644 index 0000000..b783175 --- /dev/null +++ b/.github/dependabot.yaml @@ -0,0 +1,13 @@ +--- +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..2f647cc --- /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.13' + 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..512e334 --- /dev/null +++ b/.github/workflows/publish.yaml @@ -0,0 +1,42 @@ +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.13' + + - 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: Test publish package + uses: pypa/gh-action-pypi-publish@release/v1 + with: + user: __token__ + password: ${{ secrets.TESTPYPI_API_TOKEN }} + repository-url: https://test.pypi.org/legacy/ + + - 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..2f6ec97 --- /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.9", "3.10", "3.11", "3.12", "3.13"] + + 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/MANIFEST.in b/MANIFEST.in index 8d401be..83ce77d 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1 +1 @@ -include reader/*.cfg +include src/reader/*.toml diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..b2be1ab --- /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.1.4" +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.9" + + [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.1.4" +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 \ No newline at end of file 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 deleted file mode 100644 index d70f475..0000000 --- a/setup.py +++ /dev/null @@ -1,35 +0,0 @@ -"""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"]}, -) diff --git a/reader/__init__.py b/src/reader/__init__.py similarity index 53% rename from reader/__init__.py rename to src/reader/__init__.py index fa1933a..383f287 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 -# Version of realpython-reader package -__version__ = "1.0.0" +try: + import tomllib +except ModuleNotFoundError: + # Third party imports + import tomli as tomllib + -# Read URL of feed from config file -cfg = ConfigParser() -with resources.path("reader", "config.cfg") as path: - cfg.read(str(path)) +# Version of realpython-reader package +__version__ = "1.1.4" -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 98% rename from reader/__main__.py rename to src/reader/__main__.py index 45a8925..56e5fa7 100644 --- a/reader/__main__.py +++ b/src/reader/__main__.py @@ -40,7 +40,7 @@ Version: -------- -- realpython-reader v1.0.0 +- realpython-reader v1.1.4 """ # Standard library imports import sys 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 69% rename from reader/feed.py rename to src/reader/feed.py index 5dd0680..16ad7be 100644 --- a/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 @@ -20,9 +20,18 @@ def _feed(url: str = URL) -> feedparser.FeedParserDict: def get_site(url: str = URL) -> str: - """Get name and link to web site of the feed.""" - info = _feed(url).feed - return f"{info.title} ({info.link})" + """Get name and link to website of the feed.""" + 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: 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