A timer to track time spent practicing Python I made this to record the time I spend practicing Python. I'll eventually add in functionality to visualize the data. I'd appreciate any feedback that will help me improve.

# timer.py - A timer to track time spent practicing python

from datetime import timedelta
from datetime import datetime as Datetime
# used json b/c I wanted to practice it
import json
import os

# dict with all python practice options
options = ["Code Review",
           "Project",
           "Puzzles (Project Euler, Finxter, kaggle)",
           "Courses, Books, or Videos",
           "Read Docs"]


# Prompts user what type of practice they would like to do. User enters int.
def practice_choice():
    print("Pick a form of practice:\n")

    # print each option in options then ask user to select
    num = 1
    for option in options:
        print(str(num) + ". " + option)
        num += 1
    selection = input("\nPick a number: ")

    return options[int(selection) - 1]


def timer(selection):
    # store notes about practice session
    notes = input("Enter notes... ")

    # start timing session and get start time
    input("Hit Enter to begin practice...")
    start_dt = Datetime.now()

    # loop to allow for pausing. infinite loop that breaks when user clicks enter to stop session.
    var = 1
    total_so_far = 0
    while var == 1:
        pause_or_stop = input("Hit Enter to stop or space to pause...")

        # when user hits enters space char
        if pause_or_stop == ' ':

            # gets time session was paused. Calculates time practicing so far. Stores for later
            # Allows user to pause multiple times
            pause_dt = Datetime.now()
            total_so_far += int((start_dt - pause_dt) / timedelta(seconds=1))  # used sec for easier testing. 

            # resume on enter key and overwrites old start time
            input("Hit Enter to resume")
            start_dt = Datetime.now()

        # breaks if enter key
        elif pause_or_stop == '':
            break

        # catch bad input
        else:
            print("Key not recognized")

    # get stop time and find total time in minutes
    end_dt = Datetime.now()
    total_mins = int((end_dt - start_dt) / timedelta(seconds=1)) + total_so_far  # used sec for easier testing. 

    # add value to data[selection] dict. value is a list of datetime info, noes, total_minutes
    data[selection].append({'day': Datetime.now().strftime("%A"),  # %b %d %Y %H:%M),
                            'date': Datetime.now().strftime("%m-%d-%y"),
                            'time': Datetime.now().strftime("%H:%M"),
                            'notes': notes,
                            'minutes': total_mins})

    # convert dict to JSON object
    with open(target_file, 'w') as json_file:
        json.dump(data, json_file, indent=4)


# prompt user for type of practice
choice = practice_choice()

# practice_log file with all practice sessions are stored
target_file = "practice_log.json"

# initialize data dict which will hold practice logs
data = {}

# creates json file if it doesn't exist
if not os.path.exists(target_file):
    # top level values of json are created corresponding to practice type
    for each_item in options:
        data[each_item] = []
    with open(target_file, 'w') as json_file:
        json.dump(data, json_file, indent=4)

# open json file and load into target_files
with open(target_file, 'r') as json_file:
    data = json.load(json_file)

# starts timer
timer(choice)
Ways to improve and redesign:

I'll start with listing inner issues (duplication, redundant or over-complicated conditional/algorithms etc). Then I'll provide you with a new Object-Oriented approach as a more manageable, flexible and reliable. 


options = [...]. "Practice options" is better designed as a constant with immutable items (tuple):

OPTIONS = ("Code Review",
        "Project",
        "Puzzles (Project Euler, Finxter, kaggle)",
        "Courses, Books, or Videos",
        "Read Docs")

practice_choice function:
Dragging num variable though the for loop is redundant and easily replaced with enumerate feature (with custom start= parameter):

print('\n'.join(f'{i}. {option}' for i, option in enumerate(self.OPTIONS, 1)))

timer function:
var variable here:

var = 1
total_so_far = 0
while var == 1:
    ...


is redundant and it's not reassigned anywhere. Simply go with:

