1

I wrote a script that performs some operations on a MongoDB database. Like inserts, queries, listing dbs and collections.

The function I created that selects a database works fine if I run it from another script. But crashes with this error if I run the script stand alone:

Traceback (most recent call last):
  File ".\ec2_mongo.py", line 212, in <module>
    main()
  File ".\ec2_mongo.py", line 194, in main
    mongo_select_all()
  File ".\ec2_mongo.py", line 113, in mongo_select_all
    mydb, instance_col = set_db()
  File ".\ec2_mongo.py", line 62, in set_db
    instance_col = mydb[instance_col]
TypeError: string indices must be integers

What confuses me is that the lines that works when the script is called from another one, crashes when the script runs on it's own.

These are the lines in question:

instance_col = 'ec2_list-' + today
instance_col = mydb[instance_col]

Here is the function I wrote that only crashes when the script is run on it's own:

def connect_db():
    try:
        myclient = MongoClient(
                host = "mongodb://localhost:27017/",
                serverSelectionTimeoutMS = 3000 # 3 second timeout
            )
    except errors.ServerSelectionTimeoutError as e:
        # set the client instance to 'None' if exception
        myclient = None
        # catch pymongo.errors.ServerSelectionTimeoutError
        print ("pymongo ERROR:", e)
    return myclient

def set_db():
    myclient = connect_db()
    today = datetime.today()
    today = today.strftime("%m-%d-%Y")
    if __name__ == '__main__':
        message = "Select Database"
        banner(message)
        print(Fore.CYAN + "Available MongoDB Databases:")
        if myclient != None:
            # the list_database_names() method returns a list of strings
            database_names = myclient.list_database_names()
            counter = 1
            for db in database_names:
                message = str(counter) + '. ' + db
                print(message)
                counter = counter + 1
        print ("There are", len(database_names), "databases.\n")
        print(f"Please select a database. Enter a number 1 through {len(database_names)}.")
        choice = input("Enter a number: ")
        if is_digit(choice) == True:
            if int(choice) > counter:
                print("Wrong selection.")
                set_db()
            choice = int(choice)
            choice = choice - 1
            mydb = database_names[choice]
            print(f"You've selected: {mydb}\n")
        else:
            print("Must enter a digit. Try again.\n")
            set_db()
        ## Run as a stand alone script
        instance_col = 'ec2_list-' + today
        print(f"***Instance col type: {type(instance_col)}***")
        instance_col = mydb[instance_col]
        print(f"Type MYDB: {mydb}")
        time.sleep(10)
    else:
        # Called from
        mydb = myclient['aws_ec2_list']
        print(f"Type MYDB: {mydb}")
        time.sleep(10)
        instance_col = 'ec2_list-' + today
        print(f"***Instance col type: {type(instance_col)}***")
        time.sleep(30)
        instance_col = mydb[instance_col]
    return mydb, instance_col
bluethundr
  • 1,005
  • 17
  • 68
  • 141

2 Answers2

2

The reason @npwhite listed is correct.

Probably a quick fix:

Just change:

mydb = database_names[choice]

to:

mydb = myclient[database_names[choice]]
Panwen Wang
  • 3,573
  • 1
  • 18
  • 39
1

The reason the behaviour is different depending on whether you run the script directly or import it is because of the if __name__ == '__main__': condition. When you run the module directly, __name__ will be equal to "__main__", however if the module is imported, __name__ will not equal "__main__". This answer explains in more detail.

The exception is being thrown because mydb has type str, and you are trying to index the string with another string which is invalid.

In the if __name__ == '__main__' branch, mydb is a string since its assigned the result of database_names[choice], where database_names is a list of strings, so mydb will be a string too.

In the else branch, mydb is assigned the result of myclient['aws_ec2_list'], which is the dictionary-style way of getting a database instance from a mongo client instance shown here, so mydb will be a database instance.

npwhite
  • 36
  • 4
  • OK yes, thanks I've got that about the `if __name__ == '__main__:` condition already. What I want to know is why `mydb` becomes a string when run under `__main__` and it a mongodb object when the script is imported. How would I correct that? – bluethundr Jul 15 '20 at 21:50
  • @bluethundr My edit might clear things up a bit more. – npwhite Jul 15 '20 at 22:22
  • why does `mydb` only become a `str` type if I use it in the `if __name__ == '__main__':` block? That only happened when I introduced that if/then statement. If I remove it, and run it the way I did before I introduced the if/then it works. Real curious why that's happening. – bluethundr Jul 16 '20 at 11:02
  • This version of my script works whether it's a stand alone or on import. https://pastebin.com/nXeS4LSu It's when I introduce the `if __name__ == '__main__':` statement that it blows up! – bluethundr Jul 16 '20 at 11:48
  • The pastbin version works because you assign mydb twice, the first as a string and the second as the db instance. Since the db instance overwrites the `str`, if effectively cancels out your error. `mydb` is becoming a `str` because you are assigning it to be a string, `database_names[choice]` will yield a `str`, `myclient['aws_ec2_list']` will yield a db instance. – npwhite Jul 16 '20 at 20:42
  • OK thanks for that explanation. I was able to fix this by using this statement to do the assignment: `mydb = myclient[database_names[choice]]` – bluethundr Jul 16 '20 at 20:51