Day 056 - Project 56 : Sorting Songs by Artist

I’m getting an error “[Errno 17] File exists:” but I cannot see in the UI were the files are. I’ve searched multiple times but I only see a few other files and “100MostStreamedSongs.csv”
https://replit.com/@Ahelsinger/Day56100Days

This is what I see

1 Like

It looks like you are making the directory, but it already exists. Simply checking if the directory exists already should solve your problem. Try using a function like the following, so it will only create if the directory doesn’t already exist.

import os

def mkdir(path):
    if not os.path.exists(path):
        os.mkdir(path)

Edit: It turns out you cannot create a file and directory with the same name. The code above has been edited to reflect this.

This could also work too:

def mkdir(path):
    try:
        os.mkdir(path)
    except FileNotFoundError:
        pass # or print something, but this will prevent it from erroring out
2 Likes

Oh, were did you click? I only see this.

2 Likes

Open the sidebar

image

4 Likes

I'm stuck from here:

import csv, os, random,time

with open("100MostStreamedSongs.csv") as file:
  reader = csv.DictReader(file)
  dir=os.listdir()
  for row in reader:
    name1="songs"
    name=random.randint(1,1000000000000000000000000)
    print(f"{row['Artist(s)']}")
    os.mkdir(name1+str(name))
    artists=row['Artists(s)']

Hi :slight_smile: ,

First of all thx for this amazing web series and learning support.

I have a question: In this command path = os.path.join(f"{artist}/", song) He uses a f string and puts comas and slashes in it. Is the use of this to end the f string and to continue to print without the f string? But why not just put song in another {} and continue as a f string. Would that not work?

I hope you understand my question… English is not my first language… .

1 Like

I don’t see that line of code in the solution.

You could do this:

path = os.path.join(f"{artist}/", f"{song}")

but it’s just adding extra characters to do the same thing.

1 Like

I decided to do things a little differently here. I created a folder for the rank > artist > song.txt where song.txt had rank, artist and song. Then I created a backup folder which had all of the songs.txt listed with the song in the file.

2 Likes

I would suggest as an alternative to the tutorial solution, instead of just creating a folder for each artist , to first create an Artists parent folder.

The way it currently works is it dumps a folder for each artist in your file tree, making it a time-consuming process if you should need to delete them all to try something else with your code.

:warning: :stop_sign: Spoilers : Solutions :stop_sign: :warning:
Here is the tutorial solution :

import csv, os
with open("100MostStreamedSongs.csv") as file:
  reader = csv.DictReader(file)
  
  for row in reader:
    dir = os.listdir()
    artist = row["Artist(s)"].title()
    print(artist)
    if artist not in dir:
      os.mkdir(artist)
    song = row["Song"]
    print(row["Song"])
    path = os.path.join(f"{artist}/", song)
    f = open(path, "w")
    f.close()

and here is the modified version :

import csv, os
parentdirectory = "Artists" #1
with open("100MostStreamedSongs.csv") as file:
  reader = csv.DictReader(file)
  for row in reader:
    dir = os.listdir()
    artist = row["Artist(s)"].title()
    print(artist)
    if artist not in dir:
      new_directory_path = os.path.join(parentdirectory, artist) #2
      try:#3
        os.mkdir(new_directory_path)#4
      except:#5
        print(end="")#6
    song = row["Song"]
    print(row["Song"])
    path = os.path.join("Artists/",f"{artist}/", song)#7
    f = open(path, "w")
    f.close()

With this method we get a much more manageable file tree :

We can delete everything at once instead of having to delete each folder one by one. And everything still works (at least for the tutorial solution version.)

:warning:If you delete the Artists parent folder you will manually have to recreate it.:warning:

(My) Explanation
I believe it works by :

  1. It creates a variable named parentdirectory and sets it to “Artists” (to use as the new parent folder)
  2. Instead of immediately creating a directory for each artist after the if, it uses new_directory_path = os.path.join(parentdirectory, artist) (Artists/artist) to create a new variable (new_directory_path) which we’ll use instead of just using the artist.
  3. It tries to create a new directory Artists/(whicheverartist) except (for when the directory already exists) in which case it will just print blank lines with end=“” to not take up any space
  4. lastly instead of :
    path = os.path.join(f"{artist}/", song)
    we use
    path = os.path.join("Artists/",f"{artist}/", song)
    which will correctly add each song to its corresponding artist’s folder within the new Artists (parent) folder

I’m sure there are other ways it could be written too.

I’m currently only on day 56 so I don’t know if this is covered in future days or if you’ll need the code to work the way it originally does in future days, so use the altered solution/method at your own risk.

I’m also uncertain if the way the original code works is meant to be an integral part of the lesson.

To make the most out of the lesson I would first use the altered code and see how it works, what it does, try to break it down and dissect and understand it , then you can easily reset everything by deleting the parent artists folder and deleting the code , and then try the original solution version. But it’s up to you how you want to proceed.

This is just my understanding.
if anyone spots anything wrong please let me know so I can fix it.
To attempt to answer your question:

The way filepaths are named they use / to move in and out of directories. In this case you can think of it as folders inside of folders.

So when you have a file path like /mydocuments/notes/mynotes.txt

It can be understood as you have the file mynotes.txt INSIDE the folder notes which is INSIDE the folder mydocuments.

Since the goal of the lesson is to put each song into a folder corresponding to the artist of that song we aim to get :

(whichever)Artist/song.txt

as the file path.

Since os.path.join just uses artist to get the artist’s name and not the name of the artist plus slash /. The slash in the code you are referring to is simply put there to tell it to put the song inside the artist folder.

When you combine an f-string and a regular string, such as (f"{artist}/", song), it can be referred to as string concatenation. An f-string includes the part inside the quotes after the “f” and ends at the closing quotation mark. In your original code, the entire expression f"{artist}/" is an f-string, which is used to format the value of the artist variable. The rest, including song, is a regular string. When combined using os.path.join, it creates a complete file path through string concatenation.

I asked ai a leading question :
So why is the f string terminated after artist rather than continuing on to contain song as well? Is it because of how os.path.join works? We need it to join the two as a path and without terminating the f string and then adding something else after the comma, we’re telling it to join the f string with nothing which is not possible?
So without at least two things in os.path.join it cannot work because there is nothing to join together correct?

To prompt the response :
Yes, the reason the f-string is terminated after artist is because that’s how f-strings work in Python. An f-string is used for formatting a specific variable or expression within a string. In your example, it’s used to format the artist variable into the string, and the f-string is terminated at the closing curly brace immediately after artist.

The f-string does not continue to include song because that’s not how f-strings are designed to work. If you wanted to include song in the resulting string, you would typically concatenate it with the f-string using the + operator or use another f-string for song separately.

In the context of os.path.join, you want to join two strings together to create a path. You can achieve this by using the f-string for artist to format it and then joining it with the song string using os.path.join, as shown in your original code. The f-string is used for formatting one part, and the regular string is used for the other part, which is a common approach to constructing paths in Python.

os.path.join is designed to join two or more path components together to create a complete file path or directory path. If you provide only one argument to os.path.join, it doesn’t have anything to join with, and it won’t work as intended. You need at least two path components (strings) to join together to create a valid path using os.path.join. So, having two components, like in your original code (f"{artist}/", song), allows os.path.join to combine them and create a proper path.

2nd question :
And you wouldn’t be able to use a comma inside that single f string to tell os.path.join to join what’s before and after the comma, because it would still just pick up the entire f string as the first thing to join and then look for a second thing and not find one so it wouldn’t work?

Answer :
That’s correct. Using a comma inside the f-string wouldn’t work for achieving the intended result with os.path.join. When you provide a comma-separated list of arguments to os.path.join, it treats each argument as a separate path component to join. If you include a comma inside an f-string, it would be treated as part of the f-string itself, and os.path.join would not recognize it as a separate path component to join. So, in this case, you would typically concatenate the components using string concatenation (e.g., f"{artist}/" + song) to ensure they are properly joined as path components.


I think (though I’m not positive)
That you could probably just use 1 f string to create a variable to use as the file path without using os.path.join but that would defeat the purpose of part of the lesson which is to show you how os.path.join works and what it does.

(So instead of

path = os.path.join(f"{artist}/", song)


We could just say :

path = f"{artist}/{song}"

But I haven’t actually tried this yet so it could be wrong.

Question :
Would the path that i mentioned work without using os.path.join?

Answer :

Yes, the path you mentioned without using os.path.join should work. Here’s the comparison between the two approaches:

  1. Using os.path.join:
path = os.path.join(f"{artist}/", song)
  1. Without os.path.join:
path = f"{artist}/{song}"

Both of these methods will produce a valid file path with the artist’s name as a directory and the song name as a file. The choice between these two approaches can depend on your coding style and the specific requirements of your project.

Using os.path.join is generally recommended when dealing with file paths because it ensures that the path is constructed in a platform-independent way, taking care of path separators and potential issues related to different operating systems (e.g., using "" on Windows and “/” on Unix-based systems).

However, if you are working in an environment where you know the file paths will always follow a specific format and separator (e.g., always using “/”), using the simpler f-string approach can be a valid and more concise alternative. Just make sure that your code’s assumptions align with the actual file path format used in your project.

1 Like

The solution makes use of os.path to write the songs to each artist folder but this method hasn’t been sufficiently covered in the tutorials to be understandable to students. Can someone provide a good explainer on how this is working and how we should know when to utilize this technique?

import csv, os

with open("100MostStreamedSongs.csv") as file:
  reader = csv.DictReader(file)
  
  for row in reader:
    dir = os.listdir()
    artist = row["Artist(s)"].title()
    print(artist)
    if artist not in dir:
      os.mkdir(artist)
    song = row["Song"]
    print(row["Song"])
    path = os.path.join(f"{artist}/", song)
    f = open(path, "w")
    f.close()