0

I want to use a .txt file to store API tokens for an application, but I got stuck trying to find a way to Replace the API key/token if it's found in the file. This is the code tried (Python 3.5):

data_to_save = {}
data_to_save['savetime'] = str(datetime.datetime.now())[:19]
data_to_save['api_key'] = key_submitted
data_to_save['user'] = uniqueid
api_exists = False
user_exists = False
with open("databases/api_keys.txt", 'r+') as f:
    database = json.loads(f.read())
    for i in database:
        if i['api_key'] == key_submitted:
            send_text_to_user(userid, "[b]Error: That API key is already in use.[/b]", "red")
            api_exists = True
        if i['user'] == uniqueid:
            user_exists = True
    if user_exists == True:
        if api_exists = True:
            send_text_to_user(userid, "[b]Error: Your API key was already saved at another time.[/b]", "red")
        else:
            f.write(json.dumps(data_to_save)) #Here, StackOverflow
            send_text_to_user(userid, "[b]Okay, I replaced your API key.[/b]", "green")
    f.close()
if user_exists == False:
    writing = open("databases/api_keys.txt", 'a')
    writing.write(json.dumps(data_to_save))
    writing.close()

I also want to know if this is the best way to do it or the code could be optimized and how.

Thank you, it has been done. Final code:

