Indentation after try and except in multiple for loops python - python

I'm using try and except indentation. How should I indent after I the try and except code.
Example:
try:
for elem in
browser.find_elements_by_xpath('/html/body/form[2]/table/tbody/tr[1]/td[1]/select/option'):
elem.click()
except StaleElementReferenceException:
for elem in browser.find_elements_by_xpath('/html/body/form[2]/table/tbody/tr[1]/td[1]/select/option'):
elem.click()
for ele in browser.find_elements_by_xpath('/html/body/form[2]/table/tbody/tr[1]/td[2]/table/tbody/tr[1]/td[1]/select/option'):
ele.click()
I'm using "try and except" because I keep getting an error of StaleElementReferenceException. The only thing is that I need to use try and except multiple times in loops.

It seems you did right, the next line after the except that is out of the except should be indented at the same level. So:
try:
bla()
except X as y:
foo()
bar()
Note the additional blank line before bar to keep the code more readable.
Also, in the try block you can move the call browser.find_elements_by_xpath to a variable assignment before, and that way keep your code a bit more readable.
e.g.
all_options = browser.find_elements_by_xpath(
'/html/body/form[2]/table/tbody/tr[1]/td[1]/select/option')
for option in all_options:
option.click()
In case you want to have another try except wrapping the other loop in the except, you can do that nicely in multiple ways. My recommendation is to wrap the loop in a function that will "convert" the exception to a return value:
def click_all_options(path):
try:
for option in browser.find_elements_by_xpath(path):
option.click()
return True
except StaleElementReferenceException:
return False
and in the code:
select1 = '/html/body/form[2]/table/tbody/tr[1]/td[1]/select/option'
select2 = '/html/body/form[2]/table/tbody/tr[1]/td[2]/table/tbody/tr[1]/td[1]/select/option'
result = click_all_options(select1):
if not result:
click_all_options(select2):

Related

Python remove try inside except?

