Personal archive tool, looking for suggestions on improving the code

M

mo reina

0 down vote favorite


i've written a tool in python where you enter a title, content, then
tags, and the entry is then saved in a pickle file. it was mainly
designed for copy-paste functionality (you spot a piece of code you
like on the net, copy it, and paste it into the program), not really
for handwritten content, though it does that with no problem.

i mainly did it because i'm always scanning through my pdf files,
books, or the net for some coding example of solution that i'd already
seen before, and it just seemed logical to have something where you
could just put the content in, give it a title and tags, and just look
it up whenever you needed to.

i realize there are sites online that handle this ex. http://snippets.dzone.com,
but i'm not always online when i code. i also admit that i didn't
really look to see if anyone had written a desktop app, the project
seemed like a fun thing to do so here i am.

it wasn't designed with millions of entries in mind, so i just use a
pickle file to serialize the data instead of one of the database APIs.
the query is also very basic, only title and tags and no ranking based
on the query.

there is an issue that i can't figure out, when you are at the list of
entries there's a try, except clause where it tries to catch a valid
index (integer). if you enter an inavlid integer, it will ask you to
enter a valid one, but it doesn't seem to be able to assign it to the
variable. if you enter a valid integer straightaway, there are no
problems and the entry will display.

anyway let me know what you guys think. this is coded for python3.

main file:

#!usr/bin/python

from archive_functions import Entry, choices, print_choice,
entry_query
import os

def main():
choice = ''
while choice != "5":
os.system('clear')
print("Mo's Archive, please select an option")
print('====================')
print('1. Enter an entry')
print('2. Lookup an entry')
print('3. Display all entries')
print('4. Delete an entry')
print('5. Quit')
print('====================')
choice = input(':')

if choice == "1":
entry = Entry()
entry.get_data()
entry.save_data()

elif choice == "2":
queryset = input('Enter title or tag query: ')
result = entry_query('entry.pickle', queryset)
if result:
print_choice(result, choices(result))
else:
os.system('clear')
print('No Match! Please try another query')
pause = input('\npress [Enter] to continue...')

elif choice == "3":
queryset = 'all'
result = entry_query('entry.pickle', queryset)
if result:
print_choice(result, choices(result))

elif choice == "4":
queryset = input('Enter title or tag query: ')
result = entry_query('entry.pickle', queryset)
if result:
entry = result[choices(result)]
entry.del_data()
else:
os.system('clear')
print('No Match! Please try another query')
pause = input('\npress [Enter] to continue...')

elif choice == "5":
break

else:
input('please enter a valid choice...')
main()

if __name__ == "__main__":
main()

archive_functions.py:

#!/bin/usr/python
import sys
import pickle
import os
import re

class Entry():
def get_data(self):
self.title = input('enter a title: ')
print('enter the code, press ctrl-d to end: ')
self.code = sys.stdin.readlines()
self.tags = input('enter tags: ')

def save_data(self):
with open('entry.pickle', 'ab') as f:
pickle.dump(self, f)

def del_data(self):
with open('entry.pickle', 'rb') as f:
data_list = []
while True:
try:
entry = pickle.load(f)
if self.title == entry.title:
continue
data_list.append(entry)
except:
break
with open('entry.pickle', 'wb') as f:
pass
with open('entry.pickle', 'ab') as f:
for data in data_list:
data.save_data()

def entry_query(file, queryset):
'''returns a list of objects matching the query'''
result = []
try:
with open(file, 'rb') as f:
entry = pickle.load(f)
os.system('clear')
if queryset == "all":
while True:
try:
result.append(entry)
entry = pickle.load(f)
except:
return result
break
while True:
try:
if re.search(queryset, entry.title) or
re.search(queryset, entry.tags):
result.append(entry)
entry = pickle.load(f)
else:
entry = pickle.load(f)
except:
return result
break
except:
print('no entries in file, please enter an entry first')
pause = input('\nPress [Enter] to continue...')

