How to reset callback_query value to receive multiple inputs in Python-Telegram-Bot - telegram

After the user clicks on one button, the callback_query.data is assigned a value and is no longer able to receive additional inputs and the while loop constantly runs with that value. I am not sure how to either reset the callback_query.data value or to request the user for an additional input.
I would like to receive multiple inputs from the user using InlineKeyboard and only continue to the run code when the user clicks on the done InlineKeyboardButton.
reply_markup = InlineKeyboardMarkup(button_list)
update.message.reply_text('Please choose the headers:', reply_markup=reply_markup)
query = update.callback_query
query_list = query.data.split(" ")
while 'done' not in query_list:
if query.data not in query_list:
query_list.append(update.callback_query.data)
else:
pass
query.answer()
query.edit_message_text(text="Your input has been updated!")
This line of code uses the Python-Telegram-Bot package.

By using ConversationHandler and a while loop, I am able to receive a dynamic number of inputs.
def select(update: Update, context: CallbackContext) -> int:
global reply_markup
reply_markup = InlineKeyboardMarkup(build_menu(button_list, n_cols=1))
update.message.reply_text('Please choose the headers:', reply_markup=reply_markup)
return SECOND
def display(update: Update, context: CallbackContext) -> int:
query = update.callback_query
global query_list
query_list = query.data.split(" ")
context.bot.send_message(chat_id=update.effective_chat.id, text=f"{query.data} is added as a column header")
query.answer()
query.edit_message_text(text="Select other headers or click Done to end the selection!", reply_markup=reply_markup)
return THIRD
def display_2(update: Update, context: CallbackContext) -> int:
query = update.callback_query
while 'done' not in query_list:
if query.data not in query_list and query.data != 'done':
query_list.append(query.data)
context.bot.send_message(chat_id=update.effective_chat.id, text=f"{query.data} is added as a column header")
elif query.data == 'done':
break
else:
text = f"{query.data} is already added as a column header, choose another header or click Done to end the selection!"
context.bot.send_message(chat_id=update.effective_chat.id, text=text)
del update.callback_query.data
return THIRD
query.edit_message_text(text="I have received your input! Type /workout to receive your workout for today!")
return ConversationHandler.END
As seen in display_2 the while loop will bring the user back to the THIRD state of the conversation, which is display_2. I am not sure whether there is a more efficient way in doing this but this achieves my intent.

Related

Flask Testing with SQLite DB, deleting not working

In order to test my flask application, I setup a local SQLite DB with create_engine. To delete, I have my route call TablesEtl().delete_record_by_id().
class TablesEtl:
def __init__(self) -> None:
self.engine = create_engine(get_database_engine_uri(), connect_args={"check_same_thread": False})
def get_session(self) -> Session:
session = Session(self.engine, autoflush=False)
return session
def delete_record_by_id(self, record_id: int, table: Base) -> None:
session = self.get_session()
session.query(table).filter(table.id == record_id).delete()
session.commit()
session.close()
def get_record_by_id(self, record_id: int, table: Base) -> dict:
session = self.get_session()
query: Base = Query(table, session=session).filter(table.id == record_id).first()
session.close()
if query is None:
return {}
info = {}
counting = 0
for field in query.__table__.columns.keys():
info[field.replace("_", "-")] = getattr(query, field)
counting += 1
return info
The test_delete is done by creating a record, deleting it through this route and then requesting to the "information" route, information about the record I've just created. It should return a 404, since the record doesn't exist.
The problem is that it returns 200 and the correct information about the record I've just created.
This didn't occured when I ran the tests with pytest alone, but now I'm running with pytest-xdist and I think the second request is coming before the record was actually deleted. I also noticed that a db-name.db-journal was created side by side with my original db-name.db.
def test_not_finding_model_info_after_delete(self) -> None:
model_id = create_model_on_client(self.client, self.authorization_header, "Model Name")
self.client.delete(API_DELETE_MODEL, headers=self.authorization_header, json={"model-id": model_id})
response = get_model(self.client, self.authorization_header, model_id)
self.assertEqual(404, response.status_code)
When I run it with some time.sleep it works, but I don't want to depend upon a sleep for it to work and when I run pytest a second time with --lf option it works, but I believe it's because there are less calls being made to the db. When I get all the records that were asked to be deleted and check against the db, they are in fact deleted. I believe it's something to do with this .db-journal.
Can someone shade a light to it? Another DB would be better for this problem?

How to make this function where I can give it an argument or not

