calling a procedure when a button is pressed - position

i thought this would be easy but here i am!!
I was to call a procedure when the button is pressed and display the result on a label
class DSFRSapp(App):
def build(self):
self.root = FloatLayout()
i = Image(source='DSFRSLogo.png',
allow_stretch=True,
pos_hint = ({'center_x':0.5, 'y': .25}))
spinner = Spinner(
text='Pick a Station',
values=('Appledore','Axminster','Bampton','Barnstaple','Bere Alston','Bideford','Bovey Tracey','Braunton','Bridgwater','Brixham','Buckfastleigh','Budleigh Salterton','Burnham on sea','Camels Head','Castle Cary','Chagford','Chard','Cheddar','Chulmleigh','Colyton','Combe Martin','Crediton','Crewkerne','Crownhill','Cullompton','Dartmouth','Dawlish','Exeter Danes Castle','Exeter Middlemoor','Exmouth','Frome','Glastonbury','Greenbank','Hartland','Hatherleigh','Holsworthy','Honiton','Ilfracombe','Ilminster','Ivybridge','Kingsbridge','Kingston','Lundy Island','Lynton','Martock','Minehead','Modbury','Moretonhampstead','Nether Stowey','Newton Abbot','North Tawton','Okehampton','Ottery St Mary','Paignton','Plympton','Plymstock','Porlock','Princetown','Salcombe','Seaton','Shepton Mallet','Sidmouth','Somerton','South Molton','Street','Taunton','Tavistock','Teignmouth','Tiverton','Topsham','Torquay','Torrington','Totnes','USAR','Wellington','Wells','Williton','Wincanton','Witheridge','Wiveliscombe','Woolacombe','Yelverton','Yeovil'),
size_hint=(None, None),
size=(150, 44),
pos_hint = ({'center_x':0.5, 'y': 0.35}))
b = Button(text="Search For Incidents",size_hint=(None, None),
pos_hint =({'center_x':0.5, 'y': 0.25}),
size=(150, 44))
LblRes = Label(text="Results will display here",
pos_hint =({'center_x':0.5, 'y': 0.15}),
size_hint=(600,100),color=(1,1,1,1),font_size=35)
b.bind(on_press=FindIncident(Spinner.text))
self.root.add_widget(spinner)
self.root.add_widget(LblRes)
self.root.add_widget(i)
self.root.add_widget(b)
return
def FindIncident( sStation ):
webpage = request.urlopen("http://www.dsfire.gov.uk/News/Newsdesk/IncidentsPast7days.cfm?siteCategoryId=3&T1ID=26&T2ID=35")#main page
soup = BeautifulSoup(webpage)
incidents = soup.find(id="CollapsiblePanel1") #gets todays incidents panel
Links = [] #create list call Links
for line in incidents.find_all('a'): #get all hyperlinks
Links.append("http://www.dsfire.gov.uk/News/Newsdesk/"+line.get('href')) #loads links into Links list while making them full links
n = 0
e = len(Links)
if e == n: #if no links available no need to continue
print("No Incidents Found Please Try Later")
sys.exit(0)
sFound = False
while n < e: #loop through links to find station
if sFound: #if the station has been found stop looking
sys.exit(0)
webpage = request.urlopen(Links[n]) #opens link in list)
soup = BeautifulSoup(webpage) #loads webpage
if soup.find_all('p', text=re.compile(r'{}'.format(sStation))) == []:#check if returned value is found
#do nothing leaving blank gave error
a = "1" #this is pointless but stops the error
else:
print(soup.find_all('p', text=re.compile(r'{}'.format(sStation)))) #output result
WebLink = Links[n]
sFound = True # to avoid un needed goes through the loop process
n=n+1 # moves counter to next in list
if not sFound: #after looping process if nothing has been found output nothing found
print("nothing found please try again later")
return;
if __name__ =="__main__":
DSFRSapp().run()
so when the button is pressed to call FindIncident using the spinner text as the string. That way it can search for the station and instead of print i will put the results in the label, possibly with a link to the website too.
any help would be great!
Raif

b.bind(on_press=FindIncident(Spinner.text))
You need to pass a function as the argument to bind. You aren't passing FindIncident, you're calling it and passing the result...which is None.
Try
from functools import partial
b.bind(on_press=partial(FindIncident, spinner.text))
Also declare FindIncident as def FindIncident( sStation, *args ):, as bind will automatically pass extra arguments. You could also do the same thing with a lambda function.
Be careful with your code cases as well - at the moment you use Spinner.text, when you probably mean spinner.text, as you created a variable spinner.

