8
\$\begingroup\$

This program connects to Soundcloud and turns the supplied username into a CSV file with the user's like list. The code accomplishes this although it looks rather messy and was wondering what I could change to improve it.

import soundcloud
import urllib.request
import json
import requests

client = soundcloud.Client(client_id="********************************")


def get_username():
    """
    Asks user for their Soundcloud username.
    """
    username = input("Please enter your Soundcloud username: ")
    user_id = (valid_username(username))
    print(user_id)


def valid_username(username):
    """
    Checks if supplied username is valid.
    """
    global user
    try:
        user = client.get("/resolve", url="http://soundcloud.com/{}/".format(username))
        user_info(user.id)
    except requests.HTTPError:  # Runs if account cannot be found.
        print("The username you entered is invalid, please try again")
        get_username()


def user_info(user_id):
    """
    Retrieves the users information from their user ID
    and prints the number of tracks they have liked.
    """
    global number_of_user_likes
    retrieve = urllib.request.urlopen("https://api.soundcloud.com/users/{}.json?consumer_key="
                                      "********************************".format(user_id)).read()
    decode_user_info = json.loads(retrieve.decode())
    number_of_user_likes = decode_user_info['public_favorites_count']
    username = decode_user_info['username']
    print("You have liked a total of {} tracks".format(number_of_user_likes))
    save_likes(username)


def save_likes(username):
    """
    Creates a CSV file with headers and then saves the users likes to the CSV file.
    """
    csv_file = open("{}s like list.csv".format(username), 'w', encoding='UTF-8')
    csv_file.write("Track Title, Track URL\n")  # Writes headers to CSV file.
    offset_number = 0
    complete = False
    while number_of_user_likes > 0 and complete is False:
        if offset_number < number_of_user_likes:
            try:
                track_fetch = urllib.request.urlopen(
                    "http://api.soundcloud.com/users/{}/favorites.json?client_id="
                    "********************************&offset={}&limit1".format(user.id, offset_number)).read()
                track_data = json.loads(track_fetch.decode())
                if "," in track_data[0]["title"]:
                    track_title = track_data[0]["title"].replace(",",
                                                                 "")  # Removes commas as causes issues with .csv files
                else:
                    track_title = track_data[0]["title"]
                csv_file.write("{},{}\n".format(track_title, track_data[0]["permalink_url"]))
                offset_number += 1
                print("{} of {} ({}%)".format(offset_number, number_of_user_likes,
                                              round(float(100 / number_of_user_likes * offset_number), 2)))
            except IndexError:
                print("There is an issue with Soundcloud, please try again")
            except requests.HTTPError:
                print("There is an issue with Soundcloud, please try again")
            except requests.ConnectionError:
                print("Check your internet connection")
        else:
            complete = True
            finished()


def finished():
    """
    Runs after the users like list has finished being saved.
    """
    print("Finished")


get_username()
\$\endgroup\$
0

3 Answers 3

5
\$\begingroup\$

Use of functions

Functions can return values. You should do that instead of passing back results indirectly via global variables.

In get_username(), user_id = (valid_username(username)) is nonsense, since the valid_username() function never returns anything.

Worse, you are misusing functions as if they were goto labels. get_username() and valid_username() are mutually recursive, which is inappropriate. Furthermore, get_username() triggers all of the activity in the program, which is much more than getting the username as the function name would suggest.

Rearranging everything…

def prompt_username():
    return input("Please enter your Soundcloud username: ")

def get_account(username):
    try:
        return client.get("/resolve", url="http://soundcloud.com/{}/".format(username))
    except requests.HTTPError:
        raise ValueError("Invalid username")

def prompt_account():
    while True:
        try:
            return get_account(prompt_username())
        except ValueError:
            print("The username you entered is invalid.  Please try again")

def get_favorites(account):
    req = urllib.request.urlopen("https://api.soundcloud.com/users/{}.json?consumer_key={}",
                                 account.id, consumer_key)
    …
    return decoded_user_info['username']

def save_favorites(account, favorites):
    …

Now you can write this, which does convey what the program does, unlike your get_username() "function" which was really just a goto label:

account = prompt_account()
save_favorites(account, get_favorites(account))
\$\endgroup\$
2
\$\begingroup\$
  • First of all I would use a class for this, handling variables between functions using globals is very ugly and hard to maintain. Using a class we can easily share attributes across all of its methods easily and cleanly.

  • user_id = (valid_username(username)) is actually useless as you're not returning anything from that function.

  • To shorten our urls we can create a global variable:

   API_BASE_URL = "https://api.soundcloud.com"
  • As you are importing the requests library why not use its .get() method as well instead of urllib.request.urlopen. Another advantage here is that the Response object returned here has a .json() method that returns a dict to us. So, code will simplify to:

    r = requests.get(url)
    decode_user_info = r.json()
  • For creating a csv file better use the csv module instead of writing lines with , and also you are removing commas from track titles to make your workaround work.

  • For handling files always use the with statement, it makes sure the file is closed properly in case an error occurs.

  • Instead of using while number_of_user_likes > 0 and complete is False: if offset_number < number_of_user_likes:..., you can use a simple for-loop with range():

    for offset_number in range(self.number_of_likes):