So I made this very small function. it is a bonehead easy function but frankly borderline my capabilities.. Im learning. The function works as expected, but I would like to go further. I would like to make it so I can either give it an argument (a username) and just get the information for that single user, or default to reporting all users. is this possible w/o starting over from what I have so far?
I have just poked around and seen some examples but nothing that I can fit into my script. that I can understand at least.
import boto3
iam = boto3.client('iam')
def user_group():
for myusers in iam.list_users()['Users']:
Group = iam.list_groups_for_user(UserName=myusers['UserName'])
print("User: " + myusers['UserName'])
for groupName in Group['Groups']:
print("Group: " + groupName['GroupName'])
print("----------------------------")
user_group()
I would like to have the ability to run this script in two fashions.
1) add an argument(s) of 'username' so I can get the response for a particular user
2) default to getting response for all users if no argument is given.
This can be done by using an argument with a default value:
def user_group(user = None):
if user is None:
print("No user")
else:
print(user)
user_group()
user_group('some user')
prints
No user
some user
In your case you may want to write
def user_group(user = None):
users_to_list = iam.list_users()['Users'] if user is None else [user]
for myusers in user_to_list:
...

How to error check a function containing a SQL query with the placeholder %s

I am working on a project for school and run into a crossroad. I have a bunch of queries in a python script that a user can search when running the script. A couple of these function are using %s as a placeholder so that the user can enter a value.
However, I want to check that what they enter is actually in my database. For instance, if you ask a user for a movie genre, it should produce an error if they enter something that is not in my tables.
I have spent a few days trying to find a way to do it, with no luck. I am not the greatest with functions, and I get things mixed up some times.
To simplify things I copied one of my queries requiring user input for testing. I thought I had come up with a solution on how to error check, but no luck. I have pasted the code below. If you know what I am missing, your help would be much appreciated.
#!/usr/bin/python36
#Modules
############################
import psycopg2
import sys
import os
#Open Database
############################
global cursor
#def OpenDatabase():
try:
connection = psycopg2.connect(database='Blockbuster36', user='dbadmin')
cursor = connection.cursor()
except psycopg2.DatabaseError:
print("No connection to database.")
sys.exit(1)
#Functions
###########################
def Show_Tuples():
tuple = cursor.fetchone()
while tuple != None:
print(tuple)
tuple = cursor.fetchone()
#3) Display all films of a certain genre
def Qry3_Film_of_Genre(Genre_Choice):
Qry3 = '''select Film_Title, Genre_Name from Film
join Film_Genre
on Film.Film_ID = Film_Genre.Film_ID
join Genre
on Film_Genre.Genre_ID = Genre.Genre_ID
where Genre_Name = %s;'''
cursor.execute(Qry3, (Genre_Choice,))
Show_Tuples()
def Qry3_Error_Check(Genre_Choice):
try:
Qry3_ec = "select Genre_ID, Genre_Name from Genre where Genre_Name = %s;"
cursor.execute(Qry3_ec, (Genre_Choice,))
results = cursor.fetchone()
if results[0] > 0:
print()
Qry3_Film_of_Genre(Genre_Choice)
# else:
elif results[0] == None:
print("This Genre does not exist")
Genre_Choice = input("Please enter the Genre name to filter movies by: ")
except psycopg2.Error as query_issue:
print("Something wrong with the query", query_issue)
except Exception as unkown:
print("Error: Something else went wrong")
print("Here is the issue: ", unkown)
#Main Code
##########################
Genre_Choice = input("Please enter the Genre name to filter movies by: ")
Check_Genre = Qry3_Error_Check(Genre_Choice)
#print("Function Return: ", Check_Genre)
#print()
#print(Check_Genre)
#if Check_Genre == None:
# print("This Genre does not exist")
# Genre_Choice = input("Please enter the Genre name to filter movies by: ")
# Check_Genre = Qry3_Error_Check(Genre_Choice)
#elif Check_Genre != None:
# Qry3_Film_of_Genre(Genre_Choice)
#while Check_Genre != Genre_Choice:
# print("This Genre does not exist")
# Genre_Choice = input("Please enter the Genre name to filter movies by: ")
# Check_Genre = Qry3_Error_Check(Genre_Choice)
#if Check_Genre == None:
# Qry3_Film_of_Genre(Genre_Choice)
#Close Cursor and Database
###################################
cursor.close()
connection.close()
Essentially I want the error message to keep printing, along with entering another genre name, until a valid genre is entered. Right now it keeps saying it is wrong even if enter a valid genre name and get output with the Qry3_Error_Check function.
Once the user has entered a valid genre name, based on the error-checking function query, then the original query will appear.
I have made some progress, entering a valid genre now works. However, when entering an invalid genre name it jumps to the general except and prints the error "NoneType object is not subscriptable." Obviously, there are no rows that match, thus the NoneType error. However, it should re-prompt the user in the elif statement above. What do enter as an elif statement so that the user is re-prompted for a valid genre name?
Note: I commented out the bulk of my main code for now.
If you want to check specifically for SQL errors, you can have a separate except block for them. Also, its usually (but not always) a bad idea to use exceptions for control flow.
Something like below (I have not tested this) is probably close to what you need.
try:
Qry3_ec = "select Genre_ID, Genre_Name from Genre where Genre_Name = %s;"
cursor.execute(Qry3_ec, (Genre_Choice,))
results = cursor.fetchall()
if results.count > 0:
print(results)
else:
print("Nothing found!")
except psycopg2.Error as e:
print("Something wrong with the query")
print(e)
except Exception as e
print("Something else went wrong")
print(e)
Good luck with your project. :)