data_to_save = {'savetime': str(datetime.datetime.now())[:19], 'api_key': key_submitted, 'user': uniqueid}
with open("databases/api_keys.txt", 'r') as f:
    database = json.loads(f.read())
    for i in database:
        if i['user'] == uniqueid:
            database.remove(i)
        if i['api_key'] == key_submitted:
            send_text_to_user(userid, "[b]Error: That API key is already in use.[/b]", "red")
            api_exists = True
            break
    if not api_exists:
        database.append(data_to_save)
        f.write(json.dumps(database)
    send_text_to_user(userid, "[b]Okay, your API key was succesfully stored.[/b]")

With this approach we don't even need to write different saves just in case the user exists or doesn't exists because it deletes it if it's found, so it never exists when the code runs and it just need to save a "new" record every single time, except if the API key already belongs to another user.

Saelyth
  • 1,694
  • 2
  • 25
  • 42

1 Answers1

2

There are many issues with given code, so let's start from the beginning:

  1. We don't need to create empty dict object to fill it on the next lines

    data_to_save = {}
    data_to_save['savetime'] = str(datetime.datetime.now())[:19]
    data_to_save['api_key'] = key_submitted
    data_to_save['user'] = uniqueid
    

    when we can just create it filled like

    data_to_save = {'savetime': str(datetime.datetime.now())[:19],
                    'api_key': key_submitted,
                    'user': uniqueid}
    
  2. Assignments are not allowed in if statements (more at docs)

    if api_exists = True:
    

    so this line will cause SyntaxError (i guess this is a typo).

  3. Checks like

    if user_exists == True:
        ...
    

    are redundant, we can just write

    if user_exists:
        ...
    

    and have the same effect.

  4. We don't need to explicitly close file when use with statement, this is what context managers for: cleanup after exiting with statement block.

  5. Your databases/api_key.txt file after first iteration will have invalid JSON object, because you are simply writing new serialized data_to_save object at the end of the file, while you should modify database object (which seems to be a list of dictionaries) and write serialized new version of it, so we don't need r+ mode as well.

Let's define utility function which saves new API key data like

def save_database(database,
                  api_keys_file_path="databases/api_keys.txt"):
    with open(api_keys_file_path, 'w') as api_keys_file:
        api_keys_file.write(json.dumps(database))

then we can have something like

data_to_save = {'savetime': str(datetime.datetime.now())[:19],
                'api_key': key_submitted,
                'user': uniqueid}
api_exists = False
user_exists = False
with open("databases/api_keys.txt", 'r') as api_keys_file:
    database = json.loads(api_keys_file.read())
    # database object should be iterable,
    # containing dictionaries as elements,
    # so only possible solution -- it is a list of dictionaries
    for i in database:
        if i['api_key'] == key_submitted:
            send_text_to_user(userid, "[b]Error: That API key is already in use.[/b]", "red")
            api_exists = True
        if i['user'] == uniqueid:
            user_exists = True

    if user_exists:
        if api_exists:
            send_text_to_user(userid, "[b]Error: Your API key was already saved at another time.[/b]", "red")
        else:
            # looking for user record in database
            user_record = next(record for record in database
                               if record['user'] == uniqueid)
            # setting new API key
            user_record['api_key'] = key_submitted
            save_database(database)
            send_text_to_user(userid, "[b]Okay, I replaced your API key.[/b]", "green")

if not user_exists:
    database.append(data_to_save)
    save_database(database)

Example

I've created directory databases with api_keys.txt file which contains single line

[]

because at the beginning we have no API keys.

Let's assume our missed objects are defined like

key_submitted = '699aa2c2f9fc41f880d6ec79a9d55f29'
uniqueid = 3
userid = 42


def send_text_to_user(userid, msg, color):
    print(msg)

so with above code it gives me at first script execution empty output, and on the second one:

[b]Error: That API key is already in use.[/b]
[b]Error: Your API key was already saved at another time.[/b]

Further improvements

  • Should we break from for-loop if one of conditions (API key or user has been already registered in database) is satisfied?
  • Maybe it will be better to use already written database instead of reinventing the wheel? If you need JSON objects you should take a look at TinyDB.
Azat Ibrakov
  • 9,998
  • 9
  • 38
  • 50
  • I am pretty much learning python without tutorials with just trial and error, so that's why my code is so random sometimes. That said... **1:** You are right, and I knew about this one, but I guess I am too tired to see. **2:** Yeah, point 2 was totally a typo. **3:** Oh, that is nice. **4:** Hm, didn't know this one, but in essence I guess it gives the same result. **5:** That's clever. I just recreate the entire file and write it instead of trying to keep most of the file untouched and just overwrite the desired data. – Saelyth Jun 01 '17 at 18:04
  • On your suggested improvements: I wouldn't want to break the for loop because I want to make sure that different users don't share the same api key (it must be unique). Also the same for users, I don't want 2 different entries for each user, so I tried the for loop in order to flag that user or api key (or both) exists, and then do a different thing for each case. – Saelyth Jun 01 '17 at 18:08
  • @Saelyth: btw why you don't use [`sqlite`](https://www.sqlite.org/) or any other, already written, well-supported and stable database? – Azat Ibrakov Jun 01 '17 at 18:09
  • So your code so far helps me, but is not finished. It's missing the case "if an user already have an api key saved but it wants to replace it with a new api key". That's where I did put that comment `#Here, stackoverflow` and it was the very reason of me asking for help. – Saelyth Jun 01 '17 at 18:11
  • The person that asked me to do this doesn't want to use a proper database and refuses to install any software (which is why I didn't even tried sqlite or TinyDB, and I choose to use .txt files). – Saelyth Jun 01 '17 at 18:14
  • @Saelyth: fixed, added function to avoid boilerplate – Azat Ibrakov Jun 01 '17 at 18:16
  • @Saelyth: or you need to replace old API key with new one? – Azat Ibrakov Jun 01 '17 at 18:17
  • That's the point, to replace the old API key with the new one. I made it work locally now. And my approach is: If user is found but the api key is different than the one found for that user... it delete that item from the list of dictionaries and then write back the list of dictionaries+the new dictionary, which in essence is like overwriting that specific user dictionary entirely. – Saelyth Jun 01 '17 at 18:24
  • @Saelyth: fixed: added replacing old API key with new one – Azat Ibrakov Jun 01 '17 at 18:25
  • You, Sir, totally earned this. Thank you for the help :) – Saelyth Jun 01 '17 at 18:26
  • @Saelyth: you are welcome, you can also [**Vote Up**](https://stackoverflow.com/help/someone-answers) if i don't miss something – Azat Ibrakov Jun 01 '17 at 18:27