Star Wars API Client Lab with Python 🚀¶
In this hands-on lab, you'll build a Python client for the Star Wars API (SWAPI) with the help of GitHub Copilot! This lab demonstrates how GitHub Copilot can assist with creating data classes, abstract base classes, implementations, and tests in Python.
Lab Overview 📋¶
Duration: 1 hour
Difficulty: Intermediate
Prerequisites:
- Basic knowledge of Python, REST APIs, and unit testing
- Python 3.8+: Download Python
- pip package manager (included with Python)
What You'll Build 🏗️¶
A Python client for the Star Wars API with the following components:
- Data Classes to represent Star Wars characters
- An abstract base class defining the API client methods
- An implementation of the abstract base class using Python's requests library
- Unit tests to verify the functionality
Getting Started 🚀¶
Step 1: Set Up the Project Structure¶
We'll start by setting up a basic Python project structure. GitHub Copilot will help us create the necessary files and configurations.
Copilot Tip
You can ask Github Copilot Chat to provide you with the steps to create a Python based project with a prompt like: How can I create a Python based project with unit tests?
Let's set up our project directory. Create an empty folder and navigate to it via terminal.
Create a virtual environment and activate it:
A sample project structure could look like this:
starwarsapi-python/
├── requirements.txt
├── src/
│ ├── app.py
│ ├── star_wars_api.py
│ ├── star_wars_api_impl.py
│ └── star_wars_character.py
└── tests/
└── test_star_wars_api.py
Step 2: Install Required Packages¶
Let's create a requirements.txt
file with the necessary dependencies.
Copilot Tip
Ask GitHub Copilot to help you create a requirements.txt file with dependencies for HTTP requests and unit testing.
Ensure that the dependency versions align with your project requirements, after using Copilot for creating the dependencies.
Your requirements.txt
could look something like this:
Install the dependencies:
Step 3: Create the Data Class for Star Wars Character¶
Info
This demonstration can be implemented through different methods. Originally, this demonstration is adapted from a Java version, that's why you may notice some Java-inspired structures. Feel free to experiment with different Python approaches and ask GitHub Copilot for suggestions or alternative implementations as you work through the lab.
Please navigate in your browser to the Star Wars API and get familiar with the API. We will create a data class to represent a Star Wars Character.
Let's start by using Luke Skywalker as an example: https://swapi.info/people/1
Please copy the JSON in your clipboard:
{
"name": "Luke Skywalker",
"height": "172",
"mass": "77",
"hair_color": "blond",
"skin_color": "fair",
"eye_color": "blue",
"birth_year": "19BBY",
"gender": "male",
"homeworld": "https://swapi.info/api/planets/1",
"films": [
"https://swapi.info/api/films/1",
"https://swapi.info/api/films/2",
"https://swapi.info/api/films/3",
"https://swapi.info/api/films/6"
],
"species": [],
"vehicles": [
"https://swapi.info/api/vehicles/14",
"https://swapi.info/api/vehicles/30"
],
"starships": [
"https://swapi.info/api/starships/12",
"https://swapi.info/api/starships/22"
],
"created": "2014-12-09T13:50:51.644000Z",
"edited": "2014-12-20T21:17:56.891000Z",
"url": "https://swapi.info/api/people/1"
}
Now, let's create a data class to represent a Star Wars character.
Copilot Tip
Create a star_wars_character.py
file and use Github Copilot Ask/Edit mode to create a data class for the Star Wars Character. You can use the JSON payload above as an example payload within the prompt.
Remember that you can directly create files out of Copilot chat by clicking on the three dots icon and then clicking Insert into New File
, if you are using the Ask mode:
Below is a sample implementation for star_wars_character.py
.
Sample Solution
from dataclasses import dataclass
from typing import List
@dataclass
class StarWarsCharacter:
"""Data class for Star Wars character from SWAPI"""
name: str
height: str
mass: str
hair_color: str
skin_color: str
eye_color: str
birth_year: str
gender: str
homeworld: str
films: List[str]
species: List[str]
vehicles: List[str]
starships: List[str]
created: str
edited: str
url: str
@classmethod
def from_dict(cls, data: dict) -> 'StarWarsCharacter':
"""Create a StarWarsCharacter from a dictionary"""
return cls(
name=data.get('name', ''),
height=data.get('height', ''),
mass=data.get('mass', ''),
hair_color=data.get('hair_color', ''),
skin_color=data.get('skin_color', ''),
eye_color=data.get('eye_color', ''),
birth_year=data.get('birth_year', ''),
gender=data.get('gender', ''),
homeworld=data.get('homeworld', ''),
films=data.get('films', []),
species=data.get('species', []),
vehicles=data.get('vehicles', []),
starships=data.get('starships', []),
created=data.get('created', ''),
edited=data.get('edited', ''),
url=data.get('url', '')
)
Copilot Tip
If you are facing an error, you can ask Github Copilot to help you fix the error.
On Mac press CMD + .
and on Windows press Control + .
to open the quick fix menu and use the Github Copilot to fix the error.
Quick fix menu should look like this:
Step 4: Create the API Abstract Base Class (Interface)¶
We will create an abstract base class (interface in Java) that will be used to query the Star Wars API. The abstract base class will have a method that will return a StarWarsCharacter
object.
Copilot Tip
Create a new file called star_wars_api.py
file and use Github Copilot chat to create a Python abstract base class for the Star Wars API. We need to have a method that returns Luke Skywalker as a StarWarsCharacter
object.
Sample Prompt for Copilot
Can you help me create a plain python abstract base class that queries the Star Wars API and returns a StarWarsCharacter object? I would like to start with just one method for querying Luke Skywalker. The abstract base class should be called StarWarsAPI.
Here's a sample implementation for star_wars_api.py
:
Sample Solution
from abc import ABC, abstractmethod
from star_wars_character import StarWarsCharacter
class StarWarsAPI(ABC):
"""Abstract Base Class for Star Wars API client"""
@abstractmethod
def get_luke_skywalker(self) -> StarWarsCharacter:
"""
Get Luke Skywalker's information
Returns:
StarWarsCharacter: Luke Skywalker's data
Raises:
Exception: If an error occurs while fetching the data
"""
pass
Step 5: Create a Test for the StarWarsAPI Abstract Base Class¶
We will now create a test for the StarWarsAPI
abstract base class. The test will be used to verify that the abstract base class is working as expected. We will use pytest to create the test.
Copilot Tip
Open the test_star_wars_api.py
file in the tests folder and use Github Copilot to create a test for the StarWarsAPI
abstract base class. Use the /tests
command to let Copilot generate the test for you.
Here's a sample implementation for test_star_wars_api.py
:
Sample Solution
import pytest
from src.starwarsapi.star_wars_api_impl import StarWarsAPIImpl
from src.starwarsapi.star_wars_character import StarWarsCharacter
def test_get_luke_skywalker():
"""Test that get_luke_skywalker returns Luke Skywalker's data"""
star_wars_api = StarWarsAPIImpl()
luke_skywalker = star_wars_api.get_luke_skywalker()
assert luke_skywalker is not None
assert luke_skywalker.name == "Luke Skywalker"
assert luke_skywalker.height == "172"
assert luke_skywalker.gender == "male"
If you run tests now, you will run into errors. You will see that the StarWarsAPIImpl
class is not yet created. We will implement it in the next step.
Step 6: Implement the API abstract base class¶
Now, let's implement the abstract base class using Python's requests library.
We will first create the StarWarsAPIImpl
class that implements the StarWarsAPI
abstract base class. The class will have a method that will return a StarWarsCharacter
object. As we need to have a start, we will use the Luke Skywalker example and also use a method to query specificly for Luke Skywalker.
Copilot Tip
Create a new file star_wars_api_impl.py
file and use Github Copilot to implement the StarWarsAPI
abstract base class. The implementation should use the requests library to fetch data from the Star Wars API.
Here's a sample implementation for star_wars_api_impl.py
:
Sample solution
from star_wars_api import StarWarsAPI
from star_wars_character import StarWarsCharacter
class StarWarsAPIImpl(StarWarsAPI):
"""Implementation of the Star Wars API client"""
def __init__(self):
self.base_url = "https://swapi.info/api"
def get_luke_skywalker(self) -> StarWarsCharacter:
"""
Get Luke Skywalker's information
"""
We will now implement the get_luke_skywalker
method in the StarWarsAPIImpl
class. We will use requests
library to query the Star Wars API and retrieve the information about Luke Skywalker from the URL: https://swapi.info/api/people/1
The method should be able to fulfill the following points:
- Import the
requests
library - Build the URL for Luke Skywalker (
https://swapi.info/api/people/1/
) - Make a GET request to the API
- Parse the response JSON into a
StarWarsCharacter
object - Return the
StarWarsCharacter
object
Sample Prompts
You can use the following prompts one by one to implement the method. Accept the suggestions by pressing Tab
:
# Import the requests library
# Build the URL for Luke Skywalker
# Make a GET request to the Star Wars API
# Parse the response JSON into a StarWarsCharacter object
# Return the StarWarsCharacter object
Sample Solution
import requests
from star_wars_api import StarWarsAPI
from star_wars_character import StarWarsCharacter
class StarWarsAPIImpl(StarWarsAPI):
"""Implementation of the Star Wars API client"""
def __init__(self):
self.base_url = "https://swapi.info/api"
def get_luke_skywalker(self) -> StarWarsCharacter:
"""
Get Luke Skywalker's information
Returns:
StarWarsCharacter: Luke Skywalker's data
Raises:
Exception: If an error occurs while fetching the data
"""
# Build the URL for Luke Skywalker
url = f"{self.base_url}/people/1/"
# Make a GET request to the Star Wars API
response = requests.get(url, allow_redirects=True)
# Raise an exception for HTTP errors
response.raise_for_status()
# Parse the response JSON into a StarWarsCharacter object
return StarWarsCharacter.from_dict(response.json())
Now let's run the test to see if it works:
If you encounter any issues, you might need to fix the URL. The SWAPI URL sometimes changes, so make sure you're using the correct one. It could be https://swapi.dev/api
or https://swapi.info/api
.
Optional: Add a Method for Darth Vader¶
We will now implement the getDarthVader
method in the StarWarsAPIImpl
class. We will use requests
to query the Star Wars API and retrieve the information about Darth Vader from the url: https://swapi.info/api/people/4
Copilot Tip
Add a new method to the StarWarsAPI
abstract base class and implement it in the StarWarsAPIImpl
class.
Either use a comment or the Copilot Edit/Agent mode with a prompt: Add a method to get information about Darth Vader
.
First, add the method to the abstract base class in star_wars_api.py
:
Sample Solution
from abc import ABC, abstractmethod
from star_wars_character import StarWarsCharacter
class StarWarsAPI(ABC):
"""abstract base class for Star Wars API client"""
@abstractmethod
def get_luke_skywalker(self) -> StarWarsCharacter:
"""
Get Luke Skywalker's information
Returns:
StarWarsCharacter: Luke Skywalker's data
Raises:
Exception: If an error occurs while fetching the data
"""
pass
@abstractmethod
def get_darth_vader(self) -> StarWarsCharacter:
"""
Get Darth Vader's information
Returns:
StarWarsCharacter: Darth Vader's data
Raises:
Exception: If an error occurs while fetching the data
"""
pass
Then, implement the get_darth_vader
method in star_wars_api_impl.py
:
Sample Solution
import requests
from star_wars_api import StarWarsAPI
from star_wars_character import StarWarsCharacter
class StarWarsAPIImpl(StarWarsAPI):
"""Implementation of the Star Wars API client"""
def __init__(self):
self.base_url = "https://swapi.info/api"
def get_luke_skywalker(self) -> StarWarsCharacter:
"""
Get Luke Skywalker's information
Returns:
StarWarsCharacter: Luke Skywalker's data
Raises:
Exception: If an error occurs while fetching the data
"""
url = f"{self.base_url}/people/1/"
response = requests.get(url, allow_redirects=True)
response.raise_for_status() # Raise an exception for HTTP errors
return StarWarsCharacter.from_dict(response.json())
def get_darth_vader(self) -> StarWarsCharacter:
"""
Get Darth Vader's information
Returns:
StarWarsCharacter: Darth Vader's data
Raises:
Exception: If an error occurs while fetching the data
"""
url = f"{self.base_url}/people/4/"
response = requests.get(url, allow_redirects=True)
response.raise_for_status() # Raise an exception for HTTP errors
return StarWarsCharacter.from_dict(response.json())
Since we have duplicate code now, let's refactor the code.
Copilot Tip
Add a method _get_star_wars_character(self, character_id)
and let Copilot help you refactor.
Sample Solution After Refactoring
import requests
from star_wars_api import StarWarsAPI
from star_wars_character import StarWarsCharacter
class StarWarsAPIImpl(StarWarsAPI):
"""Implementation of the Star Wars API client"""
def __init__(self):
self.base_url = "https://swapi.info/api"
def get_luke_skywalker(self) -> StarWarsCharacter:
"""
Get Luke Skywalker's information
Returns:
StarWarsCharacter: Luke Skywalker's data
Raises:
Exception: If an error occurs while fetching the data
"""
return self._get_star_wars_character(1)
def get_darth_vader(self) -> StarWarsCharacter:
"""
Get Darth Vader's information
Returns:
StarWarsCharacter: Darth Vader's data
Raises:
Exception: If an error occurs while fetching the data
"""
return self._get_star_wars_character(4)
def _get_star_wars_character(self, character_id: int) -> StarWarsCharacter:
"""
Get a Star Wars character by ID
Args:
character_id: The ID of the character to fetch
Returns:
StarWarsCharacter: The character data
Raises:
Exception: If an error occurs while fetching the data
"""
url = f"{self.base_url}/people/{character_id}/"
response = requests.get(url, allow_redirects=True)
response.raise_for_status() # Raise an exception for HTTP errors
return StarWarsCharacter.from_dict(response.json())
Optional: Add a Test for the Darth Vader Method¶
Now let's add a test for the get_darth_vader
method:
Copilot Tip
Add a new test method to the test_star_wars_api.py
file that will test the get_darth_vader
method. You can use a comment as a prompt to do so: # Test the get_darth_vader method
Here's a sample test method to add to test_star_wars_api.py
:
Sample Solution
def test_get_darth_vader():
"""Test that get_darth_vader returns Darth Vader's data"""
star_wars_api = StarWarsAPIImpl()
darth_vader = star_wars_api.get_darth_vader()
assert darth_vader is not None
assert darth_vader.name == "Darth Vader"
assert darth_vader.height == "202"
assert darth_vader.gender == "male"
Run the test again to make sure it passes:
Step 9: Create a Simple App Module¶
Let's create a simple application module to demonstrate the API client:
Copilot Tip
Open the app.py
file and use Github Copilot to create a simple application that demonstrates the Star Wars API client.
Here's a sample implementation for app.py
:
Sample Solution
from star_wars_api_impl import StarWarsAPIImpl
def main():
"""Main function to demonstrate the Star Wars API client"""
print("Star Wars API Client")
print("--------------------")
api = StarWarsAPIImpl()
# Get Luke Skywalker
luke = api.get_luke_skywalker()
print(f"\nName: {luke.name}")
print(f"Height: {luke.height} cm")
print(f"Mass: {luke.mass} kg")
print(f"Hair color: {luke.hair_color}")
print(f"Appears in {len(luke.films)} films")
# Get Darth Vader
vader = api.get_darth_vader()
print(f"\nName: {vader.name}")
print(f"Height: {vader.height} cm")
print(f"Mass: {vader.mass} kg")
print(f"Hair color: {vader.hair_color}")
print(f"Appears in {len(vader.films)} films")
if __name__ == "__main__":
main()
Run the application:
You should see output similar to this:
Star Wars API Client
--------------------
Name: Luke Skywalker
Height: 172 cm
Mass: 77 kg
Hair color: blond
Appears in 4 films
Name: Darth Vader
Height: 202 cm
Mass: 136 kg
Hair color: none
Appears in 4 films
Bonus Challenges 🌟¶
If you have extra time, try these bonus challenges with GitHub Copilot:
- Add a method to get information about a character by name (hint: you'll need to use the search endpoint)
- Add a method to get information about a planet
- Add a method to get all characters from a specific film
- Implement caching to avoid making the same API calls repeatedly
Summary 📝¶
In this lab, you've learned how to:
- Use GitHub Copilot to create a Python client for a REST API
- Create data classes, abstract base classes, and implementations in Python
- Write unit tests to verify functionality
- Use Python's requests library to make API calls
GitHub Copilot has helped you write code faster and with less effort, allowing you to focus on the design and architecture rather than the implementation details. This is a great example of how AI-assisted coding can enhance your development workflow, especially when working with external APIs! 🚀