Wrapping a To Do App with Poetry

tacoda

Ian Johnson

Posted on January 8, 2024

Wrapping a To Do App with Poetry

We are going to start with a simple todo cli application with a straight-forward implementation.
The source of this code comes from Geeks for Geeks.

Starting Code

import sys
import datetime


def help():
    sa = """Usage :-
$ ./todo add "todo item" # Add a new todo
$ ./todo ls          # Show remaining todos
$ ./todo del NUMBER  # Delete a todo
$ ./todo done NUMBER     # Complete a todo
$ ./todo help            # Show usage
$ ./todo report      # Statistics"""
    sys.stdout.buffer.write(sa.encode('utf8'))


def add(s):
    f = open('todo.txt', 'a')
    f.write(s)
    f.write("\n")
    f.close()
    s = '"'+s+'"'
    print(f"Added todo: {s}")


def ls():
    try:

        nec()
        l = len(d)
        k = l

        for i in d:
            sys.stdout.buffer.write(f"[{l}] {d[l]}".encode('utf8'))
            sys.stdout.buffer.write("\n".encode('utf8'))
            l = l-1

    except Exception as e:
        raise e


def deL(no):
    try:
        no = int(no)
        nec()
        with open("todo.txt", "r+") as f:
            lines = f.readlines()
            f.seek(0)
            for i in lines:
                if i.strip('\n') != d[no]:
                    f.write(i)
            f.truncate()
        print(f"Deleted todo #{no}")

    except Exception as e:
        print(f"Error: todo #{no} does not exist. Nothing deleted.")


def done(no):
    try:

        nec()
        no = int(no)
        f = open('done.txt', 'a')
        st = 'x '+str(datetime.datetime.today()).split()[0]+' '+d[no]
        f.write(st)
        f.write("\n")
        f.close()
        print(f"Marked todo #{no} as done.")

        with open("todo.txt", "r+") as f:
            lines = f.readlines()
            f.seek(0)
            for i in lines:
                if i.strip('\n') != d[no]:
                    f.write(i)
            f.truncate()
    except:
        print(f"Error: todo #{no} does not exist.")


def report():
    nec()
    try:

        nf = open('done.txt', 'r')
        c = 1
        for line in nf:
            line = line.strip('\n')
            don.update({c: line})
            c = c+1
        print(
            f'{str(datetime.datetime.today()).split()[0]} Pending : {len(d)} Completed : {len(don)}')
    except:
        print(
            f'{str(datetime.datetime.today()).split()[0]} Pending : {len(d)} Completed : {len(don)}')


def nec():
    try:
        f = open('todo.txt', 'r')
        c = 1
        for line in f:
            line = line.strip('\n')
            d.update({c: line})
            c = c+1
    except:
        sys.stdout.buffer.write("There are no pending todos!".encode('utf8'))


if __name__ == '__main__':
    try:
        d = {}
        don = {}
        args = sys.argv
        if(args[1] == 'del'):
            args[1] = 'deL'
        if(args[1] == 'add' and len(args[2:]) == 0):
            sys.stdout.buffer.write(
                "Error: Missing todo string. Nothing added!".encode('utf8'))

        elif(args[1] == 'done' and len(args[2:]) == 0):
            sys.stdout.buffer.write(
                "Error: Missing NUMBER for marking todo as done.".encode('utf8'))

        elif(args[1] == 'deL' and len(args[2:]) == 0):
            sys.stdout.buffer.write(
                "Error: Missing NUMBER for deleting todo.".encode('utf8'))
        else:
            globals()[args[1]](*args[2:])

    except Exception as e:

        s = """Usage :-
$ ./todo add "todo item" # Add a new todo
$ ./todo ls          # Show remaining todos
$ ./todo del NUMBER  # Delete a todo
$ ./todo done NUMBER     # Complete a todo
$ ./todo help            # Show usage
$ ./todo report      # Statistics"""
        sys.stdout.buffer.write(s.encode('utf8'))

Enter fullscreen mode Exit fullscreen mode

This app uses the name todo, but we will start with this file in todo.py to try to replicate a beginner's script. If you would like to use this simply as todo, you will have to add an interpreter shebang and make the file executable.

#!/usr/bin/env python3
Enter fullscreen mode Exit fullscreen mode
chmod a+x ./todo
Enter fullscreen mode Exit fullscreen mode

Feel free to do this if you would like, but we are going to take a simpler approach. Instead, we will move forward with todo.py.

Make this a Poetry project

To turn this into a poetry project, we need to move this from a file to a directory.

First we make the directory.

mkdir todo-app
Enter fullscreen mode Exit fullscreen mode

Then we move our python file in the new directory.

mv todo.py todo-app/todo.py
Enter fullscreen mode Exit fullscreen mode

Now we navigate to the directory.

cd todo-app
Enter fullscreen mode Exit fullscreen mode

Initialize the new poetry project.

poetry init

# This command will guide you through creating your pyproject.toml config.

# Package name [todo-app]:  
# Version [0.1.0]:  
# Description []:  
# Author [Ian Johnson <tacoda@hey.com>, n to skip]:  
# License []:  
# Compatible Python versions [^3.12]:  

# Would you like to define your main dependencies interactively? (yes/no) [yes] no
# Would you like to define your development dependencies interactively? (yes/no) [yes] no
# Generated file

# [tool.poetry]
# name = "todo-app"
# version = "0.1.0"
# description = ""
# authors = ["Ian Johnson <tacoda@hey.com>"]
# readme = "README.md"

# [tool.poetry.dependencies]
# python = "^3.12"


# [build-system]
# requires = ["poetry-core"]
# build-backend = "poetry.core.masonry.api"


# Do you confirm generation? (yes/no) [yes]
Enter fullscreen mode Exit fullscreen mode

Here, I accept most of the defaults. Enter or Return will accept the default for each of these prompts. The default value is given in square brackets at the end of a prompty. I specifically chose not to define my dependencies interatively to work through it.

Run the Project

We can run commands in the virtual environment set up by poetry on a one-by-one basis by using the run subcommand.

poetry run python todo.py            
# Creating virtualenv todo-app-7hL1Y5Tc-py3.12 in /Users/ianjohnson/Library/Caches/pypoetry/virtualenvs
# Usage :-
# $ ./todo add "todo item" # Add a new todo
# $ ./todo ls            # Show remaining todos
# $ ./todo del NUMBER    # Delete a todo
# $ ./todo done NUMBER   # Complete a todo
# $ ./todo help          # Show usage
# $ ./todo report        # Statistics
Enter fullscreen mode Exit fullscreen mode

Run a Shell

In addition to running commands one at a time, we can enter a virual environment shell to interact with our project in a more familiar way.

poetry shell
# Spawning shell within /Users/ianjohnson/Library/Caches/pypoetry/virtualenvs/todo-app-7hL1Y5Tc-py3.12

python todo.py
# Usage :-
# $ ./todo add "todo item" # Add a new todo
# $ ./todo ls            # Show remaining todos
# $ ./todo del NUMBER    # Delete a todo
# $ ./todo done NUMBER   # Complete a todo
# $ ./todo help          # Show usage
# $ ./todo report        # Statistics
Enter fullscreen mode Exit fullscreen mode

Great! We have now wrapped our script inside Poetry. In addition to our todo.py script, we now have a pyproject.toml file that was generated by Poetry.

Key Takeaways

  • Wrap Python scripts with Poetry using init
  • Poetry uses a pyproject.toml file
  • Run commands using run
  • Enter a virtual environment shell using shell
💖 đŸ’Ē 🙅 🚩
tacoda
Ian Johnson

Posted on January 8, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related