Department SQL Class Exercise
Countries API Exercise - Building a Country Class
🎯 Goal: Object-Oriented Data Processing
Why Create a Class?
Encapsulation & Abstraction: Instead of working with messy dictionaries full of nested data, we create a clean Country
object that’s easy to understand and manipulate.
# Instead of this mess:
country_dict['capitalInfo']['latlng'][0] # Latitude? Longitude? Confusing!
# We get this:
country.capital_lat # Crystal clear!
Encapsulation & Abstraction:
The idea is to hide complexity and simplify the interface.
The API returns a big dictionary with lots of nested data and weird field names. Instead of remembering that the capital’s latitude is data['capitalInfo']['latlng'][0]
, you create a Country
object where it’s simply country.capital_lat
.
Encapsulation = wrapping/hiding complicated technical details
Abstraction = creating a simple, intuitive interface
In practice:
- Instead of manipulating
{'name': {'common': 'France'}, 'capitalInfo': {'latlng': [48.8566, 2.3522]}}
- You manipulate a
Country
object withcountry.name
andcountry.capital_lat
It’s like having a TV remote - you press “volume +”, you don’t think about the electronic circuits behind it. The Country
class is your “remote control” for country data. You custom it once to your needs and then you can manipulate the data without thinking about the technicalities.
Result: More readable code, fewer errors, easier to use and maintain. Classes let us transform raw API data into intuitive, reusable objects with useful methods.
🌍 The REST Countries API
We’ll use this API that provides information about countries.
Base URL: https://restcountries.com/v3.1/
Key Endpoints:
GET /name/{countryName}
- Search by country nameGET /capital/{capitalName}
- Search by capital city
Quick Test:
import requests
# Get France data
response = requests.get("https://restcountries.com/v3.1/name/france")
france_data = response.json()[0] # API returns a list, take first result
print(france_data.keys()) # See all available attributes
Important: Query countries one by one to avoid overloading the API when testing. Don’t use /all
endpoint until your code works.
🏗 Your Task: Build the Country Class
Step 1: Create the Basic Class
You need to create a Country
class that simplifies the API data. The API returns complex nested dictionaries - your job is to extract and simplify the useful information.
Key simplifications to make:
name['common']
→name
(simple string)capitalInfo['latlng']
→capital_lat
,capital_lon
(separate float attributes)capital[0]
→capital
(first capital as string)- Keep:
population
,area
,region
,borders
,landlocked
class Country:
def __init__(self, api_data):
# Extract and simplify the essential data we want
self.name: str = # TODO: Extract from api_data
self.code: str = # TODO: Extract country code (cca2)
self.population: int = # TODO: Extract population
self.area: float = # TODO: Extract area
self.region: str = # TODO: Extract region
self.capital: str = api_data['capital'][0] if api_data.get('capital') else "N/A"
# Simplify capital coordinates
if 'capitalInfo' in api_data and 'latlng' in api_data['capitalInfo']:
coords = api_data['capitalInfo']['latlng']
self.capital_lat: float = coords[0]
self.capital_lon: float = coords[1]
else:
self.capital_lat, self.capital_lon = 0.0, 0.0
# Other useful attributes
self.borders: list = # TODO: Extract borders list (or empty list if missing)
self.landlocked: bool = # TODO: Extract landlocked status
self.languages: list = # TODO: Convert languages dict to list of values
Step 2: Implement __repr__
Method
Question: What is __repr__
and why do we need it?
__repr__
defines how your object appears when printed. Without it:
print(france) # Output: <__main__.Country object at 0x7f8b8c0d5f40> - useless!
With __repr__
:
def __repr__(self):
# self is the current Country object - use self.attribute_name to access data
# __repr__ is a special method that controls what is printed
pass
print(france) # Output: France (67,391,582 people, 551,695 km²) - useful!
Implement a clean __repr__
that shows the most important country information of your choice.
🧮 Methods to Implement
1. Capital Distance Calculator
Calculate the distance between two countries’ capitals using the Haversine formula (implementation here for example):
def capital_distance(self, other_country: Country):
"""Calculate distance between capitals in kilometers"""
# self = this country, other_country = the country to compare with
# Use self.capital_lat, self.capital_lon and other_country.capital_lat, other_country.capital_lon
# Apply the Haversine formula provided above
pass
# We then could compare like so:
france: Country = Country(france_data)
germany: Country = Country(germany_data)
france.capital_distance(germany) # We call the method of the class Country for the instance france
2. Population Density
def density(self):
"""Population density (people per km²)"""
# Implement this - handle division by zero!
pass
3. Border Analysis
def are_neighbors(self, other_country):
"""Do these countries share a border?"""
# Check if other_country's code is in self.borders list
pass
def common_neighbors(self, other_country) -> list[Country]:
"""Return list of shared neighbor country codes"""
# Use set intersection between self.borders and other_country.borders
# then query the API again to retrieve the country that have the codes
# and return a list of Countries
# Example: borders: ['DZA', 'LBY'] and ['DZA', 'NER'] → common: ['DZA']
pass
france: Country = Country(france_data)
germany: Country = Country(germany_data)
shared_neighbors = france.common_neighbors(germany) # Returns list of Country objects
for neighbor in shared_neighbors:
print(neighbor) # this would print "Belgium (Brussels)", "Luxembourg (Luxembourg)", "Switzerland" (Bern) depening on your implementation of __repr__
⚖️ Comparison Methods with __lt__
Question: What is __lt__
and how does it enable sorting?
__lt__
(less than) is a special method that tells Python how to compare two objects:
You can see how to implement it on geeksforgeeks
def __lt__(self, other):
"""Define how to compare countries - YOU IMPLEMENT THIS"""
# Your code here - compare by population
# What should happen if populations are equal?
pass
# Once implemented, you can perform sorting and max on your countries:
countries = [france, germany, italy]
sorted_countries = sorted(countries) # This will sort the countries by population
biggest_country = max(countries) # This will return the country with the highest pop (germany)
Your task:
- Implement
__lt__
to compare countries by population - Challenge: What happens if two countries have the same population? How do you break ties? (Hint: use a second criterion like area, name)
End use: This enables automatic sorting and comparison of Country objects using Python’s built-in functions like sorted()
, max()
, min()
.
📤 Submission
Fork the repository first, then push your answers to the course website on a branch named corrections
:
git switch -c corrections
git add your-notebook.ipynb
git commit -m "Countries API exercise"
git push origin corrections.
Create a Pull Request: Once pushed, go to GitHub and create a Pull Request from your corrections branch to the main repository. This allows for code review and feedback on your implementation.
🎯 Learning Objectives
By completing this exercise, you’ll understand:
- How to transform raw API data into clean, usable objects
- The power of encapsulation (hiding complexity behind simple interfaces)
- How special methods like
__repr__
and__lt__
make objects more Pythonic - Practical API consumption patterns for real-world projects
Good luck! 🚀