In this modern era of on-demand content, podcasts have become a popular medium for storytelling, education, and entertainment.

To make it easier for users to discover and explore the vast world of podcasts, we will build a web application using Podcastindex.org excellent database.

This article will guide you through creating a podcast search web application using FastAPI, with Jinja2 templating for the UI and the Podcast Index API.

Example of the running web application:

0:00
/0:00

Example of the running web application

If you prefer, you can follow along with the video version of this post:


Overview

This web application allows users to search for podcasts and view episodes of selected podcasts. The application integrates with the Podcast Index API to fetch podcast data and uses FastAPI to manage routes and render dynamic web pages.

This project consists of several key components:

  • Environment configuration using .env files.
  • The main application script (main.py).
  • A wrapper for the Podcast Index API (podcastindex_wrapper.py).
  • HTML templates for rendering web pages.

Environment Configuration

To keep sensitive information secure, we use a .env file to store environment variables such as API keys.

This file is loaded at runtime, ensuring that the credentials are not hardcoded into the application:

# PODCASTINDEX API
PODCAST_INDEX_API_KEY=<YOUR_PODCAST_INDEX_API_KEY>
PODCAST_INDEX_API_SECRET=<YOUR_PODCAST_INDEX_API_KEY>

You will need to obtain a free API key from Podcastindex.org by signing up here.


Are you tired of writing the same old Python code? Want to take your programming skills to the next level? Look no further! This book is the ultimate resource for beginners and experienced Python developers alike.

Get "Python's Magic Methods - Beyond __init__ and __str__"

Magic methods are not just syntactic sugar, they're powerful tools that can significantly improve the functionality and performance of your code. With this book, you'll learn how to use these tools correctly and unlock the full potential of Python.

Core Components

Let's now take a closer look at the core components of this application.

The Main Application Script

The main.py file initializes the FastAPI app and defines the routes for handling incoming requests. Here is the code from main.py:

from fastapi import FastAPI, Request
from fastapi.templating import Jinja2Templates
from podcastindex_wrapper import search_podcasts, get_podcast_episodes
from datetime import datetime


app = FastAPI()

templates = Jinja2Templates(directory="templates")


@app.on_event("startup")
def startup_event():
    templates.env.filters['timestamp_to_date'] = lambda timestamp: datetime.fromtimestamp(timestamp).strftime('%Y-%m-%d')


@app.get("/")
async def read_root(request: Request, q: str = ''):
    results = []
    if q:
        results = search_podcasts(q)
    return templates.TemplateResponse("index.html", {"request": request, "results": results, "query": q})


@app.get("/podcast/{podcast_id}/{q}")
async def podcast_episodes(request: Request, podcast_id: int, q: str = None):
    episodes = get_podcast_episodes(podcast_id)
    return templates.TemplateResponse("episodes.html", {"request": request, "episodes": episodes,
                                                        "query": q})


if __name__ == "__main__":
    import uvicorn

    uvicorn.run(app, host="0.0.0.0", port=8000)

This code defines a web application using the FastAPI framework in Python. The application uses Jinja2 for templating and the podcastindex_wrapper module (that we will see next) to search for podcasts and retrieve their episodes.

The startup_event function is registered to run when the application starts up, and it adds a custom filter to the Jinja2 environment that converts a timestamp to a formatted date string.

The read_root function is a route handler for the root URL of the application (/). It takes an optional query parameter q, searches for podcasts using the search_podcasts function from podcastindex_wrapper if q is provided, and renders the index.html template with the search results and query.

The podcast_episodes function is a route handler for URLs in the format /podcast/{podcast_id}/{q}, where podcast_id is the ID of a podcast and q is an optional query parameter. It retrieves the episodes for the specified podcast using the get_podcast_episodes function from podcastindex_wrapper, and renders the episodes.html template with the episodes and query.

Finally, the code checks if the script is being run as the main module (__name__ == "__main__"), and if so, start the FastAPI application using the uvicorn server, listening on the host 0.0.0.0 and port 8000.


The Podcast Index API Wrapper

This script abstracts the interaction with the Podcast Index API, providing simple functions to search for podcasts and fetch podcast episodes.

Here’s the code from podcastindex_wrapper.py:

import podcastindex
from decouple import config


# Load your Podcast Index API credentials from environment variables
PODCAST_INDEX_API_KEY = config("PODCAST_INDEX_API_KEY")
PODCAST_INDEX_API_SECRET = config("PODCAST_INDEX_API_SECRET")

config_index = {
    "api_key": PODCAST_INDEX_API_KEY,
    "api_secret": PODCAST_INDEX_API_SECRET
}

index = podcastindex.init(config_index)


def search_podcasts(query: str):
    result = index.search(query, clean=True)
    return result["feeds"]


def get_podcast_episodes(podcast_id: int):
    result = index.episodesByFeedId(podcast_id)
    return result["items"]

This code defines two functions, search_podcasts and get_podcast_episodes, that use the podcastindex library to interact with the Podcast Index API.

The API credentials are loaded from environment variables using the decouple library, and passed to the podcastindex.init function to initialize an instance of the podcastindex client.

The search_podcasts function takes a query string as input, and calls the index.search method of the podcastindex client to search for podcasts matching the query. It returns the "feeds" field of the search result, which contains a list of matching podcasts.

The get_podcast_episodes function takes a podcast ID as input, and calls the index.episodesByFeedId method of the podcastindex client to retrieve a list of episodes for the specified podcast. It returns the "items" field of the result, which contains a list of episode objects.


Dependencies

The requirements.txt file lists the dependencies needed for the application. The dependencies include:

  • fastapi: For building the web application.
  • uvicorn: For running the ASGI server.
  • jinja2: For templating rendering
  • python-decouple: For managing environment variables.
  • podcastindex-python: The Python client for the Podcast Index API.

Don't forget to create the file or install the dependencies.


HTML Templates

The templates directory will contain the Jinja2 templates that define the structure and layout of the web pages. These templates enable dynamic content rendering, making the application responsive to user inputs and API responses:

  • base.html: The base HTML template that will be used by the other templates
  • index.html: The main template for displaying search results.
  • episodes.html: The template for displaying podcast episodes.

Tagged in: