Issue with 'else' sequence using Spotipy/Spotify API - python

My team and I (newbies to python) have written the following code to generate spotify songs related to a specific city and related terms.
If the user inputs a city that is not in our CITY_KEY_WORDS list, then it tells the user that the input will be added to a requests file, and then writes the input to a file.
The code is as follows:
from random import shuffle
from typing import Any, Dict, List
import spotipy
from spotipy.oauth2 import SpotifyClientCredentials
sp = spotipy.Spotify(
auth_manager=SpotifyClientCredentials(client_id="",
client_secret="")
)
CITY_KEY_WORDS = {
'london': ['big ben', 'fuse'],
'paris': ['eiffel tower', 'notre dame', 'louvre'],
'manhattan': ['new york', 'new york city', 'nyc', 'empire state', 'wall street', ],
'rome': ['colosseum', 'roma', 'spanish steps', 'pantheon', 'sistine chapel', 'vatican'],
'berlin': ['berghain', 'berlin wall'],
}
def main(city: str, num_songs: int) -> List[Dict[str, Any]]:
if city in CITY_KEY_WORDS:
"""Searches Spotify for songs that are about `city`. Returns at most `num_songs` tracks."""
results = []
# Search for songs that have `city` in the title
results += sp.search(city, limit=50)['tracks']['items'] # 50 is the maximum Spotify's API allows
# Search for songs that have key words associated with `city`
if city.lower() in CITY_KEY_WORDS.keys():
for related_term in CITY_KEY_WORDS[city.lower()]:
results += sp.search(related_term, limit=50)['tracks']['items']
# Shuffle the results so that they are not ordered by key word and return at most `num_songs`
shuffle(results)
return results[: num_songs]
else:
print("Unfortunately, this city is not yet in our system. We will add it to our requests file.")
with open('requests.txt', 'r') as text_file:
request = text_file.read()
request = request + city + '\n'
with open('requests.txt', 'w+') as text_file:
text_file.write(request)
def display_tracks(tracks: List[Dict[str, Any]]) -> None:
"""Prints the name, artist and URL of each track in `tracks`"""
for num, track in enumerate(tracks):
# Print the relevant details
print(f"{num + 1}. {track['name']} - {track['artists'][0]['name']} {track['external_urls']['spotify']}")
if __name__ == '__main__':
city = input("Virtual holiday city? ")
number_of_songs = input("How many songs would you like? ")
tracks = main(city, int(number_of_songs))
display_tracks(tracks)
The code runs fine for the "if" statement (if someone enters a city we have listed). But when the else statement is run, 2 errors come up after the actions have been executed ok (it prints and writes the user's input into a file).
The errors that come up are:
Traceback (most recent call last):
File "...", line 48, in <module>
display_tracks(tracks)
File "...", line 41, in display_tracks
for num, track in enumerate(tracks):
TypeError: 'NoneType' object is not iterable
Please excuse my lack of knowledge, but please could someone help with this issue?
We would also like to create a playlist of the songs at the end, however have been facing difficulties with this.

Your main function does not have a return statement in the else clause and that causes tracks to be None. Iterating on tracks when it's None is what's causing the error.
There are a few things you can do to improve the code:
separation of concerns: the main function is doing two different things, checking the input and fetching the tracks.
do .lower() once in the beginning so you don't have to repeat it.
following documentation conventions.
checking the response before using it
some code cleaning
see below the changes I suggested above:
def fetch_tracks(city: str, num_songs: int) -> List[Dict[str, Any]]:
"""Searches Spotify for songs that are about `city`.
:param city: TODO: TBD
:param num_songs: TODO: TBD
:return: at most `num_songs` tracks.
"""
results = []
for search_term in [city, *CITY_KEY_WORDS[city]]:
response = sp.search(search_term, limit=50)
if response and 'tracks' in response and 'items' in response['tracks']:
results += response['tracks']['items']
# Shuffle the results so that they are not ordered by key word and return
# at most `num_songs`
shuffle(results)
return results[: num_songs]
def display_tracks(tracks: List[Dict[str, Any]]) -> None:
"""Prints the name, artist and URL of each track in `tracks`"""
for num, track in enumerate(tracks):
# Print the relevant details
print(
f"{num + 1}. {track['name']} - {track['artists'][0]['name']} "
f"{track['external_urls']['spotify']}")
def main():
city = input("Virtual holiday city? ")
city = city.lower()
# Check the input city and handle unsupported cities.
if city not in CITY_KEY_WORDS:
print("Unfortunately, this city is not yet in our system. "
"We will add it to our requests file.")
with open('requests.txt', 'a') as f:
f.write(f"{city}\n")
exit()
number_of_songs = input("How many songs would you like? ")
tracks = fetch_tracks(city, int(number_of_songs))
display_tracks(tracks)
if __name__ == '__main__':
main()

When your if-statement is executed, you return a list of items and feed them into the display_tracks() function. But what happens when the else-statement is executed? You add the request to your text-file, but do not return anything (or a NoneType item) and feed that into display_tracks(). display_tracks then iterates of this NoneType-item, throwing your exception.
You only want to show the tracks if there actually are any tracks to display. One way to do this would be to move the call of display_tracks() into your main function, but then the same error would be thrown if no tracks are found to your search-terms. Another solution would be to first check if your tracks are not empty or to catch the TypeError-exception with something like
tracks = main(city, int(number_of_songs))
try:
display_tracks(tracks)
except TypeError:
pass

Related

My intersection() method returns an error

I have tried to make a piece of code that shows me only the common values of user input as compared to a set list. I continue to get the error:
TypeError: unhashable type: 'list'
Here's what I've done:
main_List = ["name", "job", "backstory", "all"]
provided_List = []
def main()
resp = input("Do you need a name, job, backstory, or all?")
provided_List.append(resp.split())
update_Set = provided_List.intersection(main_List)
print(update_Set)
main()
Essentially, I'm trying to ignore everything but key words the user inputs. As far as I can tell I am doing everything exactly like the examples but it still isn't coming out the way I want. I'm not sure what I'm missing here.
You are attempting to use set methods on list objects. You need to cast both main_list and provided_list as set objects:
main_options = {"name", "job", "backstory", "all"}
prompt = "Select any of name, job, backstory, or all: "
resp = main_options & set(input(prompt).split())
Usage:
Select any of name, job, backstory, or all: name
# resp: {'name'}
Select any of name, job, backstory, or all:
# resp: set()
Select any of name, job, backstory, or all: name backstory job
# resp: {'backstory', 'job', 'name'}

Python script ran OK for weeks using cron job and it's now giving a KeyError

Here's the code I'm trying to run. Works when executed from PyCharm. I set up a cronjob and it worked wonders for weeks. It's now giving a KeyError out of the bloom. Can't find what's wrong since I haven't touched anything in the cronjob.
import csv
import json
import os
import random
import time
from urllib.parse import urlencode
import requests
API_URL = "https://community.koodomobile.com/widget/pointsLeaderboard?"
LEADERBOARD_FILE = "leaderboard_data.json"
def get_leaderboard(period: str = "allTime", max_results: int = 20) -> list:
payload = {"period": period, "maxResults": max_results}
return requests.get(f"{API_URL}{urlencode(payload)}").json()
def dump_leaderboard_data(leaderboard_data: dict) -> None:
with open("leaderboard_data.json", "w") as jf:
json.dump(leaderboard_data, jf, indent=4, sort_keys=True)
def read_leaderboard_data(data_file: str) -> dict:
with open(data_file) as f:
return json.load(f)
def parse_leaderboard(leaderboard: list) -> dict:
return {
item["name"]: {
"id": item["id"],
"score_data": {
time.strftime("%Y-%m-%d %H:%M"): item["points"],
},
"rank": item["leaderboardPosition"],
} for item in leaderboard
}
def update_leaderboard_data(target: dict, new_data: dict) -> dict:
for player, stats in new_data.items():
target[player]["rank"] = stats["rank"]
target[player]["score_data"].update(stats["score_data"])
return target
def leaderboard_to_csv(leaderboard: dict) -> None:
data_rows = [
[player] + list(stats["score_data"].values())
for player, stats in leaderboard.items()
]
random_player = random.choice(list(leaderboard.keys()))
dates = list(leaderboard[random_player]["score_data"])
with open("the_data.csv", "w") as output:
w = csv.writer(output)
w.writerow([""] + dates)
w.writerows(data_rows)
def script_runner():
if os.path.isfile(LEADERBOARD_FILE):
fresh_data = update_leaderboard_data(
target=read_leaderboard_data(LEADERBOARD_FILE),
new_data=parse_leaderboard(get_leaderboard()),
)
leaderboard_to_csv(fresh_data)
dump_leaderboard_data(fresh_data)
else:
dump_leaderboard_data(parse_leaderboard(get_leaderboard()))
if __name__ == "__main__":
script_runner()
Here's the error taht it gives me. Seems like there's a problem with dictionary hence the KeyError.
File "/Users/Rob/PycharmProjects/Koodo/TEST.Json.py", line 75, in <module>
script_runner()
File "/Users/Rob/PycharmProjects/Koodo/TEST.Json.py", line 64, in script_runner
fresh_data = update_leaderboard_data(
File "/Users/Rob/PycharmProjects/Koodo/TEST.Json.py", line 44, in update_leaderboard_data
target[player]["rank"] = stats["rank"]
KeyError: 'triggered123'
Here's the data in JSON file : https://pastebin.com/HQyL4Kyx
It looks to me like the first time your code is run, it caches the leaderboard.
On subsequent runs, it will update the existing leaderboard with the new values, by looping over each of the new entries, finding their username, and looking up that key in the older dictionary. However, if there's a new user, that key will not exist in the old dictionary.
You're getting the error because the player triggered123 is a new user to the leaderboard, and was listed after you first ran the script.
You need to update update_leaderboard_data to handle this case (by checking if the key exists in the dictionary before attempting to access it).
That's because the key named triggered123 is NOT present in the dictionary target.
Source: https://wiki.python.org/moin/KeyError
The problem is that in update_leaderboard_data you iterate over the new data and use them to lookup in the old data. If a new key does not already exists in the old data, you'll get the KeyError.
Try to print the dict target to see if the key triggered123 is in it.
Or use the get method of the dict with a default value: so no error wil be raised, and you'll be able to search this default value in your output.

How to use weatherstack.com api

Hi I'm pretty new to python. I try to make a chatbot with rasa for personal use. Would like to add weather api now. I use weatherstack.com. If I use their example this works, but if I adjust it to my own liking I always end up with the same error.
condition = api_response['current']['weather_descriptions'] TypeError: list indices must be integers or slices, not str
This is the code I am using.
class Actionweatherapi(Action):
def name(self):
return "actions.weatherapi"
def run(self, dispatcher, tracker, domain):
import requests
location = tracker.get_slot('location')
if location == 'None':
location = 'fetch:ip'
api_result = requests.get('http://api.weatherstack.com/current?access_key=0000000&query={}'.format(location))
api_response = api_result.json()
country = api_response['request']['query']
condition = api_response['current']['weather_descriptions']
temperature_c = api_response['current']['temperature']
humidity = api_response['current']['humidity']
wind_mph = api_response['current']['wind_speed']
response = """It is currently {} in {} at the moment. The temperature is {} degrees, the humidity is {}% and the wind speed is {} mph.""".format(condition, country, temperature_c, humidity, wind_mph)
dispatcher.utter_message(response)
return [AllSlotsReset()]
I have also reviewed the printf-style String Formatting on the python website, but I cannot figure out how to get it working.
The json response is like this example
http://api.weatherstack.com/current?access_key=1c80e8b9fe4fcef4f8d6fd7514a8e9e9&query=New%20York

Function works correctly, except through another module, no visible reason

There is an issue I don't understand. I have two customized modules in a directory.
Note the following codes were voluntarily shorten. I give the function that does not work, and another one which is similar but works.
Let's see:
module_parsing.py
import re
def hippodrome_numreu_prix(response):
"""Parse the hippodrome's name, number of the meeting and prize's name.
response -- html page. Arrival table.
returns a tuple as (hippdrome's name, nb of meeting, prize's name)
"""
#hippodrome means racecourse, but this word exists in English
#There are several races on a hippodrome for a particular day, that's called a meeting("reunion" in French). On a particular day there are several meetings too. So that's why we call it number of meeting: n°1, n°2...
#A race is for a prize ("prix" in French). This prize has a name.
hip_num = response.xpath("//html//h1[#class='CourseHeader-title']/text()").extract()
hip_num = ''.join(hip_num)
#HIPPODROME
if re.search('-\s{0,5}([A-zÀ-ÿ|-|\s]+)\s{0,5}/', hip_num):
hippo = re.search('-\s{0,5}([A-zÀ-ÿ|-|\s]+)\s{0,5}/', hip_num).group(1).lower().replace(' ','')
else:
hippo = None
#NUMBER OF MEETING
if re.search('R[0-9]+', hip_num):
num_reunion = re.search('R[0-9]+', hip_num).group()
else:
num_reunion = 'PMH'
#PRIZE
prix = response.xpath("//html//h1[#class='CourseHeader-title']/strong/text()").extract_first().lower()
return (hippo,num_reunion,prix)
def allocation_devise(response):
"""Parse the amount of allowance and currency (€, £, $, etc)
response -- html page. Arrival table.
returns a tuple as (allowance's amount, currency's symbol)
"""
#"allocation" means allowance. That's sum of the prizes for winners: 1st, 2nd, 3rd, etc.
#"devise" means currency. Depending of the country of the hippodrome, the allowance are expressed in different currencies.
alloc_devise = response.xpath("//html//div[#class='row-fluid row-no-margin text-left']/p[2]/text()[2]").extract_first()
#ALLOWANCE
if re.search(r'[0-9]+',alloc_devise.replace('.','')):
alloc = int(re.search(r'[0-9]+',alloc_devise.replace('.','')).group())
else:
alloc = None
#CURRENCY
if re.search(r'([A-Z|a-z|£|$|€]+)',alloc_devise):
devise = re.search(r'([A-Z|a-z|£|$|€]+)',alloc_devise).group()
else:
devise = None
return (alloc, devise)
module_correction.py has a dependency with the previous one:
from module_parsing import *
def fonction_parse_correction(
champ_corrige, response):
dico_fonction_parse = {
'allocation': allocation_devise,
'devise': allocation_devise,
'hippodrome':hippodrome_numreu_prix,
'reunion' : hippodrome_numreu_prix,
'prix': hippodrome_numreu_prix,
}
if champ_corrige in {'allocation','hippodrome'}:
return dico_fonction_parse[champ_corrige](response)[0]
elif champ_corrige in {'devise','reunion'}:
return dico_fonction_parse[champ_corrige](response)[1]
elif champ_corrige in {'prix'}:
return dico_fonction_parse[champ_corrige](response)[2]
Now when I am in scrapy shell to test my function:
scrapy shell https://www.paris-turf.com/programme-courses/2019-07-09/reunion-chateaubriant/resultats-rapports/prix-synergie-1150124
#Here I change the path with sys.path.insert() and only import module_correction
In [1]: from module_correction import *
In [2]: fonction_parse_correction('hippodrome',response)
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-34-e82fe121aab0> in <module>
----> 1 fonction_parse_correction('hippodrome',response)
~/.../scrapy_project/scrapy_project/module_correction.py in fonction_parse_correction(champ_corrige, response)
103 if champ_corrige in {'allocation','hippodrome'}:
--> 104 return dico_fonction_parse[champ_corrige](response)[0]
105 elif champ_corrige in {'devise','reunion'}:
~/.../scrapy_project/scrapy_project/module_parsing.py in hippodrome_numreu_prix(response)
157 #HIPPODROME
--> 158 if re.search('-\s{0,5}([A-zÀ-ÿ|-|\s]+)\s{0,5}/', hip_num):
160 hippo = re.search('-\s{0,5}([A-zÀ-ÿ|-|\s]+)\s{0,5}/', hip_num).group(1).lower().replace(' ','')
161 else:
AttributeError: 'NoneType' object has no attribute 'group'
It turns weird when I execute allocation_devise() through fonction_parse_correction(), because it works:
In [3]: fonction_parse_correction('allocation',response)
Out[3]: 35000
and this more weird when I simply copy and paste hippodrome_numreu_prix() function in the shell to execute it by itself as:
In [4]: hippodrome_numreu_prix(response)
Out[4]: ('châteaubriant', 'R1', 'prix synergie')
So here, I clearly see there is no problem of None type because search() clearly finds it. Besides, it seems this is not a problem of use it by a dict because the similar function allocation_devise() works well, and even works on the same argument response.
What is wrong that I don't see ?
Notes: Ubuntu 18.04, Scrapy 1.5.2, Python 3.7.1

Using Python Classes and Lists to print reports from a csv

I have a homework assignment that I have been stuck on for several days.
Basic problem description:
Incident class has properties: ID, time, type, location, narrative and status
methods: init, brief, isMorning, resolve
script takes one argument, the full path of crime report csv.
First few lines of CSV:
ID Time Type Location Narrative
1271 11:54 AM Drug Violation Wolf Ridge Report of possible drug violation. Student was referred to the university.
My code so far:
import sys
class Incident:
def __init__(self, ID, time, type, location, narrative, status):
self.ID = id
self.time = time
self.type = type
self.location = location
self.narrative = narrative
self.status = status
def brief(self):
print '''{0}: {1}, {2}
{3}
'''.format(self.ID, self.type, self.status, self.narrative)
def isMorning(self):
if 'AM' in self.time:
return True
else:
return False
def resolve(self):
if self.status == 'Pending':
self.status = 'Resolved'
try:
dataset = sys.argv[1] except IndexError:
print 'Usage: Requires full path input file name.'
sys.exit()
# Create an empty list to contain the Incident objects. crimeList = []
# Read the crime report. with open(dataset, 'r') as f:
# Read the header.
headers = f.readline().split(',')
# Read each record and parse the attributes.
for line in f:
lineList = line.strip().split(',')
reportNumber = lineList[0]
timeReported = lineList[1]
incidentType = lineList[2]
location = lineList[3]
narrative = lineList[4]
status = lineList[5].strip()
### Create initialize an Incident object instance and store it in a variable
crime = Incident(reportNumber, timeReported, incidentType, location, narrative, status)
### Append the new Incident object to the crimeList.
crimeList.append(crime)
What i'm stuck on:
I need to access the "nth" Incident in the crimeList and run various methods. I can't seem to find a way to access the item and have it functional to run methods on.
I've tried enumerating and splicing but just can't get anything to work?
Anyone have any suggestions?
Look up the nth crime from your crimeList like so: x=crimeList[n], and then call the methods on that instance: x.brief(), x.resolve(), etc.

Categories