How to solve a tkinter memory leak?

I have a dynamic table with a fixed row number (like a FIFO Queue), which updates continuously through tkinter's after() function. Inside the table is a Button, which text is editable.
To make the Button's text editable I used the solution of BrenBarn and reference a loop variable into a function call at the command-attribute.
When the function update_content_items() is cycled, I found, that the memory usage is increasing MB by MB per second. I can confirm that after commenting out the lambda expression, the memory leak was gone. (as seen live running 'top' in the terminal)
It seems I have to use the lambda, otherwise the Button will have a wrong index and the user edits the wrong row, when I just used self.list_items[i], though the user clicked the right one.
Is there a way to solve the problem? How can the user click the right button and edit it while having the right index and getting rid of the leak?
The corresponding code:
def update_content_items(self):
"""
Continuously fills and updates the Table with rows and content.
The size of the table rows is initially fixed by an external value at config.ini
:return: nothing
"""
if len(self.list_items) > self.queueMaxlen:
self.queueMaxlen = len(self.list_items)
self.build_table()
try:
for i in range(len(self.list_items)):
item = self.list_items[i]
self.barcodeImgList[i].image = item.plateimage
orig_image = Image.open(io.BytesIO(item.plateimage))
ein_image = ImageTk.PhotoImage(orig_image)
self.barcodeImgList[i].configure(image=ein_image)
# keeps a reference, because somehow tkinter forgets it...??? Bug of my implementation???
self.barcodeImgList[i].image = ein_image
orig_image = None
ein_image = None
#FIXME Memory LEAK?
self.numberList[i].configure(text=item.number,
command=lambda K=i: self.edit_barcode(self.list_items[K]))
self.timestampList[i].configure(text=item.timestamp)
self.search_hitlist[i].config(bg='white', cursor="xterm")
self.search_hitlist[i].unbind("<Button-1>")
if item.queryresult is not None:
if item.queryresult.gesamtstatus != 'Gruen':
self.search_hitlist[i].insert(tk.END, item.queryresult.barcode +
'\n' + item.queryresult.permitlevel)
self.search_hitlist[i].configure(bg='red', cursor="hand2")
self.search_hitlist[i].bind("<Button-1>", item.url_callback)
else:
self.search_hitlist[i].configure(bg='green', cursor="xterm")
self.search_hitlist[i].configure(state=tk.DISABLED)
self.on_frame_configure(None)
self.canvas.after(10, self.update_content_items)
except IndexError as ie:
for number, thing in enumerate(self.list_items):
print(number, thing)
raise ie
def edit_barcode(self, item=None):
"""
Opens the number plate edit dialogue and updates the corresponding list item.
:param item: as Hit DAO
:return: nothing
"""
if item is not None:
new_item_number = EditBarcodeEntry(self.master.master, item)
if new_item_number.mynumber != 0:
item.number = new_item_number.mynumber
self.list_items.request_work(item, 'update')
self.list_items.edit_hititem_by_id(item)
self.parent.master.queryQueue.put(item)
else:
print("You shouldn't get here at all. Please see edit_barcode function.")
EDIT: It seems there is indeed a deeper memory leak (python itself). The images won't get garbage collected. Memory is slowly leaking in Python 3.x and I do use PIL. Also here: Image loading by file name memory leak is not properly fixed
What can I do, because I have to cycle through a list with records and update Labels with images? Is there a workaround? PhotoImage has no explicit close() function, and if I call del, the reference is gc'ed and no configuring of the Label possible.
an example of my proposed changes, with indentation fixed:
def update_content_items(self):
"""
Continuously fills and updates the Table with rows and content.
The size of the table rows is initially fixed by an external value at config.ini
:return: nothing
"""
if len(self.list_items) > self.queueMaxlen:
self.queueMaxlen = len(self.list_items)
self.build_table()
try:
for i in range(len(self.list_items)):
item = self.list_items[i]
self.barcodeImgList[i].image = item.plateimage
orig_image = Image.open(io.BytesIO(item.plateimage))
ein_image = ImageTk.PhotoImage(orig_image)
self.barcodeImgList[i].configure(image=ein_image)
# keeps a reference, because somehow tkinter forgets it...??? Bug of my implementation???
self.barcodeImgList[i].image = ein_image
orig_image = None
ein_image = None
self.numberList[i].configure(text=item.number) # removed lambda
self.numberList[i].bind("<Button-1>", self.edit_barcode_binding) # added binding
self.timestampList[i].configure(text=item.timestamp)
self.search_hitlist[i].config(bg='white', cursor="xterm")
self.search_hitlist[i].unbind("<Button-1>")
if item.queryresult is not None:
if item.queryresult.gesamtstatus != 'Gruen':
self.search_hitlist[i].insert(tk.END, item.queryresult.barcode +
'\n' + item.queryresult.permitlevel)
self.search_hitlist[i].configure(bg='red', cursor="hand2")
self.search_hitlist[i].bind("<Button-1>", item.url_callback)
else:
self.search_hitlist[i].configure(bg='green', cursor="xterm")
self.search_hitlist[i].configure(state=tk.DISABLED)
self.on_frame_configure(None)
self.canvas.after(10, self.update_content_items)
except IndexError as ie:
for number, thing in enumerate(self.list_items):
print(number, thing)
raise ie
def edit_barcode_binding(self, event): # new wrapper for binding
K = self.numberList.index(event.widget) # get index from list
self.edit_barcode(self.list_items[K]) # call the original function
def edit_barcode(self, item=None):
"""
Opens the number plate edit dialogue and updates the corresponding list item.
:param item: as Hit DAO
:return: nothing
"""
if item is not None:
new_item_number = EditBarcodeEntry(self.master.master, item)
if new_item_number.mynumber != 0:
item.number = new_item_number.mynumber
self.list_items.request_work(item, 'update')
self.list_items.edit_hititem_by_id(item)
self.parent.master.queryQueue.put(item)
else:
print("You shouldn't get here at all. Please see edit_barcode function.")