In python I have a function called func() which could raise NoSuchElementException:
I'm trying to call it with input1 if not correct call it with input2 else give up.
Is there any better way to write this code for clarity:
submit_button = None
try:
submit_button = func(input1)
except NoSuchElementException:
try:
submit_button = func(input2)
except NoSuchElementException:
pass
you can do something like:
for i in [input1, input2]:
try:
submit_button = func(i)
break
except NoSuchElementException:
print(f"Input {i} failed")
else:
print("None of the options worked")
If the first input doesn't throw an exception you break the loop, otherwise you keep in the loop and try the second input.
This way you can try as many inputs as you want, just add them into the list
The else at the end executes if you never broke the loop, which means none of the options worked
This works well if you want to try several inputs and treat them equally. For only two inputs your code looks readable enough to me. (try this, if it doesn't work try that)
Just another option...
from contextlib import suppress
submit_button = None
for i in input1, input2:
with suppress(NoSuchElementException):
submit_button = func(i)
break

Try-except doesn't re-run the entire code inside a for loop

I'm really new to Python and writing code, and this code doesn't seem to rerun through all the lines after it hits the exception. I want it to start again from the try function after it hits the exception. Among these 40 web elements that the code is running through, maybe like 4-5 do not have this element (id="tt_single_values_spent) and will giveNoSuchElementException. What I want is that once my code will hit the error, it will skip it and continue to gather the information. I am 100% sure the problem is with the code and not the site itself
.
for i in range(40):
try:
act4 = browser.find_element_by_css_selector('dd[id="tt_single_values_spent"]').get_attribute('innerText')
time_log = re.sub('h', '', act4)
if time_log != str("Not Specified"):
total_time.append(float(time_log))
print(act4)
pyautogui.press("down", 1);time.sleep(0.5)
except NoSuchElementException:
print('Not found')
My result:
1h
45h
4h
13h
1h
31.8h
34.2h
5h
Not found
Not found
Not found
Not found
Not found
Not found
Not found
The reason your code repeatedly outputs "Not found" once it encounters a single "Not found" element, is that your code only advances elements within the try block.
Instead, you should always advance. You can do this with a finally block, or with code outside of the try-except block:
for i in range(40):
try:
act4 = browser.find_element_by_css_selector('dd[id="tt_single_values_spent"]').get_attribute('innerText')
time_log = re.sub('h', '', act4)
if time_log != str("Not Specified"):
total_time.append(float(time_log))
print(act4)
except NoSuchElementException:
print('Not found')
finally:
pyautogui.press("down", 1)
time.sleep(0.5)

Python IF statement can't recognize else:

I am trying to verify if a row is displayed or not. I am using python and Selenium. Here is what I have tried so far
try:
row = self.driver.find_element_by_xpath(<row6>).is_displayed()
if row is False:
print("button is not displayed. Test is passed")
else:
do stuff
except:
NoSuchElementException
I am trying to achieve the following:
Page #1 will only display a button if Page #2 has row < 6.
I still have logic to write for the condition -> if row is False: . However, it should atleast print the string if it is false.
At the moment, the else: in my code is not working. There is no error displays but try: exits at the NoSuchElementException.
UPDATE: I have also tried the following code where I verify if button is displayed on page #1, go to the page #2 and validate if row6 is present. That works if button is displayed. If button is not displayed, it throws an error :NoSuchElementException: Message: Unable to locate element:
try:
button = self.driver.find_element_by_xpath(PATH)
if button.is_displayed():
do stuff
row = self.driver.find_element_by_xpath(<row6>)
if row.is_displayed():
do stuff
else:
do stuff
except:
button = self.driver.find_element_by_xpath("PATH").is_displayed()
if button is False:
print("button is hidden. Test is passed")
Any suggestion on how I can make this work??
I don't know Selenium, but it sounds like there may be multiple exceptions here, not all of the same type, and not where you might expect them to happen. For instance, everything is fine when row.is_displayed() evaluates to True, but an exception is thrown otherwise - This indicates to me that row might be None or some other unexpected result. I've taken a cursory look at the docs but I couldn't see right away.
Anyway - to debug this, try putting different sections of your code into try-except blocks:
try:
button = self.driver.find_element_by_xpath(PATH)
if button.is_displayed():
do stuff
try:
row = self.driver.find_element_by_xpath(<row6>)
except: # <-- Better if you test against a specific Exception!
print(" something is wrong with row! ")
try:
if row.is_displayed():
do stuff
else:
do stuff
except: # <-- Better if you test against a specific Exception!
print( " something is wrong with using row!" )
except: # <-- Better if you test against a specific Exception!
button = self.driver.find_element_by_xpath("PATH").is_displayed()
if button is False:
print("button is hidden. Test is passed")
Also, try to put a minimum amount of code inside each try-except, so that you know where the exception is coming from.
Maybe there's no hidden row6 to be found and an exception is raised.
The syntax of your except is wrong: as it is, it will catch all exceptions and then just do nothing with the NoSuchElementException object.
Did you mean:
except NoSuchElementException:
#do something when no row6 found

Python: Break-up large function into segments

I am creating a Bot for Reddit. I currently only have 1 very large function and I am looking to create sub-functions to make it more readable.
Here is a rough break-down of what it does
def replybot():
submissions = reversed(list(subreddit.get_new(limit=MAXPOSTS)))
for post in submissions:
try:
author = post.author.name
except AttributeError:
print "AttributeError: Author is deleted"
continue # Author is deleted. We don't care about this post.
# DOES PID EXIST IN DB? IF NOT ADD IT
cur.execute('SELECT * FROM oldposts WHERE ID=?', [pid])
sql.commit()
if cur.fetchone(): # Post is already in the database
continue
cur.execute('INSERT INTO oldposts VALUES(?)', [pid])
sql.commit()
...
I am looking to break the code up into segments i.e. put
try:
author = post.author.name
except AttributeError:
print "AttributeError: Author is deleted"
continue # Author is deleted. We don't care about this post.
in it's own function and call it from within replybot() but I run into the issue of calling continue. I get SyntaxError: 'continue' not properly in loop
Is there a way for me to do this?
If you take the inner part of a loop and convert it to its own function, it's no longer in a loop. The equivalent of continue in a loop, for a function, is return (i.e. terminate this iteration (which is now a function call) early).
Raise the error again instead of trying to continue. Either simply let it bubble to the main loop, or if you need better error handling, make your own custom error. For instance:
class MyNotFatalError(Exception):
pass
def do_something():
try:
a, b, c = 1, 2
except ValueError:
raise MyNotFatalError('Something went wrong')
# In your main function
for post in submissions:
try:
do_something()
do_some_other_thing()
do_some_more()
except MyNotFatalError as err:
continue # we could print out the error text here
do_some_last_thing()
It is probably better that way because you only catch errors you know you want to catch, and still let the program crash when there are actual bugs.
If you had simply caught ValueError that would also intercept and hide all other possible sources of the same kind of error.
as Claudiu said, when you broke inner commands into it's own function; It's not no longer in the loop and your code will be look like this:
def isNotAuthorDeleted(post):
try:
author = post.author.name
return author
except AttributeError:
print "AttributeError: Author is deleted"
return false
and your loop will be:
for post in submissions:
if not isNotAuthorDeleted(post):
continue

Try two expressions with `try except`

I have two expressions. I need to try one expression, if it is raise an exception try another, but if the second raises an exception too - to raise the exception.
I tried this, but it is looks ugly and I am not sure it is the best way to solve this issue:
try:
image = self.images.order_by(func.random()).limit(1)
except:
try:
image = self.images.order_by(func.rand()).limit(1)
except ProgrammingError:
raise ProgrammingError(
'The database engine must be PostgtreSQL or MySQL')
How do you do it?
Making a separate function is very helpful.
def get_random_image(self):
for rand in func.random, func.rand:
try:
return self.images.order_by(rand()).limit(1)
except ProgrammingError:
pass
raise ProgrammingError('This database engine is not supported')
Use a loop:
for methname in ("random", "rand"):
try:
image = self.images.order_by(getattr(func, methname)()).limit(1)
break
except ProgrammingError:
continue
else:
raise ProgrammingError("The database engine must be PostgtreSQL or MySQL")
The loop's else clause is executed only if the loop terminates normally (i.e., without a break) which is why we break after doing the image assignment. If you consider this too tricksy, because the else clause is so infrequently used with for, then this would also work:
image = None
for methname in ("random", "rand"):
try:
image = self.images.order_by(getattr(func, methname)()).limit(1)
except ProgrammingError:
continue
if not image:
raise ProgrammingError("The database engine must be PostgtreSQL or MySQL")
In this particular case, I'd actually try and detect the database before selecting the function. Can you reach the database connection from your code? If so, just switch on the drivername:
random = None
if drivername == 'postgres':
random = func.random
elif drivername == 'mysql':
random = func.rand
else:
raise ValueError('This module requires that you use PostgreSQL or MySQL')
Then, when selecting images, use the random value:
image = self.images.order_by(random()).limit(1)
Actually it MIGHT be a design flaw. Raising exceptions is to act on an event that should normally not occur. If you want to do something functionally into an except (except for handling the exception), than it looks like the first statement that you want to try is not a statement that should get an exception at all.
So instead of:
try:
do statement 1
except ...
try:
do statement 2
except:
think about :
if (statement_1 result == ...)
try:
do statement 2
except:
If you want to check if rand or random is a function of a class, you also could use
if 'rand' in dir(some object of a class)

Categories

Resources