Related

How to use a list from a function into another function

I'm making a GUI program that there are two buttons. When I click the first button, I want to generating a random code. When I click the second button, I want to see a list of the codes that generated from the first button. (For now, just to print the codes-list, into the 'Run' window.) My script is the following:
class Ui_Main_Window(object):
# For the first button:
def Code_maker(self):
import random
Codes_list = []
chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!$%^&*`#/\?[]{}-_~"
for x in range(1):
password = ""
for y in range(0, 10):
password_char = random.choice(chars)
password += password_char
print(password)
Codes_list.append(password)
# For the second button:
def show_list(self):
print (Codes_list)
# To connect the actions with the buttons:
def setupUi(self, Main_Window):
# After some code...
self.Button_New_code.clicked.connect(self.Code_maker)
# After some code...
self.Button_Show_list.clicked.connect(self.show_list)
# The continuation of the program...
My problem is that when I click the first button, the code genarating successfully and it printing into the 'Run' window. But when I click the second button the program crashes and after some seconds, it closes. I get this error:
> Unresolved reference 'Codes_list'.
(I use Pycharm and the error doesn't showing into the 'Run' window, but into the 'Problems' window.)
I tried to repair it with a global list, but the program works worse. It crashes when I click anything of the two buttons... If I write code with this way, it becomes:
class Ui_Main_Window(object):
Codes_list = []
# For the first button:
def Code_maker(self):
import random
global Codes_list
chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!$%^&*`#/\?[]{}-_~"
for x in range(1):
password = ""
for y in range(0, 10):
password_char = random.choice(chars)
password += password_char
print(password)
Codes_list.append(password)
# For the second button:
def show_list(self):
print (Codes_list)
# To connect the actions with the buttons:
def setupUi(self, Main_Window):
# After some code...
self.Button_New_code.clicked.connect(self.Code_maker)
# After some code...
self.Button_Show_list.clicked.connect(self.show_list)
I searched about my problem in Google, but I tried so many things, that I don't remember what I tried. Nothing worked.
This should work. I have added comments on the lines I have added or modified for your reference. Can you please check?
class Ui_Main_Window(object):
def __init__(self):
# added the constructor to attach the list with the object itself
self.Codes_list = [] #this will contain the numbers
# For the first button:
def Code_maker(self):
import random
#Codes_list = [] # Removed
chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!$%^&*`#/\?[]{}-_~"
for x in range(1):
password = ""
for y in range(0, 10):
password_char = random.choice(chars)
password += password_char
print(password)
self.Codes_list.append(password) # modified to update the object attribute
# For the second button:
def show_list(self):
print (self.Codes_list) # modified to access the object attribute
# To connect the actions with the buttons:
def setupUi(self, Main_Window):
# After some code...
self.Button_New_code.clicked.connect(self.Code_maker)
# After some code...
self.Button_Show_list.clicked.connect(self.show_list)
# The continuation of the program...

Scraper cannot run suddenly and show "driver is not defined"

I am running a scraper that get the data from Nowgoal. It was running fine till this morning. I run it again without any changes in the program, it showed me an error "driver is not defined".
However, I have already defined as follows:
options = webdriver.ChromeOptions() #initialise webdriver options
driver = webdriver.Chrome(resource_path('./drivers/chromedriver.exe'),options=options)
I am not sure what exactly is the problem, the error directed to the last line of the program where I quit the driver as follow:
driver.quit()
It happened a few times, I closed all the IDLE and opened it again, it worked. But now, no matter what it got me the same error.
Below is the detail code and the Link_output file is here to get the main URLs.
#######Main script start here - open the file where the urls has been stored-make sure Link_output is in same folder as script running######
read_workbook = load_workbook(filename="Link_output.xlsx")
read_sheet = read_workbook.active
for row in range(2,read_sheet.max_row+1):
for column in "BDG": #Here you can add or reduce the columns
cell_name = "{}{}".format(column, row)
main_urls.append(read_sheet[cell_name].value)
#we have URL ready
print('Urls we are going to scrape : ' ,main_urls)
#filter out the dictionary based on bookmakers entered in config file- we will have the bookmakers available in my_dictionay_button
wanted_bookmaker_lst = bkmaker_param.split(',')
for maker in wanted_bookmaker_lst:
for k,v in main_dictionary_button.items():
if k.startswith(maker):my_dictionary_button[k]=v
#now loop through each URL
for file_key ,main_url in enumerate(main_urls):
#start the new workbook for each new URL
workbook = Workbook()
#Error flag clear - first time action flag also cleared
i=0
error_flag =0
file_key += 1
#Main url -print here-every third value is the link
if file_key % 3 == 0:
print(main_url)
# first we will enter into main_url - urls generally open with Crown tab - so we will click through each button of the bookmakers
for bookmaker ,odds_url_button in my_dictionary_button.items():
if i == 0 and error_flag == 0 :#first time action
#start the driver for the first time
driver = webdriver.Chrome(resource_path('./drivers/chromedriver.exe'),options=options)
#driver = webdriver.Chrome(executable_path = driver_path,options=options )
try:
driver.get(main_url) #Get the main url
except TimeoutException:
driver.get(main_url) #in case of timeout error - try again
time.sleep(5)
try:
driver.find_element_by_xpath(odds_url_button).click() #click on the first bookmaker button
driver.switch_to.window(driver.window_handles[0]) #in case any pop up is opening due to any reason - swtich to main window
except NoSuchElementException: #In case button is not found
print('Button not found')
lst_of_reattempt.append(driver.current_url) #Get the current url for which we were not able to find the button
saved_button_for_halftime = odds_url_button #save the button for later reattempt
driver.quit()
i+=1 #Firt time actions are over
error_flag == 1 #initialise the error count
continue
i+=1
elif error_flag == 1: #if previous one went into error
if odds_url_button == '//*[#id="htBtn"]/a': #In case the error happened while clicking on half time button
half_time = 1
revised_url = get_url_from_button(saved_button_for_halftime,main_url,half_time)# Get the revised url
userAgent = ua.random #change user agent everytime browser went into error
options.add_argument(f'user-agent={userAgent}')
driver = webdriver.Chrome(resource_path('./drivers/chromedriver.exe'),options=options) #trigger driver
#driver = webdriver.Chrome(executable_path = driver_path,options=options )
try:
driver.get(revised_url) #Revised URL open
time.sleep(5)
except TimeoutException:
driver.get(revised_url) #In case of timeout- reattempt
time.sleep(5)
error_flag = 0 #disable error flag - so we can proceed as usual
else:
revised_url = get_url_from_button(odds_url_button,main_url)
userAgent = ua.random
options.add_argument(f'user-agent={userAgent}')
driver = webdriver.Chrome(resource_path('./drivers/chromedriver.exe'),options=options)
#driver = webdriver.Chrome(executable_path = driver_path,options=options )
try:
driver.get(revised_url)
except TimeoutException:
driver.get(revised_url)
error_flag = 0
else: #In case of no error
driver.find_element_by_xpath(odds_url_button).click()#Click on next button
driver.switch_to.window(driver.window_handles[0]) #in case any pop up is opening due to any reason - swtich to main window
i+=1
time.sleep(random.randint(5,7)) #sleep for random amount of time - to make the script robust
htmlSource = driver.page_source #Get the html code
soup = bs4.BeautifulSoup(htmlSource,'html.parser') #pass the page
#get the fixed data which is common and do not change for one book maker
title, home_team , away_team , place, weather ,tournament,m_date,m_time,data_first,data_second,data_third,final_score = get_fixed_data(soup)
#home team ranking
home_team_ranking = main_urls[file_key-3]
away_team_ranking = main_urls[file_key-2]
print('Title data :',title)
if title != 'No Data':#check if the data found or not
#create the folder path
print(m_date)
folder_month ,folder_day ,folder_year = m_date.split('-') #/
folder_hour ,folder_minute = m_time.split(':')
#fle_name = folder_day +folder_month + folder_year
#folder_name = folder_day +'_'+folder_month+'_' + folder_year
#convert the time to gmt
folder_time_string = folder_year +'-'+folder_month +'-'+folder_day +' '+ folder_hour+':'+folder_minute+':00'
#folder name change
folder_name =time.strftime("%d-%m-%Y", time.gmtime(time.mktime(time.strptime(folder_time_string, "%Y-%d-%m %H:%M:%S"))))
print(bookmaker)
#Output_file_format
try:
print('Creating directory')
os.mkdir(os.path.join(os.getcwd()+'\\'+folder_name))
except FileExistsError:
print('Directory already exist')
inter_file_name = 'Odds_nowgoal_'+str(title.replace('v/s','vs'))+'_'+folder_name+'.xlsx'
ola = os.path.join('\\'+folder_name,inter_file_name)
output_file_name = os.path.join(os.getcwd()+ola)
#sheet_title_first_table
sheet_title = '1X2 Odds_'+bookmaker
#add data to excel
excel_add_table(sheet_title,data_first,title,home_team , away_team , place, weather ,tournament,m_date,m_time,bookmaker,home_team_ranking,away_team_ranking,final_score)
#sheet_title_second_table
sheet_title = 'Handicap Odds_'+bookmaker
#add data to excel
excel_add_table(sheet_title,data_second,title,home_team , away_team , place, weather ,tournament,m_date,m_time,bookmaker,home_team_ranking,away_team_ranking,final_score)
#sheet_title_third_table
sheet_title = 'Over_Under Odds_'+bookmaker
#add data to excel
excel_add_table(sheet_title,data_third,title,home_team , away_team , place, weather ,tournament,m_date,m_time,bookmaker,home_team_ranking,away_team_ranking,final_score)
else :
lst_of_reattempt.append(home_team_ranking)
lst_of_reattempt.append(away_team_ranking)
lst_of_reattempt.append(driver.current_url) #add the url into list of reattempt
saved_button_for_halftime = odds_url_button #save the button when error happens - so we can convert it into URL and later reattempt
error_flag = 1
driver.quit() #Quit the driver in case of any error
driver.quit()

Error : list.remove(x) : x not in list, I don't understand why

I'm working on a little text-based game and having a problem with removing element from a list.
Here is a code to run that throws the error ValueError: list.remove(x): x not in the list but I don't see why.
LVL = 'lvl'
DAMAGE = 'damage'
Items = {
'Sword':{
LVL : 1,
DAMAGE : 5,
},
'Wand':{
LVL : 1,
DAMAGE : 3,
},
}
class player():
def __init__(self):
self.inventory = []
class item():
def __init__(self, name: str, **kwarg):
self.name = name
self.dmg = kwarg.get(DAMAGE)
self.lvl = kwarg.get(LVL)
def __str__(self):
return self.name
if __name__ == "__main__":
user = player()
for i in Items.keys():
it = item(i, **Items[i])
user.inventory.append(it)
# check user's inventory
print('Inventory after append items :')
for i in user.inventory:
print(i)
# let's say i want the user to drop items
items_name = [i for i in Items.keys()]
items_to_drop = [item(i, **Items[i]) for i in items_name]
for i in items_to_drop:
user.inventory.remove(i)
My guess is that even if the items from user.inventory are the same as those in items_to_drop, the program sees it as two different variables. In which case I do not see how to perform what I want, that is removing items from user.inventory given a list filled with items to remove (because I cannot loop over user.inventory directly right ?)
I apologize if this question has been answered before. I have searched for it but with no success.
In the end, as suggested in comment, I had to overwrite __eq__() method of class item so that Python sees if two same items are actually in both user.inventory and items_to_drop. It works !
If needed, I simply did
def __eq__(self, other):
return self.name == other.name
One easy solution:
# let's say i want the user to drop items
items_name = [i for i in Items.keys()]
items_to_drop = [item(i, **Items[i]) for i in items_name]
for i in items_to_drop:
try:
user.inventory.remove(i)
except:
continue
another one and better one:
# let's say i want the user to drop items
items_name = [i for i in Items.keys()]
items_to_drop = [item(i, **Items[i]) for i in items_name]
invetory_copy = inventory.copy()
for i in inventory:
if i in items_to_drop:
inventory_copy.remove(i)
inventory = inventory_copy

How to test a function that is using user-interface

I am trying to test a function that needs an interaction of the user. The question is how can I do it programmatically ?
Here is an example where we ask for the user to select an item in a list (main.py) :
import tkinter as tk
from tkinter import Button, OptionMenu, StringVar
def ask_for_item_in_list(lst, title, default_index=0):
root, item = tk.Tk(), None
WIDTH, HEIGHT = 300, 120
root.title(title)
root.maxsize(width=WIDTH, height=HEIGHT)
root.minsize(width=WIDTH, height=HEIGHT)
root.resizable(0, 0)
variable = StringVar(root)
variable.set(lst[default_index])
option_menu = OptionMenu(root, variable, *lst)
option_menu.pack(fill="none", expand=True)
def on_close():
# The window has been closed by the user
variable.set(None)
close()
def close():
# It quits mainloop()
root.quit()
# It closes the window
root.destroy()
button_ok = Button(root, text='OK', command=close)
button_ok.pack(fill='none', expand=True)
root.protocol('WM_DELETE_WINDOW', on_close)
# Execution stops here as long as the user has not closed the window or
# pressed ok
root.mainloop()
# We retrieve the selected item
item = variable.get()
if item == 'None':
item = None
return item
if __name__ == '__main__':
lst = ['Item 1', 'Item 2', 'Item 3']
title = 'Select an item'
default_selected_idx = lst.index('Item 2')
selected_item = ask_for_item_in_list(lst, title, default_selected_idx)
print(selected_item)
I used pytest to write all my tests since I can't use object oriented programming. Actually, the code must be maintainable by people who are not professional developpers.
As you can see, I can't test this function this way, since it will wait for the user input (test_main.py):
from main import ask_for_item_in_list
def test_ask_for_item_in_list():
lst = ['Item 1', 'Item 2', 'Item 3']
title = 'Select an item'
# Here TRY to test if changing the default selected index works
default_selected_idx = lst.index('Item 2')
# Code to simualte that the user as clicked on OK ?
# user.click_button('OK') ?
selected_item = ask_for_item_in_list(lst, title, default_selected_idx)
assert selected_item == 'Item 2'
Do I need to change the way I have coded this ?
Is it relevant to test this kind of function ?
I have faced this problem many times (whatever is the language used), I would like to know how this is supposed to be done in a clean way.
Thanks for reading ! :)
Usually one fills the user input beforehand with expected or special values and then calls the test function several times. Also you may simulate clicks with various tools.
In C++ you could do something like:
int number_of_tests = 10;
int tests_passed = 0;
tests_passed += my_test_function_int(0);
tests_passed += my_test_function_int(-1);
...
tests_passed += my_test_function_string("foo");
tests_passed += my_test_function_string("");
tests_passed += my_test_function_string(" ");
...
return (tests_passed == number_of_tests);
This is just an example how one can do it (in our company we do it this way).
Also it is not very hard to add new tests for non-programmers or new people.

Trying to build Risk style game in Tkinter

I have been trying to build my skills in Python and am trying to create a risk style game.
I am not far in to it at the moment as I am trying to get to grips with classes and Tkinter.
My first trial is to create a series of buttons to take the place of the different countries. I then want these buttons to update the amount of armies on the country when they are clicked.
So far I have been able to get the map to generate from the class I have created and the buttons are clickable. When a button is clicked it updates the amount of armies but always for the last button.
How do I get it so that the button I click updates and not the last one?
Have I gone about this in entirely the wrong way?
from tkinter import *
import random
class territory:
def __init__ (self, country, player = "1", current_armies = 0, x=0, y=0):
self.country = country
self.current_armies = current_armies
self.player = player
self.y = y
self.x = x
def get_armies(self):
print(self.country + " has " + str( self.current_armies)+ " armies.")
def add_armies (self, armies):
self.current_armies += armies
def roll_dice (self, dice=1):
rolls = []
for i in range(0, dice):
rolls.append(random.randint(1,6))
rolls.sort()
rolls.reverse()
print (self.country + " has rolled " + str(rolls))
return rolls
def owner(self):
print (self.country + " is owned by " + self.player)
def get_country(self):
print(country)
def button (self):
Button(window, text = territories[0].current_armies, width = 10, command = click1(territories, 0)).grid(row=y,column=x)
window = Tk()
def create_territories():
countries = ["UK", "GER", "SPA", "RUS"]
terr_pos = [[1,0],[2,0],[1,5],[4,1]]
sta_arm = [1,1,1,1]
terr = []
player = "1"
for i in range(len(countries)):
terr.append(territory(countries[i],player, sta_arm [i] , terr_pos[i][0],terr_pos[i][1]))
if player == "1":
player = "2"
else:
player = "1"
return terr
def click1(territory, i):
territory[i].current_armies += 1
build_board(territory)
def build_board(territories):
for i in range(0,4):
Button(window, text = territories[i].country+"\n"+str(territories[i].current_armies), width = 10, command = lambda: click1(territories, i)).grid(row=territories[i].y,column=territories[i].x)
territories = create_territories()
window.title ("Domination")
create_territories()
build_board(territories)
window.mainloop()
In your def button(self):... you are always referencing territories[0]:
Button(window, text=territories[0].current_armies,... command=click1(territories, 0)...
As such, you are always using the first territory as your reference, so you ought to initialize each territory with its index in territories[] so you can pass that into your Button constructor.
On your question of "entirely the wrong way," I'd personally send that question over to CodeReview, since that's more of their domain (we fix broken code, they address smelly code), though there is significant overlap. We do prefer one question per question, however, and "is this whole thing wrong?" is a little broad for StackOverflow.

Resources