while True:
    ...

total_so_far variable is better named as total_time
Datetime.now() is duplicated on constructing a dictionary of session info (notes, total minutes etc). Instead, use end_dt from the nearest statement end_dt = Datetime.now() as it's already contains the current datetime.
creating non-existent target file:
filling data dict with:

for each_item in options:
    data[each_item] = []


is easily substituted with dict comprehension:

data = {o: [] for o in self.OPTIONS}

saving data

with open(target_file, 'w') as json_file:
    json.dump(data, json_file, indent=4)


is extracted into a separate function/method with respective name




Now, the Object-Oriented PracticeTimer class with lot of benefits like:


class constants
encapsulated state/behavior
dealing with target file: initializing/saving
methods with specific responsibilities
easy to extend


Underlying Class diagram:





# timer.py - A timer to track time spent practicing python

from datetime import timedelta
from datetime import datetime as Datetime
import json
import os


class PracticeTimer:
    # practice options
    OPTIONS = ("Code Review",
               "Project",
               "Puzzles (Project Euler, Finxter, kaggle)",
               "Courses, Books, or Videos",
               "Read Docs")

    # practice_log file with all practice sessions are stored
    TARGET_FILE = "practice_log.json"

    def __init__(self):
        # creates json file if it doesn't exist
        if not os.path.exists(self.TARGET_FILE):
            # top level values of json are created corresponding to practice type
            data = {o: [] for o in self.OPTIONS}
            with open(self.TARGET_FILE, 'w') as json_file:
                json.dump(data, json_file, indent=4)

        # open json file and load session data
        with open(self.TARGET_FILE, 'r') as json_file:
            self._data = json.load(json_file)

        self._current_choice = None

    def practice_choice(self):
        """Prompts user to select practice item. User enters int."""
        print("Pick a form of practice:\n")

        # print options, then ask user to select
        print('\n'.join(f'{i}. {option}' for i, option in enumerate(self.OPTIONS, 1)))
        choice = input("\nPick a number: ")
        self._current_choice = self.OPTIONS[int(choice) - 1]
        return self._current_choice

    def _save_log(self):
        with open(self.TARGET_FILE, 'w') as json_file:
            json.dump(self._data, json_file, indent=4)

    def run(self):
        self.practice_choice()

        # store notes about practice session
        notes = input("Enter notes... ")

        # start timing session and get start time
        input("Hit Enter to begin practice...")
        start_dt = Datetime.now()

        # loop to allow for pausing. Infinite loop that breaks when user clicks enter to stop session.
        total_time = 0
        while True:
            pause_or_stop = input("Hit Enter to stop or space to pause...")

            # when user hits enters space char
            if pause_or_stop == ' ':

                # gets time session was paused. Calculates time practicing so far. Stores for later
                # Allows user to pause multiple times
                pause_dt = Datetime.now()
                total_time += int((start_dt - pause_dt) / timedelta(seconds=1))  # used sec for easier testing.

                # resume on enter key and overwrites old start time
                input("Hit Enter to resume")
                start_dt = Datetime.now()

            # breaks if enter key
            elif pause_or_stop == '':
                break
            # catch bad input
            else:
                print("Key not recognized")

        # get stop time and find total time in minutes
        end_dt = Datetime.now()
        total_mins = int((end_dt - start_dt) / timedelta(seconds=1)) + total_time  # used sec for easier testing.

        # add info to the current session (as a list of datetime info, notes, total_minutes)
        self._data[self._current_choice].append({'day': end_dt.strftime("%A"),  # %b %d %Y %H:%M),
                                                 'date': end_dt.strftime("%m-%d-%y"),
                                                 'time': end_dt.strftime("%H:%M"),
                                                 'notes': notes,
                                                 'minutes': total_mins})

        self._save_log()


if __name__ == '__main__':
    p_timer = PracticeTimer()
    p_timer.run()