def choices(list_result):
'''takes a list of objects and returns the index of the selected
object'''
os.system('clear')
index = 0
for entry in list_result:
print('{}. {}'.format(index, entry.title))
index += 1
try:
choice = int(input('\nEnter choice: '))
return choice
except:
pause = input('\nplease enter a valid choice')
choices(list_result)


def print_choice(list_result, choice):
'''takes a list of objects and an index and displays the index of
the list'''
os.system('clear')
print('===================')
print(list_result[choice].title)
print('===================')
for line in list_result[choice].code:
print(line, end="")
print('\n\n')
back_to_choices(list_result)

def back_to_choices(list_result):
print('1. Back to entry list')
print('2. Back to Main Menu')
choice = input(':')
if choice == "1":
print_choice(list_result, choices(list_result))
elif choice == "2":
pass
else:
print('\nplease enter a valid choice')
back_to_choices(list_result)
 
P

Peter Otten

mo said:
i've written a tool in python where you enter a title, content, then
tags, and the entry is then saved in a pickle file. it was mainly
designed for copy-paste functionality (you spot a piece of code you
like on the net, copy it, and paste it into the program), not really
for handwritten content, though it does that with no problem.

i mainly did it because i'm always scanning through my pdf files,
books, or the net for some coding example of solution that i'd already
seen before, and it just seemed logical to have something where you
could just put the content in, give it a title and tags, and just look
it up whenever you needed to.

i realize there are sites online that handle this ex.
http://snippets.dzone.com, but i'm not always online when i code. i also
admit that i didn't really look to see if anyone had written a desktop
app, the project seemed like a fun thing to do so here i am.

it wasn't designed with millions of entries in mind, so i just use a
pickle file to serialize the data instead of one of the database APIs.
the query is also very basic, only title and tags and no ranking based
on the query.

there is an issue that i can't figure out, when you are at the list of
entries there's a try, except clause where it tries to catch a valid
index (integer). if you enter an inavlid integer, it will ask you to
enter a valid one, but it doesn't seem to be able to assign it to the
variable. if you enter a valid integer straightaway, there are no
problems and the entry will display.

anyway let me know what you guys think. this is coded for python3.
def choices(list_result):
'''takes a list of objects and returns the index of the selected
object'''
os.system('clear')
index = 0
for entry in list_result:
print('{}. {}'.format(index, entry.title))
index += 1
try:
choice = int(input('\nEnter choice: '))
return choice
except:
pause = input('\nplease enter a valid choice')
choices(list_result)

When the exception is triggered you call choices() recursively but discard
the result. Therefore you get Python's default, None, which is not a valid
index. Change the last line to

return choices(list_result)

for a minimal fix. However, I suggest that you use a while loop instead of
the recursion:

def choices(list_result):
while True:
os.system('clear')
for index, entry in enumerate(list_result):
print('{}. {}'.format(index, entry.title))
try:
choice = int(input('\nEnter choice: '))
if 0 <= choice < len(list_result):
return choice
except ValueError:
pass
input('\nplease enter a valid choice')

I've also added a test for the integer range and replaced the bare except
with a more specific one. I recommend that you never use bare excepts
because they can hide unexpected exceptions and lead to nasty bugs.

You should also remove the recursive call of main(). Its only effect is that
when you enter an invalid choice twice you will have to enter "5" twice to
really exit your script.

Peter
 
M

mo reina

When the exception is triggered you call choices() recursively but discard
the result. Therefore you get Python's default, None, which is not a valid
index. Change the last line to

return choices(list_result)

for a minimal fix. However, I suggest that you use a while loop instead of
the recursion:

def choices(list_result):
    while True:
        os.system('clear')
        for index, entry in enumerate(list_result):
            print('{}. {}'.format(index, entry.title))
        try:
            choice = int(input('\nEnter choice: '))
            if 0 <= choice < len(list_result):
                return choice
        except ValueError:
            pass
        input('\nplease enter a valid choice')

I've also added a test for the integer range and replaced the bare except
with a more specific one. I recommend that you never use bare excepts
because they can hide unexpected exceptions and lead to nasty bugs.

You should also remove the recursive call of main(). Its only effect is that
when you enter an invalid choice twice you will have to enter "5" twice to
really exit your script.

Peter

