Skip to content
Empty file added _ext/__init__.py
Empty file.
101 changes: 101 additions & 0 deletions _ext/rss.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
"""
Create an RSS feed of tutorials
Cribbed from: https://github.com/python/peps/blob/main/pep_sphinx_extensions/generate_rss.py
"""

from dataclasses import dataclass, asdict
from datetime import datetime, UTC
from email.utils import format_datetime
from html import escape
from pprint import pformat
from typing import TYPE_CHECKING
from urllib.parse import urljoin

if TYPE_CHECKING:
from sphinx.application import Sphinx


def _format_rfc_2822(dt: datetime) -> str:
datetime = dt.replace(tzinfo=UTC)
return format_datetime(datetime, usegmt=True)


@dataclass
class RSSItem:
title: str
date: datetime
description: str
url: str
author: str = "PyOpenSci"

@classmethod
def from_meta(cls, page_name: str, meta: dict, app: "Sphinx") -> "RSSItem":
"""Create from a page's metadata"""
url = urljoin(app.config.html_baseurl, app.builder.get_target_uri(page_name))
# purposely don't use `get` here because we want to error if these fields are absent
return RSSItem(
title=meta[":og:title"],
description=meta[":og:description"],
date=datetime.fromisoformat(meta["date"]),
author=meta.get(":og:author", "PyOpenSci"),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
author=meta.get(":og:author", "PyOpenSci"),
author=meta.get(":og:author", "pyOpenSci"),
url=url,
)

def render(self) -> str:
return f"""\
<item>
<title>{escape(self.title, quote=False)}</title>
<link>{escape(self.url, quote=False)}</link>
<description>{escape(self.description, quote=False)}</description>
<author>{escape(self.author, quote=False)}</author>
<guid isPermaLink="true">{self.url}</guid>
<pubDate>{_format_rfc_2822(self.date)}</pubDate>
</item>"""


@dataclass
class RSSFeed:
items: list[RSSItem]
last_build_date: datetime = datetime.now()
title: str = "PyOpenSci Tutorials"
link: str = "https://www.pyopensci.org/python-package-guide/tutorials/intro.html"
self_link: str = "https://www.pyopensci.org/python-package-guide/tutorials.rss"
description: str = "Tutorials for learning python i guess!!!"
language: str = "en"

def render(self) -> str:
items = sorted(self.items, key=lambda i: i.date, reverse=True)
items = "\n".join([item.render() for item in items])
return f"""\
<?xml version='1.0' encoding='UTF-8'?>
<rss xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" version="2.0">
<channel>
<title>{self.title}</title>
<link>{self.link}</link>
<atom:link href="{self.self_link}" rel="self"/>
<description>{self.description}</description>
<language>{self.language}</language>
<lastBuildDate>{_format_rfc_2822(self.last_build_date)}</lastBuildDate>
{items}
</channel>
</rss>
"""


def generate_tutorials_feed(app: "Sphinx"):
from sphinx.util import logging

logger = logging.getLogger("_ext.rss")
logger.info("Generating RSS feed for tutorials")
metadata = app.builder.env.metadata
tutorials = [t for t in metadata if t.startswith("tutorials/")]
feed_items = [RSSItem.from_meta(t, metadata[t], app) for t in tutorials]
feed = RSSFeed(items=feed_items)
with open(app.outdir / "tutorials.rss", "w") as f:
f.write(feed.render())

logger.info(
f"Generated RSS feed for tutorials, wrote to {app.outdir / 'tutorials.rss'}"
)
logger.debug(f"feed items: \n{pformat([asdict(item) for item in feed_items])}")
22 changes: 19 additions & 3 deletions conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,17 @@
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
# import os
# import sys
# sys.path.insert(0, os.path.abspath('.'))
import os
import sys
sys.path.insert(0, os.path.abspath('.'))
from datetime import datetime
import subprocess
import os
from typing import TYPE_CHECKING
from _ext import rss

if TYPE_CHECKING:
from sphinx.application import Sphinx

current_year = datetime.now().year
organization_name = "pyOpenSci"
Expand Down Expand Up @@ -199,3 +204,14 @@
bibtex_bibfiles = ["bibliography.bib"]
# myst complains about bibtex footnotes because of render order
suppress_warnings = ["myst.footnote"]


def _post_build(app: "Sphinx", exception: Exception | None) -> None:
rss.generate_tutorials_feed(app)


def setup(app: "Sphinx"):
app.connect("build-finished", _post_build)

# Parallel safety: https://www.sphinx-doc.org/en/master/extdev/index.html#extension-metadata
return {"parallel_read_safe": True, "parallel_write_safe": True}
6 changes: 6 additions & 0 deletions tutorials/add-license-coc.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
---
:og:description: Placeholder text!!!!
:og:title: Add a License and Code of Conduct to your python package
date: 1970-01-02
---

# Add a `LICENSE` & `CODE_OF_CONDUCT` to your Python package

In the [previous lesson](add-readme) you:
Expand Down
6 changes: 6 additions & 0 deletions tutorials/add-readme.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
---
:og:description: Placeholder text!!!!
:og:title: Add a README file to your Python package
date: 1970-01-03
---

# Add a README file to your Python package

In the previous lessons you learned:
Expand Down
6 changes: 6 additions & 0 deletions tutorials/command-line-reference.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
---
:og:description: Placeholder text!!!!
:og:title: Command Line Reference Guide
date: 1970-01-04
---

# Command Line Reference Guide

```{important}
Expand Down
6 changes: 6 additions & 0 deletions tutorials/get-to-know-hatch.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
---
:og:description: Placeholder text!!!!
:og:title: Get to Know Hatch
date: 1970-01-05
---

# Get to Know Hatch

Our Python packaging tutorials use the tool
Expand Down
1 change: 1 addition & 0 deletions tutorials/installable-code.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
:og:description: Learn how to make your Python code installable so you can use it across projects.
:og:title: Make your Python code installable so it can be used across projects
date: 1970-01-01
---

# Make your Python code installable
Expand Down
6 changes: 6 additions & 0 deletions tutorials/intro.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
---
:og:description: Placeholder text!!!!
:og:title: Python packaging 101
date: 1970-01-05
---

(packaging-101)=
# Python packaging 101

Expand Down
6 changes: 6 additions & 0 deletions tutorials/publish-conda-forge.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
---
:og:description: Placeholder text!!!!
:og:title: Publish your Python package that is on PyPI to conda-forge
date: 1970-01-06
---

# Publish your Python package that is on PyPI to conda-forge

In the previous lessons, you've learned:
Expand Down
6 changes: 6 additions & 0 deletions tutorials/publish-pypi.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
---
:og:description: Placeholder text!!!!
:og:title: Publish your Python package to PyPI
date: 1970-01-07
---

# Publish your Python package to PyPI

:::{todo}
Expand Down
6 changes: 6 additions & 0 deletions tutorials/pyproject-toml.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
---
:og:description: Placeholder text!!!!
:og:title: Make your Python package PyPI ready - pyproject.toml
date: 1970-01-08
---

# Make your Python package PyPI ready - pyproject.toml

In [the installable code lesson](installable-code), you learned how to add the bare minimum information to a `pyproject.toml` file to make it installable. You then learned how to [publish a bare minimum version of your package to PyPI](publish-pypi.md).
Expand Down
6 changes: 6 additions & 0 deletions tutorials/setup-py-to-pyproject-toml.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
---
:og:description: Placeholder text!!!!
:og:title: Using Hatch to Migrate setup.py to a pyproject.toml
date: 1970-01-09
---

# Using Hatch to Migrate setup.py to a pyproject.toml

Hatch can be particularly useful to generate your project's `pyproject.toml` if your project already has a `setup.py`.
Expand Down
Loading