1

I am currently trying to make a command line todo manager that will allow the user to input a task(s), remove it and list the task(s) out. From what I tried visualizing it didn't do as I thought it would, it's my first time using sqlite3.

What I am trying to achieve:

  • Storing the task(s) in the database which will automatically add an incrementing ID to it.

Example:

python todo.py -add do the laundry on Sunday

[in the database]
Id   Task
1    do the laundry on Sunday

My code.

import sqlite3
import argparse


def parse_args():
    desc = 'Todo manager for storing and removing tasks'
    parser = argparse.ArgumentParser(description=desc)
    parser.add_argument("-a", "--add", "-add", help="To add a new item to the list",
                        type=str, nargs="+")
    parser.add_argument("-r", "-remove", "--remove", help="To remove an item from the list",
                        type=int)
    parser.add_argument("-l", "-list", "--list", help="displays the tasks or task in the list",
                        nargs="*")
    args = parser.parse_args()
    return args


@staticmethod
def dict_factory(cursor, row):
    d = {}
    for idx, col in enumerate(cursor.description):
        d[col[0]] = row[idx]
    return d


def get_todo_list():
    database_connection.row_factory = dict_factory
    cursor = database_connection.cursor()
    cursor.execute("select rowid, * FROM todo_list")
    return cursor.fetchall()


def add_to_todo_list(num,task):
    cursor = database_connection.cursor()
    cursor.execute("INSERT INTO todo_list VALUES (?)", (str(task),))
    database_connection.commit()


def remove_from_todo_list(rowid):
    cursor = database_connection.cursor()
    cursor.execute("DELETE FROM todo_list WHERE rowid = ?", (rowid,))
    database_connection.commit()


if __name__ == '__main__':
    commands = parse_args()
    # Creating table for database using sqlite
    database_connection = sqlite3.connect('todo_list.db')
    cursor = database_connection.cursor()
    cursor.execute('''CREATE TABLE if not exists todo_list(
                  description TEXT);''')
    database_connection.commit()

    if commands.add:
        # Stops accepting tasks when there is a blank task as input. 
        if not commands.add == ' ':
           add_to_todo_list(commands.add)
    elif commands.remove:
        remove_from_todo_list(commands.remove)
    elif commands.list:
        get_todo_list()

However, my database is not accepting any values when I am trying to store data. By putting Id as Id INTEGER PRIMARY KEY when creating the table i.e.

cursor.execute('''CREATE TABLE if not exists todo_list(
                      Id INTEGER PRIMARY KEY
                      description TEXT);''')

Will the Id increment as I add data to the database?

penguin
  • 628
  • 2
  • 10
  • 22
  • Try `cursor.execute("INSERT INTO todo_list VALUES (?,?)", (int(num), task,))`. Your inputs from `argparse` are coming in as `str`s, yet you defined your column `ID` as an `INTEGER` in your db. – felipe Dec 14 '19 at 16:09
  • @FelipeFaria Yes, thats a good point! I tried taking away the `num` variable because i realize for each space in the task, itll be a separate thing for the for loop. Ill go edit my code in the post. However, the database doesn't seem to change, as I tried fetching the data in it. – penguin Dec 14 '19 at 16:17
  • I'll make a reply and edit it as you edit your post so we don't go back and forth here. Shoot me an update comment when you update the post – felipe Dec 14 '19 at 16:22

1 Answers1

2

sqlite3.InterfaceError: Error binding parameter 1 - probably unsupported type.

Your inputs from argparse are coming in as str, yet you defined your column ID as an INTEGER in your db. Fix is:

cursor.execute("INSERT INTO todo_list VALUES (?,?)", (int(num), task,))

Storing the task(s) in the database which will automatically add an incrementing ID to it.

According to the sqlite docs here, defining a INTEGER PRIMARYKEY will auto-increment. Simply pass a null value to it, and sqlite takes care of the rest for you.


You have a few issues on your code when it comes to displaying and adding the tasks. First, initializing the DB:

cursor.execute(
        """CREATE TABLE if not exists todo_list(
                      id INTEGER PRIMARY KEY,
                      description TEXT);"""
    )

How you had it before your post edit was fine. Then, the add_to_todo_list:

def add_to_todo_list(task):
    cursor = database_connection.cursor()
    cursor.execute("INSERT INTO todo_list VALUES (?,?)", (None, str(task)))
    database_connection.commit()

Notice the removal of num from the functions input, and the passing of None for the column ID. Within the get_todo_list() you can fetch it more easily as so:

def get_todo_list():
    cursor = database_connection.cursor()
    cursor.execute("select * FROM todo_list")
    return cursor.fetchall()

A fix is also needed in the way you parse your args; for commands.list you need to do the following:

elif commands.list is not None:
    print(get_todo_list())

This is since commands.list will be a [] when you do app.py -list, which Python evaluates to False (empty lists are falsey). You also ought to print the contents of the function to terminal -- so don't forget that. With the edits above I can do on my terminal:

python test.py -add Random Task!
python test.py -add Hello World!

python test.py -list
[(1, "['Random', 'Task!']"), (2, "['Hello', 'World!']")]
felipe
  • 7,324
  • 2
  • 28
  • 37
  • I apologize for that, I edited my post a bit and you're correct on the typing error. My database doesn't seem to be accepting any values though when I tried inputting via command line and then listing it out. – penguin Dec 14 '19 at 16:31
  • 1
    it does work! that's an interesting point for the `commands.list`. I have a question though, will it always appear as a dictionary back to the user when using the `fetchall()` function? – penguin Dec 14 '19 at 16:51
  • 1
    Well, it returns a `list` of `tuples` with the content of the table, not a `dict`. You can parse those results very easily using `\n`, [`" ".join(...)`](https://stackoverflow.com/a/5618910/1305461) and `for-loops`. Erase the `print()` from the `print(get_todo_list())` and instead `print` from the `get_todo_list()` function within a loop that traverses the `.fetchall()` results. – felipe Dec 14 '19 at 16:55
  • Modified post to add change I did to `get_todo_list()` I did not add previously. – felipe Dec 14 '19 at 16:56
  • Oh, so that's how it is. I really thought there was another function to return a string of data instead of a tuple. Yes, I was just thinking of using those to join em together to receive a string. I will give it a shot and see if I can join em using `" ".join()`. – penguin Dec 14 '19 at 17:22
  • Yeah -- when it comes to pretty printing the data you format it through Python. The db is only there to store data. Glad this was of help! Good luck with the rest! – felipe Dec 14 '19 at 17:42
  • 1
    Also, it should be mentioned: The reason why you are seeing `"['Random', 'Task!']"` as oppose to simply `Random Task!` in the db is because of `argparse`. If you add in the very first line of the `add_to_todo_list()` function `task = " ".join(task)`, you will see it will become `Random Task!` in the database next time you do `-add`. – felipe Dec 14 '19 at 17:46
  • oh this is an unforgettable learning experience, I just realized doesn't `argparse` take input as a list initially? This makes a lot of sense, when you basically just combine em before continuing onward. Oh wow, much appreciated Felipe! food for thought! – penguin Dec 14 '19 at 17:58
  • 1
    Yes, `argparse` takes inputs as a list. That actually allows you to do some funky things with it, like [creating a `git` like structure](https://medium.com/@FelipeFaria/writing-a-command-line-interface-similar-to-git-in-python-1cabbb3a3e6e). I'm happy to hear you found this helpful! I wish you luck on your learning adventure. :) – felipe Dec 14 '19 at 19:52