Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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