Creating an aiohttp Application to Fetch and Validate NASA Mars Photos
This tutorial demonstrates how to build a simple aiohttp web server in Python, install required dependencies, retrieve random Mars photos from the NASA API, validate image size and color mode using Pillow, and serve the selected image directly to the browser.
We start by creating a new Python virtual environment (Python 3.5+ recommended) and installing aiohttp with pip install aiohttp . A basic nasa.py file is created containing an asynchronous request handler that returns a static text response.
The handler get_mars_photo is registered on the root path using app.router.add_get('/', get_mars_photo, name='mars_photo') . The application is run with web.run_app(app, host='127.0.0.1', port=8080) or via aiohttp-devtools for live reload.
To obtain real images we call the NASA Mars Rover API at https://api.nasa.gov/mars-photos/api/v1/rovers/curiosity/photos with required parameters sol (Martian day) and api_key . The following coroutine selects a random sol, fetches the JSON response, and returns a random img_src URL:
<code>import random
from aiohttp import web, ClientSession
NASA_API_KEY = 'DEMO_KEY'
ROVER_URL = 'https://api.nasa.gov/mars-photos/api/v1/rovers/curiosity/photos'
async def get_mars_image_url_from_nasa():
while True:
sol = random.randint(0, 1722)
params = {'sol': sol, 'api_key': NASA_API_KEY}
async with ClientSession() as session:
async with session.get(ROVER_URL, params=params) as resp:
resp_dict = await resp.json()
if 'photos' not in resp_dict:
raise Exception('No photos key')
photos = resp_dict['photos']
if not photos:
continue
return random.choice(photos)['img_src']
</code>To ensure the image is suitable we validate its dimensions (≥1024 px) and that it is not grayscale. Pillow is installed with pip install pillow . The validation coroutine looks like:
<code>import io
from PIL import Image
async def validate_image(image_bytes):
image = Image.open(io.BytesIO(image_bytes))
return image.width >= 1024 and image.height >= 1024 and image.mode != 'L'
</code>Another coroutine repeatedly fetches image bytes until a valid image is found:
<code>async def get_mars_photo_bytes():
while True:
image_url = await get_mars_image_url_from_nasa()
async with ClientSession() as session:
async with session.get(image_url) as resp:
image_bytes = await resp.read()
if await validate_image(image_bytes):
break
return image_bytes
</code>The final request handler returns the image directly:
<code>async def get_mars_photo(request):
image = await get_mars_photo_bytes()
return web.Response(body=image, content_type='image/jpeg')
</code>The complete application assembles the validation, image‑fetching, and routing logic, then starts the aiohttp server:
<code>import random
import io
from aiohttp import web, ClientSession
from PIL import Image
NASA_API_KEY = 'DEMO_KEY'
ROVER_URL = 'https://api.nasa.gov/mars-photos/api/v1/rovers/curiosity/photos'
# (validate_image, get_mars_image_url_from_nasa, get_mars_photo_bytes, get_mars_photo definitions as above)
app = web.Application()
app.router.add_get('/', get_mars_photo, name='mars_photo')
</code>Running this script displays a random high‑resolution Mars photo in the browser, and the code can be extended with caching, dynamic sol selection, or additional filters.
Python Programming Learning Circle
A global community of Chinese Python developers offering technical articles, columns, original video tutorials, and problem sets. Topics include web full‑stack development, web scraping, data analysis, natural language processing, image processing, machine learning, automated testing, DevOps automation, and big data.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.