hi peter, i noticed the issue you mentioned but don't understand why
they happen.

for example, when the function is called in the case of an exception,
the variable choice is re-assigned to whatever the next input is, so
why is the default None assigned instead? and what' s the difference
between just calling the function again (the variable list_result
remains unchanged) and using a return statement?

i also don' t understand what happens when main is called recursively,
the variable choice should be re-assigned to whatever the next input
is, and yet it seems that in the first call, after an invalid choice,
it doesn't assign the input to the variable.

thanks for taking the time to post and review the code by the way, i
really appreciate it
 
P

Peter Otten

mo said:
hi peter, i noticed the issue you mentioned but don't understand why
they happen.

for example, when the function is called in the case of an exception,
the variable choice is re-assigned to whatever the next input is, so
why is the default None assigned instead? and what' s the difference
between just calling the function again (the variable list_result
remains unchanged) and using a return statement?

If you have a function

def f():
return 42

and just call it from another function

def g():
f()

the result of f() is evaluated but immediately discarded. If you want to use
it inside g() you have to assign it to a variable

def g():
x = f()
y = x * x
print y

and if you want to use it outside g() you can return it.

def g():
return f()

For recursion the same rules apply, only with the same function as f and g.
Here's a simple example for you to work out the program flow:
.... print "entering level", n
.... if n == 5:
.... print "limit reached"
.... print "exiting level", n
.... print "returning 42"
.... return 42
.... else:
.... print "recursing"
.... r1(n+1)
.... print "exiting level", n
.... print "(implicitly) returning None"
....entering level 0
recursing
entering level 1
recursing
entering level 2
recursing
entering level 3
recursing
entering level 4
recursing
entering level 5
limit reached
exiting level 5
returning 42
exiting level 4
(implicitly) returning None
exiting level 3
(implicitly) returning None
exiting level 2
(implicitly) returning None
exiting level 1
(implicitly) returning None
exiting level 0
(implicitly) returning None

Try to change r1() to return the value from the innermost call (i. e. 42) to
the outside world. Once you have understood what is going on you should be
able to see what's wrong with your program.

Peter
 
M

mo reina

If you have a function

def f():
    return 42

and just call it from another function

def g():
    f()

the result of f() is evaluated but immediately discarded. If you want to use
it inside g() you have to assign it to a variable

def g():
    x = f()
    y = x * x
    print y

and if you want to use it outside g() you can return it.

def g():
   return f()

For recursion the same rules apply, only with the same function as f and g.
Here's a simple example for you to work out the program flow:


...     print "entering level", n
...     if n == 5:
...             print "limit reached"
...             print "exiting level", n
...             print "returning 42"
...             return 42
...     else:
...             print "recursing"
...             r1(n+1)
...     print "exiting level", n
...     print "(implicitly) returning None"
...>>> r1(0)

entering level 0
recursing
entering level 1
recursing
entering level 2
recursing
entering level 3
recursing
entering level 4
recursing
entering level 5
limit reached
exiting level 5
returning 42
exiting level 4
(implicitly) returning None
exiting level 3
(implicitly) returning None
exiting level 2
(implicitly) returning None
exiting level 1
(implicitly) returning None
exiting level 0
(implicitly) returning None

Try to change r1() to return the value from the innermost call (i. e. 42) to
the outside world. Once you have understood what is going on you should be
able to see what's wrong with your program.

Peter

ok i think i understand, the variable from the second function call is
returned but not assigned to any variable. since the function was
called by another function and not the main function, it returns the
variable to the first function instead of the main function.
 
J

John Bokma

mo reina said:
i mainly did it because i'm always scanning through my pdf files,
books, or the net for some coding example of solution that i'd already
seen before, and it just seemed logical to have something where you
could just put the content in, give it a title and tags, and just look
it up whenever you needed to.

Ages ago I wrote something like this in Perl, but now I use a local
install of MediaWiki to keep notes, interesting links, code snippets,
etc. One of the advantages is that I can reach it from each computer
connected to my LAN, even virtual ones.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,055
Latest member
SlimSparkKetoACVReview

Latest Threads

Top