Assignment 4: Extending the Platform
Introduction
In assignment 3, you learned how to write a program that connects to and shares information with a program
running on another computer. The systems we write programs for today are almost always connected to other
computers in some capacity. Whether it is your computer operating system sending data back to its creator, your
web browser automatically downloading updates, or you reaching out to friends and family for a video chat, nearly
all the programs we use today rely on networked communication to extend their capabilities.
While your Distributed Social (DS) program has the ability to accept, store, and distribute information written by
your users, it doesn’t yet take advantage of the vast wealth of information available to networked programs today.
So for this assignment we are going to be extending the DS platform by adding some new features for the users of
your program.
You have probably noticed how in programs like Slack, Discord, and Facebook Messenger, keywords can be entered
into chat conversations that trigger some sort of automatic inclusion or formatting. In computer science, this
process is called transclusion, which basically refers to the process of using a linking syntax to connect or include
content from one source into the content of another. For this assignment, you will be enhancing the post writing
feature you created in assignment 3 by adding support for keyword transclusion.
By now, you should have had a chance to digest some of the core principles of networked communication. You have
learned about protocols, sockets, and request/response communication with a server. Your work for assignment 4
will focus on locking in these concepts as well as exploring some new ones.
Summary of Program Requirements
Connect to and retrieve data from at least 2 web API’s using Python’s urllib module.
Use transclusion to parse and replace keywords in a journal entry with content from web API’s.
Write classes to manage your APIs.
Learning Goals
How to work with public API’s
Know when and how to write a class
Understand and handle failures when communicating over HTTP
Use inheritance to extend class functionality
Program Requirements
Your main entry point to your program will be from a module called a4.py. As with a3, you will not have a validity
checker to use for testing and you will be largely responsible for program input and output. You are encouraged to
continue to bring the user interface and other functions you created in your a1.py through a3.py modules, but you
will not be re-graded on the features you implemented for those assignments.
Your �nal program should contain the following new modules:
1. OpenWeather.py: A module for interacting with the OpenWeather API. See Part 1
2. LastFM.py: A module for interacting with the Last.FM API. See Part 2
3. ExtraCreditAPI.py: A module for interacting with an API of your choice, should you choose to earn extra
credit. See Extra Credit
4. WebAPI.py: A base class module that provides access to common features for your API modules. See Part 3:
The Refactor!
Since the code you write for this assignment will still be dependent on communication with the server, you should
also include all modules you created or used for assignment 3.
Contents
Introduction
Summary of Program
Requirements
Learning Goals
Program Requirements
Part 1
Part 2
Part 3: The Refactor!
Extra Credit
Submitting
Starter Project
How we will grade your submission
Print to PDF
https://ics32.markbaldw.in/prep/a4.html#section-part1
https://ics32.markbaldw.in/prep/a4.html#section-part2
https://ics32.markbaldw.in/prep/a4.html#section-extracredit
https://ics32.markbaldw.in/prep/a4.html#section-part3
If you were unable to complete the send function requirement in a3, let us know. We will walk you
through the steps required to send and receive messages with the DS server.
Part 1
When introducing new features to a program or learning a new tool that is unfamiliar, a good place to start is with a
test program. So for Part 1, you will temporarily set aside your DS program and focus on building a small test
program that can successfully connect to and retrieve data from a public API. You are welcome to use the sample
code below to get started.
Connecting to the OpenWeather API
To use the sample code, you must �rst create an account and generate an API key at OpenWeather:
Create an account at: https://home.openweathermap.org/users/sign_up. If you are prompted to input a
reason, just enter UCI and Education/Science.
Once registered, �nd your API by clicking the “API Keys” link on your account page. Copy your API key and
store it somewhere (as a comment in your code, for example).
Next visit the current weather page. Read through the instructions and build your API call. A basic call should
look like this:
You will likely want to customize this based on your personal preferences, so read through the documentation
thoroughly to understand the various options for weather data available through the API.
Once you have your own API key, copy it into the apikey variable in the sample code above and run the program to
see the result. Notice that the data response from the API is in JSON format. Most web API’s use the JSON format
for storing and sending data and since we are already familiar with JSON from a3, we will continue using it here
(though it’s worth noting that the OpenWeather API does support HTML and XML formats too). The loads function
in Python’s json module should be all you need to use for converting the JSON responses you received into a Python
object (you are free to adapt the extract_json helper code example provided in a3). Also notice, that in the sample
Note
import urllib, json
from urllib import request,error
def _download_url(url_to_download: str) -> dict:
response = None
r_obj = None
try:
response = urllib.request.urlopen(url_to_download)
json_results = response.read()
r_obj = json.loads(json_results)
except urllib.error.HTTPError as e:
print(‘Failed to download contents of URL’)
print(‘Status code: {}’.format(e.code))
finally:
if response != None:
response.close()
return r_obj
def main() -> None:
zip = “92697”
ccode = “US”
apikey = “YOUR API KEY”
url = f”http://api.openweathermap.org/data/2.5/weather?zip={zip},{ccode}&appid={apikey}”
weather_obj = _download_url(url)
if weather_obj is not None:
print(weather_obj[‘weather’][0][‘description’])
if __name__ == ‘__main__’:
main()
api.openweathermap.org/data/2.5/weather?zip={zip code},{country code}&appid={API key}
https://openweathermap.org/
https://home.openweathermap.org/users/sign_up
https://openweathermap.org/current
code, the print out only contains the description attribute for the weather. A more complete output might contain
detailed information like temperature, barometric pressure, and humidity. To see all the data available from the API
response, you can update the sample code to simply print the weather_obj variable.
Now that you have a functioning API call in place, you should start thinking about how to make this information
more accessible for your DS program. A good way to go about doing this is to create a class that abstracts away the
processing logic you need to write to gain access to weather data. So for the remainder of this part of the
assignment, you will write a weather class called OpenWeather that accepts location information and an API key to
store weather information in data attributes.
Your class must include and implement the following two methods:
Your class should be written such that the following validity code runs without error:
Although the sample code included in this assignment does have some error handling, you will likely want to attend
to the various errors that can arise when connecting to remote API’s more gracefully than what is provided. You will
need to understand the different types of errors that occur when communicating over HTTP. Many different
conditions can arise that your program may or may not need to communicate to your end user. For example, an
invalid API key requires a different set of actions to resolve compared to the API service going down. Therefore,
your OpenWeather class should be able to cleanly handle and inform a user about the following conditions:
Loss of local connection to the Internet
404 or 503 HTTP response codes (indicating that the remote API is unavailable)
Invalid data formatting from the remote API
There are many ways to approach handling these types of errors. If you consider that the bulk of the work occurs in
the load_data method, one good strategy might be to throw an exception when an error occurs when this method
is called. However, your exception handling MUST explicitly handle each of the conditions listed by providing a
custom message. For example, if your code catches an HTTP exception, it should raise a new exception with a
custom message related to the operation that is being performed.
We will run code that creates these error conditions to evaluate your OpenWeather class as well as your second
API (see next section).
def set_apikey(self, apikey:str) -> None:
”’
Sets the apikey required to make requests to a web API.
:param apikey: The apikey supplied by the API service
”’
#TODO: assign apikey value to a class data attribute that can be accessed by class members
pass
def load_data(self) -> None:
”’
Calls the web api using the required values and stores the response in class data
attributes.
”’
#TODO: use the apikey data attribute and the urllib module to request data from the web
api. See sample code at the begining of Part 1 for a hint.
#TODO: assign the necessary response data to the required class data attributes
pass
from OpenWeather import OpenWeather
zipcode = “92697”
ccode = “US”
apikey = “YOUR API KEY”
open_weather = OpenWeather(zipcode, ccode)
open_weather.set_apikey(apikey)
open_weather.load_data()
print(f”The temperature for {zipcode} is {open_weather.temperature} degrees”)
print(f”The high for today in {zipcode} will be {open_weather.high_temperature} degrees”)
print(f”The low for today in {zipcode} will be {open_weather.low_temperature} degrees”)
print(f”The coordinates for {zipcode} are {open_weather.longitude} longitude and
{open_weather.latitude} latitude”)
print(f”The current weather for {zipcode} is {open_weather.description}”)
print(f”The current humidity for {zipcode} is {open_weather.humidity}”)
print(f”The sun will set in {open_weather.city} at {open_weather.sunset}”)
Connect to the Last.FM API
Once you have �nished creating your OpenWeather class, you will repeat the process with a new API from Last.FM.
The Last.FM API is large and full of interesting bits of data about music that you can pull into your DS Program. The
main consideration here is to think about how you will present your API data in a DS journal post. The current
limitations of the DS server and journaling platform, for example, would prevent you from using the search feature
of the API. Take a few minutes to look through the documentation and decide on what type of information you want
to support with your keyword.
Once you settle on at least one API method from the Last.FM website, you will repeat the steps used in the previous
section. Read through the API documentation, register to get an API key, and build a supporting class called LastFM
to abstract the underlying API complexities from your program (and don’t forget the error handling!).
You are free to include as many methods from the Last.FM API as you like, each assigned to their own keyword.
However, you must connect one method to the prede�ned keyword listed in Part 2 so that our automated tests can
verify that you successfully connected to the API.
Finally, since we are not specifying how to write the class for your custom API, you will need to ensure that your
class is documented suf�ciently enough for us to understand how it works.
Part 2
Alright, at this point you should have two classes that retrieve data from web API’s. For the second part of the
assignment you will incorporate these classes into the current version of your DS program (where you left off after
a3).
You goal for part 2 should be to allow your user to write journal posts with optional keywords that are transcluded
into data from your API classes. You must create at least two keywords, one for each API, using the keywords listed
below:
You may discover that once you have one keyword connected to an API method that adding more is pretty easy. So,
if you choose, you may create your own keywords for any additional API methods that you want to support as long
as they adhere to the following format:
So for example, the following journal post:
Hello World! Today is the �rst day of the rest of my life. It is @weather outside and I am thrilled!
Should transclude to:
Hello World! Today is the �rst day of the rest of my life. It is sunny outside and I am thrilled!
Okay, that’s a pretty bad example, but you get the idea:) Here, the OpenWeather API feature ‘description’ was
assigned to the @weather keyword so that when the message was transcluded the keyword was replaced with the
description ‘sunny.’ You ARE NOT REQUIRED to bind ‘description’ to the @weather keyword, but you are required
to bind some data from the OpenWeather API to the @weather keyword.
When your user writes a post, you will also want to inform them about the keywords your program makes available.
There are a number of ways to go about accomplishing this task, the most straightforward being a print out of the
keywords. However, you might also consider making keyword selection a little more interactive. Either way, you will
not be graded on how you inform your user about keyword options, only that you have added this feature.
Finally, so that we may accurately test your program, all of your API classes (OpenWeather, LastFM, and
ExtraCreditAPI) must also implement the following function:
@weather
@lastfm
@[KEYWORD]
https://www.last.fm/api#getting-started
https://www.last.fm/api#getting-started
The grading tool will import and call the required functions automatically using its own API keys. You must
take care to not change the �le name or the function parameters (though you can safely add parameters
as long as they are given default values). If you do make changes, it is likely that your code will not pass the
grading tool.
Part 3: The Refactor!
At this point you should have two classes that look similar to the following psuedocode:
If you compare both classes you will likely �nd that both contain some redundant code. It is likely that the
set_apikey and _download_url (or whatever you call the function that handles api requests) methods perform
very similar operations. If so, you have a good indication that it might be time to refactor!
Often times when we write programs it is dif�cult to foresee all the features and code we will require to
make our program function the way we want. But as the program and our understanding of the code we
have written evolves, we gradually begin to �nd points of redundancy. The more redundant code that we
put into our program, the greater the likelihood we face undesirable conditions such as poor performance,
bugs, and confusing logic.
As programmers, it is our job to ensure that our code is performant as possible, given our knowledge and
abilities. One way that we accomplish this is by refactoring our code: reworking the code that we have
written to reduce unneccessary logic, repetition, and errors.
For this �nal part of the assignment, you will refactor your code to reduce redundancy. You will do this by creating a
base class that all of your api classes inherit, so if you have not watched the lecture on inheritance yet, now is a good
time to do it!
Your base class will start with the following template:
def transclude(self, message:str) -> str:
”’
Replaces keywords in a message with associated API data.
:param message: The message to transclude
:returns: The transcluded message
”’
#TODO: write code necessary to transclude keywords in the message parameter with
appropriate data from API
pass
Attention
class MyAPI:
def __init__(…):
# creates required class attribute apikey
# creates API specific class attributes like zip, ccode
pass
def _download_url (…):
pass
def set_apikey(…):
pass
def load_data(…):
pass
def transclude(…):
pass
Tip
You will be required to add the necessary code to each of methods in the WebAPI class. Notice that the load_data
and transclude methods have been assigned the @abstractmethod decorator. If you inherit this class and do not
implement either of the abstract methods, you will receive an error from the python interpreter. Therefore, all code
that is unique to the API used in the child class (or class that inherits from the base class) should be contained within
the these methods.
When complete, the following code should be able to run using any of your API modules:
Extra Credit
We will be awarding 5 extra credit points for the successful inclusion of a third API from a source that has not been
used for this class. If you choose to add a third API, you must explicitly de�ne how to use it in your program and let
us know via submission page comment that you would like to us to review your third API for extra credit. Your API
should be built in a module named ExtraCreditAPI.py and make use of at least one keyword named:
Since it will be near impossible for us to create a unique key for every extra credit API, you MUST hard code your
key into your ExtraCreditAPI class. To ensure consistency across all submissions, your extra credit API must
include the following global variable:
The grading tool will import your ExtraCreditAPI.py module and call the set_apikey function using the
EXTRACREDITAPIKEY global variable as the value for the apikey parameter. So make sure your module supports this
behavior!
Submitting
from abc import ABC, abstractmethod
class WebAPI(ABC):
def _download_url(self, url: str) -> dict:
#TODO: Implement web api request code in a way that supports ALL types of web APIs
pass
def set_apikey(self, apikey:str) -> None:
pass
@abstractmethod
def load_data(self):
pass
@abstractmethod
def transclude(self, message:str) -> str:
pass
def test_api(message:str, apikey:str, webapi:WebAPI):
webapi.set_apikey(apikey)
webapi.load_data()
result = webapi.transclude(message)
print(result)
open_weather = OpenWeather() #notice there are no params here…HINT: be sure to use
parameter defaults!!!
lastfm = LastFM()
test_api(“Testing the weather: @weather”, MY_APIKEY, open_weather)
# expected output should include the original message transcluded with the default weather
value for the @weather keyword.
test_api(“Testing lastFM: @lastfm, MY_APIKEY, lastfm)
# expected output include the original message transcluded with the default music data
assigned to the @lastfm keyword
@extracredit
EXTRACREDITAPIKEY = “YOUR API KEY” # replace with the key required for your custom api.
By Mark S. Baldwin
© Copyright 2021.
Upload all of your program �les in a single .zip �le to Canvas by the due date. NAME YOUR MAIN FILE a4.py. If you
complete the extra credit, be sure to add a comment on your submission page that noti�es us. Failure to leave a
comment means that we may not notice the extra credit feature when grading.
Additionally, all modules that you create or edit must include the following comment on the �rst three lines:
Starter Project
Assignment 4 Starter Repository
How we will grade your submission
This assignment will be graded on a 150 point scale, with the 150 points being allocated completely to whether or
not you submitted something that meets all of the above requirements. The following rubric will be used:
Requirements and Function | 120 pts
Does the program do what it is supposed to do?
Does the program make use of required APIs?
Does the program adhere to the principles of inheritance?
Are there any bugs or errors?
Quality and Design | 30 pts
Is the code well designed?
Is the code clearly documented?
Extra Credit | 5 pts
Does the program makes use of a third API?
Does the API work?
By now you should be clearly documenting your code and expending effort to ensure that your code design follows
the conventions we have been discussing throughout the class. Therefore, we will continue increasing the strictness
of our stance on quality and design than we have in previous assignments.
# NAME
# EMAIL
# STUDENT ID
https://classroom.github.com/a/DJg05tcx