Complete code:

import csv
import soundcloud
import urllib.request
import requests


CLIENT_ID = "*******************************"
CLIENT_SECRET = "******************************"
API_BASE_URL = "https://api.soundcloud.com"


class Client:

    def __init__(self, client_id, client_secret):
        self.client = soundcloud.Client(client_id=client_id,
                                        client_secret=client_secret)
        self.client_id = client_id
        self.username = self.get_username()
        self.user_info()
        self.save_likes()

    def get_username(self):
        """
        Asks user for their Soundcloud username.
        """
        username = input("Please enter your Soundcloud username: ")
        return self.validate_username(username)

    def validate_username(self, username):
        """
        Checks if supplied username is valid.
        """
        try:
            self.user = self.client.get("/resolve", url="http://soundcloud.com/{}/".format(username))
            return username
        except requests.HTTPError:  # Runs if account cannot be found.
            print("The username you entered is invalid, please try again")
            return self.get_username()

    def user_info(self):
        """
        Retrieves the users information from their user ID
        and prints the number of tracks they have liked.
        """
        r = requests.get("{}/users/{}.json?client_id={}".format(API_BASE_URL,
                                                                self.user.id,
                                                                self.client_id))

        decode_user_info = r.json()
        self.number_of_likes = decode_user_info['public_favorites_count']
        print("You have liked a total of {} tracks".format(self.number_of_likes))

    def save_likes(self):
        """
        Creates a CSV file with headers and then saves the users likes to the CSV file.
        """
        with open("{}s like list.csv".format(self.username), 'w', encoding='UTF-8') as f:
            writer = csv.writer(f)
            writer.writerow(["Track Title", "Track URL"])  # Writes headers to CSV file.
            for offset_number in range(self.number_of_likes):
                try:
                    r = requests.get("{}/users/{}/favorites.json?client_id={}&offset={}&limit=1".format(API_BASE_URL,
                                                                                                        self.user.id,
                                                                                                        self.client_id,
                                                                                                        offset_number))
                    track_data = r.json()[0]
                    writer.writerow([track_data['title'], track_data['permalink_url']])
                    print("{} of {} ({}%)".format(offset_number+1, self.number_of_likes,
                                                  round(float(100 / self.number_of_likes * offset_number), 2)))
                except IndexError:
                    print("There is an issue with Soundcloud, please try again")
                except requests.HTTPError:
                    print("There is an issue with Soundcloud, please try again")
                except requests.ConnectionError:
                    print("Check your internet connection")
        self.finished()

    def finished(self):
        """
        Runs after the users like list has finished being saved.
        """
        print("Finished")


if __name__ == '__main__':
    Client(CLIENT_ID, CLIENT_SECRET)
\$\endgroup\$
2
\$\begingroup\$

CSV support

Python has a csv library. Use it. It handles some issues for you automatically, such as the quoting of special characters.

Ineffective use of the SoundCloud API client

There should be no reason to resort to using the requests library when you're already using an API client that is built for the task. You don't have to worry about HTTP or JSON. It's as simple as this:

import csv
import soundcloud
import requests

client = soundcloud.Client(client_id=CLIENT)

def write_like_list(username, file):
    try:
        favorites = client.get('/users/{}/favorites'.format(username))
        csv_writer = csv.writer(file)
        csv_writer.writerow(['Track Title', 'Track URL'])
        for fav in favorites:
            csv_writer.writerow([fav.title, fav.permalink_url])
    except requests.HTTPError as e:
        if 404 == e.response.status_code:
            raise ValueError("User not found: {}".format(username))
        else:
            raise  # Some other error.  Propagate the exception

username = …
with open('{}s like list.csv'.format(username), 'w', encoding='UTF-8') as file:
    write_like_list(username, file)
\$\endgroup\$
2
  • \$\begingroup\$ Bear in mind that Python 2's native CSV library isn't Unicode aware - there's a block of code at the end of docs.python.org/2/library/csv.html#examples which you can use. \$\endgroup\$
    – Dragon
    Commented Feb 25, 2015 at 20:37
  • \$\begingroup\$ Thank you, I will make the changes, thanks for the feedback! \$\endgroup\$ Commented Feb 25, 2015 at 22:24

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.