Plone - In an AutocompleteFieldWidget, how can I fix a glitch where (none) is still selected despite another selected?

I have a form.Form whose interface has a field that is a AutocompleteFieldWidget. (none) is displayed because the field is not required. There is a glitch where (none) will remain selected even if the user hits another radio button.
Parent = schema.Choice(title=u'Parent Object',
source=ParentSourceVocabSourceBinder(),
required=False,
default=None)
The field is bound to an object class acting as a vocabulary. The object has the required functions. The search function searches for records of a mapped table based on the query from the textbox the user enters information into and the __contains__, getTerm, getTermByToken functions search for the matching record they require.
class ParentSourceVocab(object):
implements(IQuerySource)
vocabulary = SimpleVocabulary([])
session = None
def __contains__(self, term):
"""If term is None, set self.vocabulary equal to empty vocabulary and return none
"""
"""Else make fill the self.vocabulary with the found result and return SimpleTerm with result from running query
"""
def getTerm(self, value):
"""If value is None, set self.vocabulary equal to empty vocabulary and return none
"""
"""Else make fill the self.vocabulary with the found result and return SimpleTerm with result from running query
"""
def getTermByToken(self, token):
"""If token is None, set self.vocabulary equal to empty vocabulary and return none
"""
"""Else make fill the self.vocabulary with the found result and return SimpleTerm with result from running query
"""
def search(self,query_string):
if len(query_string) > 0:
q_string = query_string.strip('"').strip("'")
simples =[]
searchResults="""Query function"""
for result in results:
concat_string = """concatenated string for title"""
simples.append(SimpleTerm(title=result.obj_name,value=obj_id,token=obj_id)
self.vocabulary = SimpleVocabulary(simples)
return simples
else:
self.vocabulary = SimpleVocabulary([])
return None
class ParentSourceVocabSourceBinder(object):
implements(IContextSourceBinder)
def __call__(self, context):
return ParentSourceVocab(context)
One thing I did notice is that if the user does click (none), the form will recognize the (none) value was clicked, but if a value from the search results was clicked, it will remain clicked, despite it knowing (none) was selected. Its almost as if the (none) and the search result radio buttons belong to two different "sets."
Am I misunderstanding how this widget works? Or is there something wrong with my javascript?

Resources