I am learning how to use Zeep as my soap client. I am able to connect to a WSDL and view services, however, I am stuck on how to catch all possible exceptions. I am only able to catch KeyError. I want to be able to catch a few more:
Basically something similar to the below http client example:
except (http.client.HTTPException, ValueError, KeyError, AttributeError) as e
I would want to use try.....except
try:
session = Session()
session.auth = HttpNtlmAuth(username, password)
request_data = {
}
client = Client(wsdl, transport=Transport(session=session))
response = client.service.GetPendingBills(**request_data)
billobj = json.loads(response)
print(billobj)
bills = (len(billobj["Bills"]))
except KeyError as e:
bills = 0
Maybe this is too late, but you can import zeep.exceptions and handle all kinds of exceptions that you have that way. You just need to check the exception and catch it as you have demonstrated above.
Solution is shown in this documentation
I decided to import requests and handle the exceptions as indicated below:
except (requests.exceptions.HTTPError, KeyError, TimeoutError) as e
Thanks
Related
I want to catch an Exception in a for loop, and print a message in all items of the for loop, but at the end, still throw an Exception, so that I know the script failed. How to do that?
I'm trying this for now:
for key, value in marketplaces.items():
try:
r = requests.post(value, data=json.dumps({'Content': message(key, res)}), verify=False)
except:
r = requests.post(value, data=json.dumps({'Content': "Sorry, an error occured"}), verify=False)
But with this code, I only catch the exception and post an error message to all items in the for loop, but don't know that the script failed. I want to know this and throw and exception, after posting an error message to all items in for loop.
Any suggestions? Thank you!
The idea is to set a variable inside the except clause and check it after the loop is done. The example below is roughly what you need.
Note that there could be more than one failure, so we use a list to store all the errors, and report them all at the end.
Also note that the correct way to catch an exception in Python is to catch a specific exception -- in this case, it suffices to catch the requests.exceptions base class.
The code below stores the exception string e in the list of errors and afterwards, we check if there are any errors, and raise a new exception with the entire list of errors.
In this case, we're just raising the most generic exception type for requests exceptions -- requests.exceptions.RequestException (i.e. the base class once again). This is because there are various possible errors which could have occurred during the requests. Depending on your use case, you might prefer another more standard exception type, such as RuntimeError -- see the docs for the standard exception types.
import requests
errors = []
for key, value in marketplaces.items():
try:
r = requests.post(value, data=json.dumps({'Content': message(key, res)}), verify=False)
except requests.exceptions.RequestException as e:
r = requests.post(value, data=json.dumps({'Content': "Sorry, an error occured"}), verify=False)
errors.append(str(e))
if errors:
raise requests.exceptions.RequestException(errors)
Alternative:
Though the above is a fairly common approach that suits many use cases, it may be convoluted in some situations. Hence, I'm proposing a much simpler and more trivial alternative, which may also suit your needs, or anyone else who stumbles across this. Simply print out an error message each time an exception is raised (i.e. each time the code enters the except clause), like this:
import requests
import sys
for key, value in marketplaces.items():
try:
r = requests.post(value, data=json.dumps({'Content': message(key, res)}), verify=False)
except requests.exceptions.RequestException as e:
r = requests.post(value, data=json.dumps({'Content': "Sorry, an error occured"}), verify=False)
print(str(e), file=sys.stderr)
Hope this helps!
I have always used:
r = requests.get(url)
if r.status_code == 200:
# my passing code
else:
# anything else, if this even exists
Now I was working on another issue and decided to allow for other errors and am instead now using:
try:
r = requests.get(url)
r.raise_for_status()
except requests.exceptions.ConnectionError as err:
# eg, no internet
raise SystemExit(err)
except requests.exceptions.HTTPError as err:
# eg, url, server and other errors
raise SystemExit(err)
# the rest of my code is going here
With the exception that various other errors could be tested for at this level, is one method any better than the other?
Response.raise_for_status() is just a built-in method for checking status codes and does essentially the same thing as your first example.
There is no "better" here, just about personal preference with flow control. My preference is toward try/except blocks for catching errors in any call, as this informs the future programmer that these conditions are some sort of error. If/else doesn't necessarily indicate an error when scanning code.
Edit: Here's my quick-and-dirty pattern.
import time
import requests
from requests.exceptions import HTTPError
url = "https://theurl.com"
retries = 3
for n in range(retries):
try:
response = requests.get(url)
response.raise_for_status()
break
except HTTPError as exc:
code = exc.response.status_code
if code in [429, 500, 502, 503, 504]:
# retry after n seconds
time.sleep(n)
continue
raise
However, in most scenarios, I subclass requests.Session, make a custom HTTPAdapter that handles exponential backoffs, and the above lives in an overridden requests.Session.request method. An example of that can be seen here.
Almost always, raise_for_status() is better.
The main reason is that there is a bit more to it than testing status_code == 200, and you should be making best use of tried-and-tested code rather than creating your own implementation.
For instance, did you know that there are actually five different 'success' codes defined by the HTTP standard? Four of those 'success' codes will be misinterpreted as failure by testing for status_code == 200.
If you are not sure, follow the Ian Goldby's answer.
...however please be aware that raise_for_status() is not some magical or exceptionally smart solution - it's a very simple function that decodes the response body and throws an exception for HTTP codes 400-599, distinguishing client-side and server-side errors (see its code here).
And especially the client-side error responses may contain valuable information in the response body that you may want to process. For example a HTTP 400 Bad Request response may contain the error reason.
In such a case it may be better to not use raise_for_status() but instead cover all the cases by yourself.
Example code
try:
r = requests.get(url)
# process the specific codes from the range 400-599
# that you are interested in first
if r.status_code == 400:
invalid_request_reason = r.text
print(f"Your request has failed because: {invalid_request_reason}")
return
# this will handle all other errors
elif r.status_code > 400:
print(f"Your request has failed with status code: {r.status_code}")
return
except requests.exceptions.ConnectionError as err:
# eg, no internet
raise SystemExit(err)
# the rest of my code is going here
Real-world use case
PuppetDB's API using the Puppet Query Language (PQL) responds with a HTTP 400 Bad Request to a syntactically invalid query with a very precise info where is the error.
Request query:
nodes[certname] { certname == "bastion" }
Body of the HTTP 400 response:
PQL parse error at line 1, column 29:
nodes[certname] { certname == "bastion" }
^
Expected one of:
[
false
true
#"[0-9]+"
-
'
"
#"\s+"
See my Pull Request to an app that uses this API to make it show this error message to a user here, but note that it doesn't exactly follow the example code above.
Better is somewhat subjective; both can get the job done. That said, as a relatively inexperienced programmer I prefer the Try / Except form.
For me, the T / E reminds me that requests don't always give you what you expect (in a way that if / else doesn't - but that could just be me).
raise_for_status() also lets you easily implement as many or as few different actions for the different error types (.HTTPError, .ConnectionError) as you need.
In my current project, I've settled on the form below, as I'm taking the same action regardless of cause, but am still interested to know the cause:
try:
...
except requests.exceptions.RequestException as e:
raise SystemExit(e) from None
Toy implementation:
import requests
def http_bin_repsonse(status_code):
sc = status_code
try:
url = "http://httpbin.org/status/" + str(sc)
response = requests.post(url)
response.raise_for_status()
p = response.content
except requests.exceptions.RequestException as e:
print("placeholder for save file / clean-up")
raise SystemExit(e) from None
return response, p
response, p = http_bin_repsonse(403)
print(response.status_code)
I am using the following approach for handling Twilio exceptions in Python:
try:
#code for sending the sms
print(message.sid)
except TwilioRestException as e:
print(e)
This allows me to send sms and Exceptions are handled by Python.
Now I need to "return" the exceptions codes in order to process them, let's say, give user a message depending on the exception.
How can I achieve that?
If raising exception is not an option you could simply add return under except block.
def func():
# your logic
try:
#code for sending the sms
print(message.sid)
except TwilioRestException as e:
print(e)
return e.code # or whetever attribute e has for it...
By default function will return None if everything goes right. In client code it will be like
err = func()
if err:
# sms wasn't sent, action required
I've been given some web scraping code on Python and now need to write some code so that if there is an error, it will notify someone by email. I have found this code online:
def send_email():
to = request.form.get('to')
if not to:
return ('Please provide an email address in the "to" query string '
'parameter.'), 400
sg = sendgrid.SendGridAPIClient(apikey=SENDGRID_API_KEY)
to_email = mail.Email(to)
from_email = mail.Email(SENDGRID_SENDER)
subject = 'This is a test email'
content = mail.Content('text/plain', 'Example message.')
message = mail.Mail(from_email, subject, to_email, content)
response = sg.client.mail.send.post(request_body=message.get())
if response.status_code != 202:
return 'An error occurred: {}'.format(response.body), 500
return 'Email sent.'
Is this the kind of thing that I should use? If not, what is the best way to go about this problem?
Thanks in advance.
What I meant:
try:
# Web scrapping code
except requests.packages.urllib3.exceptions.MaxRetryError as e:
print repr(e)
send_email()
OR
use except without specifying for passive handling.
try:
# Web scrapping code
except:
send_email()
BUT:
From the PEP-8 Style Guide for Python:
When catching exceptions, mention specific exceptions whenever
possible instead of using a bare except: clause.
A bare except: clause will catch SystemExit and KeyboardInterrupt
exceptions, making it harder to interrupt a program with Control-C,
and can disguise other problems. If you want to catch all exceptions
that signal program errors, use except Exception: (bare except is
equivalent to except BaseException:).
A good rule of thumb is to limit use of bare 'except' clauses to two
cases:
If the exception handler will be printing out or logging the
traceback; at least the user will be aware that an error has occurred.
If the code needs to do some cleanup work, but then lets the exception
propagate upwards with raise. try...finally can be a better way to
handle this case.
If i have some bad authorization data (for example wrong password) SUDS rises exception (400, u'Bad Request') from which i cant get anything, but in teh log is response, which contains data that password is wrong, but how to get this response? I tried like this:
except Exception as e:
print str(e)
print self._client.last_received()
It prints:
(400, u'Bad Request')
None
But in log there is long xml which contains <SOAP-ENV:Reason><SOAP-ENV:Text xml:lang="en">Sender not authorized</SOAP-ENV:Text></SOAP-ENV:Reason>
I am pulling this out of a comment and into an answer because of the code block.
import suds.client
try:
auth_url = "https://url.to.my.service/authenticator?wsdl"
auth_client = suds.client.Client(auth_url)
cookie = auth_client.service.authenticate(user,password)
except Exception as e:
print str(e)
print auth_client.last_received()
Using this code, I receive the appropriate response from my service if I pass an invalid password:
Server raised fault: 'error.pwd.incorrect'
None
And an appropriate response if I pass an invalid user id:
Server raised fault: 'error.uid.missing'
None
Something you may want to consider doing, is changing your except statement to catch suds.WebFault instead of the generic exception. There may be something else that is occurring and triggering your exception block.
One other thing that may help with your issue, is to pass faults=True in your Client() call.
The Client can be configured to throw web faults as WebFault or to
return a tuple (, )
The code I posted above would look like this:
auth_client = suds.client.Client(auth_url, faults=True)