Implementing py-executable workflow using ChatGPT
Teaching ChatGPT How to write workflows using py-executable-checklist framework.
You (🗣️): I’m writing a Python application which defines a workflow as a series of steps defined by a class for each step.
Here is what the definition of a whole workflow looks like
def workflow():
return [
DoSomething,
]
DoSomething is a step in the workflow and is defined as
class DoSomething(WorkflowBase):
"""
Go to this page
Copy the command
Run the command
Copy the output and paste it into the email
"""
username: str
def execute(self):
logging.info(f"Hello {self.username}")
# output
return {"greetings": f"Hello {self.username}"}
Where username is the input to this workflow and execute method implements the workflow. Each step can return some state by returning a dictionary of key value pairs.
Based on the above description, can you write a workflow for the following scenario
- Gather JSON data from
r/listentothis
subreddit, specifically the top songs from the past month - Parse that JSON data such that I have an array of URLs
- Creates a file based on the name of the month in the local directory
- For each URL, the file is downloaded in ALAC format (best iTunes quality possible) and added to said folder
- When the folder contains 20 songs, the top photo from
r/earthporn
subreddit is grabbed from the reddit API - Text (title, month, year) is added to the photo in a random color
- For each song, metadata including album name and album art is added
- The entire monthly folder is added to the “automatically add to itunes” folder
ChatGPT (🤖): Here is an example of how you could define the workflow for the scenario you described using the WorkflowBase class:
import logging
import requests
import json
import os
from PIL import Image, ImageDraw, ImageFont
class GatherJSONData(WorkflowBase):
"""
Gather JSON data from the r/listentothis subreddit for the top songs of the past month.
"""
def execute(self):
# Send a GET request to the r/listentothis subreddit API
response = requests.get("https://www.reddit.com/r/listentothis/top.json", params={"t": "month"})
# Parse the JSON data from the response
data = json.loads(response.text)
# Extract the list of URLs from the data
urls = [item["data"]["url"] for item in data["data"]["children"]]
# Return the list of URLs as output
return {"urls": urls}
class CreateMonthlyFolder(WorkflowBase):
"""
Create a file based on the name of the month in the local directory.
"""
def execute(self):
# Get the current month and year
current_month = datetime.datetime.now().strftime("%B")
current_year = datetime.datetime.now().year
# Create the folder for the current month in the local directory
os.makedirs(f"{current_month}_{current_year}", exist_ok=True)
# Return the name of the created folder as output
return {"folder_name": f"{current_month}_{current_year}"}
class DownloadSongs(WorkflowBase):
"""
For each URL, download the song in ALAC format and add it to the specified folder.
"""
folder_name: str
urls: List[str]
def execute(self):
# Iterate over the list of URLs
for url in self.urls:
# Download the song in ALAC format and save it to the specified folder
download_song(url, self.folder_name, "ALAC")
# Return the folder name as output
return {"folder_name": self.folder_name}
class GrabTopPhoto(WorkflowBase):
"""
Grab the top photo from the r/earthporn subreddit from the reddit API.
"""
def execute(self):
# Send a GET request to the r/earthporn subreddit API
response = requests.get("https://www.reddit.com/r/earthporn/top.json", params={"t": "day"})
# Parse the JSON data from the response
data = json.loads(response.text)
# Extract the URL of the top photo from the data
photo_url = data["data"]["children"][0]["data"]["url"]
# Download the photo
response = requests.get(photo_url)
photo_data = response.content
# Return the photo data as output
return {"photo_data": photo_data}
class AddTextToPhoto(WorkflowBase):
"""
Add text (title, month, year) to the photo in a random
You (🗣️): Can you implement the following step?
- Text (title, month, year) is added to the photo in a random color
ChatGPT (🤖): Sure! Here is an example of how you could implement the step to add text (title, month, year) to a photo in a random color using the Python Pillow library:
import random
from PIL import Image, ImageDraw, ImageFont
class AddTextToPhoto(WorkflowBase):
"""
Add text (title, month, year) to the photo in a random color.
"""
photo_data: bytes
title: str
month: str
year: str
def execute(self):
# Open the photo data as an image
image = Image.open(io.BytesIO(self.photo_data))
# Create an ImageDraw object to draw on the image
draw = ImageDraw.Draw(image)
# Choose a random color for the text
color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
# Set the font and font size for the text
font = ImageFont.truetype("arial.ttf", 20)
# Get the width and height of the image
width, height = image.size
# Calculate the x and y positions for the text
x = 10
y = 10
# Add the title, month, and year to the image
draw.text((x, y), self.title, fill=color, font=font)
draw.text((x, y + 30), self.month, fill=color, font=font)
draw.text((x, y + 60), self.year, fill=color, font=font)
# Save the modified image to a temporary file
with tempfile.NamedTemporaryFile() as temp_file:
image.save(temp_file, "JPEG")
# Read the modified image data from the temporary file
modified_photo_data = temp_file.read()
# Return the modified photo data as output
return {"modified_photo_data": modified_photo_data}
This step assumes that you have an Arial font file named “arial.ttf” in the same directory as your Python script. You can modify the font and font size to your liking. The text is added to the image at the top left corner (x = 10, y = 10) with a vertical spacing of 30 pixels between each line of text. The random color is generated using the random.randint()
function. The modified image is saved to a temporary file and the modified image data is read from the file and returned as output.
You (🗣️): Can you follow on and implement this step?
- For each song, metadata including album name and album art is added
ChatGPT (🤖): Sure! Here is an example of how you could implement a step to add metadata including album name and album art to each song in a folder using the Python Mutagen library:
import mutagen
class AddMetadataToSongs(WorkflowBase):
"""
For each song in the specified folder, add metadata including album name and album art.
"""
folder_name: str
album_name: str
album_art: bytes
def execute(self):
# Iterate over the files in the specified folder
for file_name in os.listdir(self.folder_name):
# Skip non-audio files
if not file_name.endswith(".mp3"):
continue
# Load the audio file using Mutagen
audio_file = mutagen.File(os.path.join(self.folder_name, file_name))
# Set the album name and album art in the metadata
audio_file["album"] = self.album_name
audio_file.tags.add(mutagen.id3.APIC(3, "image/jpeg", 3, "Front cover", self.album_art))
# Save the modified metadata to the audio file
audio_file.save()
This step assumes that you have installed the Mutagen library (pip install mutagen
) and that the folder specified in the folder_name
input contains only audio files with the “.mp3” file extension. The step iterates over the files in the folder, skips non-audio files, and uses Mutagen to load the audio file. It then sets the album
field in the metadata to the value of the album_name
input and adds the album_art
image data as the front cover to the metadata using the Mutagen APIC
tag. Finally, it saves the modified metadata to the audio file.
You (🗣️): Following on from above, can you implement this step?
- The entire monthly folder is added to the “automatically add to itunes” folder
ChatGPT (🤖): Sure! Here is an example of how you could implement a step to move the entire monthly folder to the “automatically add to itunes” folder on macOS using the Python shutil library:
import shutil
class AddFolderToiTunes(WorkflowBase):
"""
Add the specified folder to the "automatically add to itunes" folder.
"""
folder_name: str
def execute(self):
# Get the path to the "automatically add to itunes" folder
iTunes_folder = os.path.expanduser("~/Music/iTunes/iTunes Media/Automatically Add to iTunes")
# Move the specified folder to the "automatically add to itunes" folder
shutil.move(self.folder_name, iTunes_folder)
This step assumes that you are using macOS and that the “automatically add to itunes” folder is located at “~/Music/iTunes/iTunes Media/Automatically Add to iTunes” on your system. The step uses the shutil.move()
function to move the specified folder to the “automatically add to itunes” folder.
Note that this step will only work on macOS systems. If you are using a different operating system, you will need to use a different method to move the folder to the equivalent location on your system.
You (🗣️): Now can you collect all the above steps and generate the following function
def workflow(): return [ DoSomething, ]
ChatGPT (🤖): Sure! Here is an example of how you could define the workflow for the scenario you described using the steps that I provided above:
def workflow():
return [
GatherJSONData,
CreateMonthlyFolder,
DownloadSongs,
GrabTopPhoto,
AddTextToPhoto,
AddMetadataToSongs,
AddFolderToiTunes
]
This workflow will first gather the JSON data for the top songs of the past month from the r/listentothis subreddit, create a folder based on the current month and year in the local directory, download the songs in the ALAC format and add them to the folder, grab the top photo from the r/earthporn subreddit, add text (title, month, year) to the photo in a random color, add metadata including album name and album art to each song in the folder, and finally move the entire folder to the “automatically add to itunes” folder.
You can customize the workflow by modifying the input parameters of each step or by adding additional steps as needed.
You (🗣️): How can I use requests with a known user agent?
ChatGPT (🤖):
To use the requests
library with a known user agent, you can pass the headers
parameter to the get()
or post()
function and specify the user agent in the "User-Agent"
header.
For example, to send a GET request to a URL with the Chrome user agent, you could do the following:
import requests
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3"
}
response = requests.get("https://www.example.com", headers=headers)
This will send the request with the specified user agent string in the "User-Agent"
header. You can specify any user agent string that you want in this header.
Note that some websites may block or throttle requests with certain user agents, so it is important to use a user agent that is appropriate for the website that you are trying to access.
Credits
Original idea from https://github.com/purplefloyd14/reddit_music_scrape