Schedule a NASA Astronomy Picture of the Day SMS with Python, Django, and Twilio

September 06, 2022
Written by
Reshma Sathe
Contributor
Opinions expressed by Twilio contributors are their own
Reviewed by
Mia Adjei
Twilion

Schedule a NASA Astronomy Picture of the Day SMS with Python, Django, and Twilio

Astronomy has always been a fascinating subject and has always piqued my interest. I found the NASA Astronomy Picture of the Day and their API during my search. That's when I thought of creating an application that would send me an SMS with the latest picture of the day from the NASA APOD API.

Instead of reinventing the wheel, I used Twilio SMS, Django, Celery, and the NASA APOD API to create my application. The goal was to:

  • Get a response (astronomy picture of the day and its related information from the NASA APOD API,
  • Create and send an SMS to selected phone numbers using Twilio, and
  • Schedule the SMS to be sent every day at 10 am local time.

 The complete code is available in this repository.

Prerequisites

  • Python 3.6x or 3.7x. If your operating system does not provide a Python interpreter, you can download an installer from the python.org site.
  • NASA API Key. You will need an API Key to use theNASA Astronomy Picture of the Day API. It's free and includes 1000 requests per minute.
  • The Python installation manager, known as pip
  • Django installed on your machine.
  • A Twilio account. Your Account SID and Auth Token are found in your Twilio account dashboard as shown below:

Twilio Console, showing where to find Account SID and Auth Token

You can create a NASA APOD API Key on the NASA Open APIs page by filling in the form:

Signup form to generate a NASA APOD API Key

Create the Django project and the Python virtual environment

Create a new Django project from your command prompt called nasa_apod_sms_project, then navigate to this new directory:

django-admin startproject nasa_apod_sms_project
cd nasa_apod_sms_project

 The folder structure created by Django looks similar to this:

Django folder structure

Next, create a new virtual environment for this project so that the dependencies for the project don't interfere with the global setup on your computer. To create a new environment called env, run the following commands on a Mac/Linux machine:

python -m venv env 
source env/bin/activate

On a Windows machine, run the commands:

python -m venv env
env\Scripts\activate

After you source the virtual environment, you'll see that your command prompt's input line begins with the name of the environment (env). Python has created a new folder called env/ in the nasa_apod_sms_project directory, which you can see by running the ls command in your command prompt.

Create a file called .gitignore in the nasa_apod_sms_project/ directory as well.

(env) $ touch .gitignore

Open the .gitignore file in the text editor of your choice — then add the env/ folder to the contents of the .gitignore file:

env/

Note that the env/ folder created by Python for the virtual environment is not the same as the .env file created to store secrets like API keys and environment variables.

Create the .env file

You will need data like the Account SID, Auth Token, NASA APOD key, and Twilio numbers to interact with the Twilio API and the NASA APOD API. These environment variables should be kept private, so you should not put their values in the code. Instead, you can store them in a .env file and list the .env file in your .gitignore file, so git doesn't track it. A .env file can be used whenever there are environment variables you need to make available to your operating system.

First, create the .env file:

(env) $ touch .env

Then, add the .env file as a line item in the .gitignore file:

env/
.env  # Add this

Next, open the .env file in your favorite text editor and add the following lines, replacing the random string placeholder values with your values:

TWILIO_ACCOUNT_SID = <YOUR TWILIO ACCOUNT SID>
TWILIO_TOKEN = <YOUR TWILIO ACCOUNT TOKEN>
NASA_APOD_API_KEY= <YOUR NASA APOD API KEY>
TWILIO_NUMBER = <YOUR TWILIO NUMBER>
RECEIVER_NUMBER = <NUMBER YOU WANT TO SEND SMS TO >

For the phone numbers above, make sure you have entered them in E.164 format.

At this point, the project folder is as follows:

Text editor, showing the folder structure of the code so far

Install the Python dependencies

The Python packages required for the project are:

 Dependencies needed for Python projects are typically listed in a file called requirements.txt. Create a requirements.txt file in the nasa_apod_sms_project directory:

(env) $ touch requirements.txt

Copy and paste this list of Python packages into your requirements.txt file using your preferred text editor:

python-dotenv
Django==3.0.8
celery==5.2.6
django_celery_beat
redis==4.3.1
django-celery-results==2.3.1
ipython==8.4.0
flower==0.9.3
twilio

The versions mentioned are what worked best for me on a Windows machine but may need to be adjusted to suit your machine and operating system.

Install all dependencies with the command below, ensuring you still have your virtual environment (env) sourced.

(env) $ pip install -r requirements.txt

Log statements printed as dependencies are installed

Set up NASA API GET request

Ok, all the setup necessary is done. Now let's get some data from the NASA APOD API.

Create a new file called sms_sender.py in the nasa_apod_sms_project/nasa_apod_sms_project directory—open sms_sender.py in your text editor.

The first thing you need to call the NASA APOD API is the API key you have stored in the .env file.

To retrieve this value, you need to load the .env file as follows — add this code to sms_sender.py:

from dotenv import load_dotenv
load_dotenv()
import os

The load_dotenv() makes all the variables from the .env file available to the Python code. You can then access the key as:

os.getenv("NASA_APOD_API_KEY")

The date will be set to the New York Timezone using the pytz module. To make the API call, you will use the requests library and the date processed with the datetime library. So, ensure that these are imported in sms_sender.py as well:

import requests
import pytz
import datetime
from datetime import datetime

Now we can call the API as follows — add this get_message_object() function just below the import statements:

def get_message_object():
    my_timezone = pytz.timezone('America/New_York')
    nasa_api_key =  os.getenv("NASA_APOD_API_KEY")

    current = datetime.now(tz=my_timezone).strftime('%Y-%m-%d')
    response = requests.get(f'https://api.nasa.gov/planetary/apod?api_key={nasa_api_key}&date={current}')
    return response.json()

Create and send the Twilio SMS

Now that you have the NASA APOD API response let's put it together and send an SMS to your registered phone number.

To send an SMS using Twilio, you need the following information, which you get from the .env file.

  1. The Twilio Account SID,
  2. Twilio Auth Token,
  3. The Twilio number assigned to your account, and
  4. The receiver's number (registered in case your account is a Trial account).

Below is how you can access these variables in your code, as well as how to instantiate the Twilio Client:

account_sid = os.getenv("TWILIO_ACCOUNT_SID")
auth_token = os.getenv("TWILIO_TOKEN")
my_twilio_number = os.getenv("TWILIO_NUMBER")
receiver_number = os.getenv("RECEIVER_NUMBER")
client = Client(account_sid, auth_token)

Now, in your code, just below your other import statements, add the following import that will let you use the Twilio Client:

import requests
import pytz
import datetime
from datetime import datetime
from twilio.rest import Client

In the following code snippet, you send the title, the image description, and a URL to the image (HD) in the message. You get the NASA APOD API response from the get_message_object() method, as explained previously, and send the message as follows:

def send_sms(*args, **kwargs):
    account_sid = os.getenv("TWILIO_ACCOUNT_SID")
    auth_token = os.getenv("TWILIO_TOKEN")
    my_twilio_number = os.getenv("TWILIO_NUMBER")
    receiver_number = os.getenv("RECEIVER_NUMBER")

    client = Client(account_sid, auth_token)
    sms = get_message_object()
    title = sms['title']
    picture = sms['hdurl']
    explanation = sms['explanation']
    message = client.messages.create(
            body=f'Today\'s picture is of: {title}! \n\n Description of Photo:{explanation}',
            from_=my_twilio_number,
            media_url=[f'{picture}'],
            to=receiver_number,
    )
    print(message.sid)

 Add this code to nasa_apod_sms_project/sms_sender.py.

Configure Celery

In this project, you will use the open-source task queue called Celery to send your SMS messages daily. The latest versions of Celery are supported out-of-the-box with Django; hence, you need minimal configuration. You will use the Celery beat, a task scheduler, and the crontab schedule to schedule the messages. Redis will be your Broker. Add the following Celery configuration to the nasa_apod_sms_project/settings.py file:

BROKER_URL = 'redis://localhost:9000/0'
CELERY_RESULT_BACKEND = 'django-db'
CELERY_ACCEPT_CONTENT = ['application/json']
CELERY_TASK_SERIALIZER = 'json'
CELERY_RESULT_SERIALIZER = 'json'
CELERY_TIMEZONE = 'Asia/Kolkata'
CELERY_IMPORTS = ['nasa_apod_sms_project.tasks']
CELERY_RESULT_CACHE = 'django-cache'

While in this tutorial, the CELERY_TIMEZONE is set to 'Asia/Kolkata'; feel free to change this to your timezone if you like.

In the same file, you will also need to add the Celery beat and Celery results to the installed apps list to ensure you can use them to schedule and store task results, respectively:

INSTALLED_APPS = [
    …
    'django_celery_beat',
    'django_celery_results',
]

Next, you will create and schedule a Celery task.

Initialize Celery

To use Celery, you need to define an instance. As per the recommendation in the Celery-Django documentation, create a new celery.py file in the nasa_apod_sms_project directory. Open this new file in your text editor, and create the Celery instance as follows:

import os
from celery import Celery

# Set the default Django settings module for the 'celery' program.
os.environ.setdefault('DJANGO_SETTINGS_MODULE','nasa_apod_sms_project.settings')
app = Celery('nasa_apod_sms_project',include=['nasa_apod_sms_project.tasks'])

# Using a string here means the worker doesn't have to serialize
# the configuration object to child processes.
# - namespace='CELERY' means all celery-related configuration keys
#   should have a `CELERY_` prefix.
app.config_from_object('django.conf:settings')

# Load task modules from all registered Django apps.
app.autodiscover_tasks()
@app.task(bind=True)
def debug_task(self):
    print('Request: {0!r}'.format(self.request))

Also, put an entry in the nasa_apod_sms_project/__init__.py file so that the application is loaded every time Django starts up. Open this file and paste in the following code:

# This will make sure the app is always imported when
# Django starts so that shared_task will use this app.
from .celery import app as celery_app

__all__ = ['celery_app',]

Return to the celery.py file. In this example, you will schedule the celery task at 10 am every day, Kolkata time, or your own timezone if you previously changed it. The timezone is mentioned in the CELERY_TIMEZONE option in settings.py.

Add crontab to the imports in celery.py, and then create the following schedule:

import os
from celery import Celery
from celery.schedules import crontab

…

app.conf.beat_schedule = {
    'everyday-10.00am':{
        'task': 'nasa_apod_sms_project.tasks.send_sms_from_twilio_number_task',
        'schedule': crontab(minute='00',hour='10'),
    }
}

Now, to create the actual "task" that is run by Celery, create a new file called tasks.py in your nasa_apod_sms_project project directory and enter the following code:

from celery.utils.log import get_task_logger
from celery import shared_task 
from .sms_sender import send_sms

logger = get_task_logger(__name__)

@shared_task
def send_sms_from_twilio_number_task():
   logger.info(f" Sent the SMS")
    return send_sms()

You now have all the pieces of the puzzle ready.

Run Server

To send an SMS with the NASA APOD photo of the day to the receiver's phone via Twilio every day at 10 am, you need to run the Django application, the Redis server, the Celery worker, and the Celery beat scheduler. You will need to run each of these services in a new terminal window, making sure your virtual environment is activated in each window.

You can run the Django application with the following command:

python manage.py runserver

Then, open a new terminal window to the project directory, activate your virtual environment, and run the Redis server with the following command:

redis-server --port 9000

Open a new Terminal window and start the Celery worker inside the project directory.

celery -A nasa_apod_sms_project worker -l info

For the Windows operating system, you might need to run the worker with the 'solo' option due to some known issues between Celery and Windows:

celery -A nasa_apod_sms_project worker -l info -P solo

You will see logs like the following as the service starts up:

 

Log statements from Celery worker

Open another Terminal window and start the task scheduler:

celery -A nasa_apod_sms_project beat -l info

Log statements from Celery beat

The Celery scheduler will trigger the task mentioned in the beat_schedule, and you should see a log statement similar to the one below in the scheduler logs:

Log statements describing the scheduled task

The Celery worker is what picks and executes the task at the stipulated time. If everything is configured correctly, you should be able to see a log similar to the one below when the task is executed.

Log statements once task is executed

  The receiver's number will receive an SMS from a Twilio number. If your account is a trial account, then the message will start with:

Sent from your Twilio trial account-

Following that will be the NASA Picture of the day's title, description, and a link to the photo. Here is a sample SMS:

Sample SMS containing information about the NASA photo of the day

We can also check the logs of whether the SMS was sent or not, what the details were, etc., in the Twilio console.  

Twilio Console SMS logs

If you click on the specific link, the detailed logs are shown.

Message details

Message details, containing the attached media and message delivery steps

Conclusion

Way to go! You just learned how to:

  • Get a response from the NASA APOD API
  • Write a function to send an SMS message using the Twilio API
  • Send messages periodically using Celery and Django

If you're interested in building more astronomy-related projects, you can try other NASA APIs. Or you can call a Weather API, tweak the SMS content, and turn it into a daily series of weather for your locality. The above application is a beginner Django project with minimal configuration. For this application to be deploy-ready, consider changes like containerizing the application, adding admin functionality, building a model to store the SMS objects, etc.

Or, check out some of the other tutorials on the Twilio blog for ideas on what to build next:

Happy Coding!

 

 

Reshma Sathe is a Master of Computer Science graduate and a Software Developer with experience in Java, Python, Kotlin, and other programming languages. She loves exploring newer technologies. She can be reached via: