2

I have a script that I will run multiple times, and the first line in the main function of this script is something like this:

def main():
    users_csv_file_init()

What this function should do is to create a CSV file and append the columns' names, if such file doesn't exist, and if it does, it should do nothing. What I wrote was this:

def users_csv_file_init():
    with open("users.csv", 'a+') as output_file:
        writer = csv.writer(output_file)
        writer.writerow(["userid", "username", "fname", "lname", "uuid"])

This fails, because it creates a new row (the columns' names) every time I run the script, and it makes sense, because a+ creates the file if it doesn't exist and open it in append mode. But I can't change to w+ either, because that deletes everything that was previously in the file. And from the modes of open(), only w+ and a+ seem to create the file if it doesn't exist.

I could manually check if the file exists, like this:

def users_csv_file_init():
    if not os.path.exists("users.csv"):
        with open("users.csv", 'w') as output_file:
            writer = csv.writer(output_file)
            writer.writerow(["userid", "username", "fname", "lname", "uuid"])

But I'm told it's unpythonic and prone to race conditions to do it like this. How can I do it properly?

Amir Shabani
  • 3,857
  • 6
  • 30
  • 67
  • You will always get raise conditions unless you implement some lock. As far as I am concerned this is the best approach. – marcos Feb 13 '20 at 19:57
  • Does this answer your question? [How does using the try statement avoid a race condition?](https://stackoverflow.com/questions/14574518/how-does-using-the-try-statement-avoid-a-race-condition) – Toby Petty Feb 13 '20 at 20:02
  • 2
    You should accept Sayse's answer instead of mine. Assuming all the scripts that open the file do it that way, it avoids the race condition because exclusive mode is atomic. – Barmar Feb 13 '20 at 20:26

3 Answers3

3

You can just catch the FileExistsError and try opening the file with the exclusive creation mode.

open for exclusive creation, failing if the file already exists

def users_csv_file_init():
    try:
        with open("users.csv", 'x') as output_file:
            writer = csv.writer(output_file)
            writer.writerow(["userid", "username", "fname", "lname", "uuid"])
    except FileExistsError:
        pass
Sayse
  • 42,633
  • 14
  • 77
  • 146
  • @Barmar - It is if opened in exclusive creation mode - [`open` docs](https://docs.python.org/3/library/functions.html#open) – Sayse Feb 13 '20 at 19:57
  • Didn't see that you used `x`, did you edit the question while I was commenting? – Barmar Feb 13 '20 at 20:00
  • @Barmar - There was a slight race condition, yes lol. I was just double checking the open docs and updating when you commented – Sayse Feb 13 '20 at 20:00
3

Open the file in a mode, and then check the file position with tell(). If it's 0, the file was created and is empty, so you need to write the header line.

def users_csv_file_init():
    with open("users.csv", 'a') as output_file:
        if output_file.tell() == 0:
            writer = csv.writer(output_file)
            writer.writerow(["userid", "username", "fname", "lname", "uuid"])

BTW, there's no need to use a+ mode if you're only writing to the file. The + modes are used when you're going to read and write the same file object.

Barmar
  • 741,623
  • 53
  • 500
  • 612
1

Yes, It's easier to ask forgiveness than it is to get permission. So you should not check for the file and just try to access it and handle the exception.

Now back to your question.

You should try to read the file in read mode and if the file doesn't exist then handle that scenario in except block.

def users_csv_file_init():
    try:
        with open("ussers.csv", 'r') as output_file:
            pass  # do nothing
    except FileNotFoundError:
        # now open again in a+ mode and do your thing
        with open("users.csv", 'a+') as output_file:
            writer = csv.writer(output_file)
            writer.writerow(["userid", "username", "fname", "lname", "uuid"])
    finally:
        pass


users_csv_file_init()
gsb22
  • 2,112
  • 2
  • 10
  • 25
  • 1
    This has the same race condition as calling `os.path.exists()` – Barmar Feb 13 '20 at 19:58
  • Barmar is correct, the problem with using `exists` or this approach is that the file could be created *after* the function call but before the call to opening the file to write to – Sayse Feb 13 '20 at 20:04
  • @Sayse, I agree to the same point but my code doesn't have os.path.exists(). It's in someone else's comment. – gsb22 Feb 13 '20 at 20:06