Raising a server error to the client with grpc - python

Let's consider a simple service:
service Something {
rpc Do(Request) returns Response;
}
message Request {
string field = 1;
}
message Response {
string response = 1;
}
Assume I have to do some checking on the Request.field, I want to raise a client error if the field is invalid:
class MyService(proto_pb2.SomethingServicer):
def Do(self, request, context):
if not is_valid_field(request.field):
raise ValueError("Damn!") # Or something like that
return proto_pb2.Response(response="Yeah!")
With the following client:
channel = grpc.insecure_channel(...)
stub = proto_pb2.SomethingStub(channel)
try:
response = stub.Do(proto_pb2.Request(field="invalid"))
except grpc.RpcError as e:
print(e)
<_Rendezvous of RPC that terminated with (StatusCode.UNKNOWN, Exception calling application: Damn!)>
So I can technically handle errors. My issue is... is there a better way? Is there a good way to change the message description? Can we change the status code?

Yes, there is a better way. You may change the status details using the ServicerContext.set_details method and you may change the status code using the ServicerContext.set_code method. I suspect that your servicer will look something like
class MyService(proto_pb2.SomethingServicer):
def Do(self, request, context):
if not is_valid_field(request.field):
context.set_code(grpc.StatusCode.INVALID_ARGUMENT)
context.set_details('Consarnit!')
return proto_pb2.Response()
return proto_pb2.Response(response='Yeah!')
.

There's a new method for this too, context.abort() - it'll actually raise an exception to terminate the RPC call:
grpc.ServicerContext.abort()

So at gRPC side someone can abort context using:
grpc.ServicerContext.abort()
At client side (python):
try:
result = {'msg', 'success'}
except grpc.RpcError as e:
if e.code() == grpc.StatusCode.INVALID_ARGUMENT:
result = {'msg', 'invalid arg error'}
elif e.code() == grpc.StatusCode.ALREADY_EXISTS:
result = {'msg', 'already exists error'}

Related

How to handle an error that is within an except block that returns?

Background
I am writing an AWS Lambda function that uses the Boto3 SDK to access AWS services in Python.
Context
My main function/lambda handler makes an API request. I check the status code from the request and raise custom exceptions based on the response code. For each except I create a descriptive message to send an email via SNS to notify me about the error.
What I've tried
I read this thread on custom python exceptions with error codes and error messages
I read this thread on nested excpetions but did not really understand how to apply it to my situation.
I also read this thread on handling exceptions that occur within except clause but I do not understand how to translate it.
Question
How do I handle the SNS sending if that raises an error and return the proper information?
class Error(Exception):
pass
class Error_401(Error):
pass
class Error_403(Error):
pass
class Error_404(Error):
pass
class Error_500(Error):
pass
# This syntax is most likely wrong, I just pasted it
# from link 1
class SNS_Send_Error(Error):
def __init__(self, code):
self.code = code
def __str__(self):
return repr(self.code)
def create_sns_client(arn,message):
try:
sns = boto3.client("sns")
res = sns.publish(
TargetArn=arn,
Message=json.dumps({"default": json.dumps(message)}),
MessageStructure="json"
)
except ClientError as e:
code = e.reponse['ResponseMetadata']['HTTStatusCode']
message = e.response['Error']['Code']
print(message)
raise SNS_Send_Error('SNS failed to send message. \
Responed with error code: {}'.fomat(code),code)
return res
def status_codes(arg):
if arg == 401:
raise Error_401
elif arg == 403:
raise Error_403
elif arg == 404:
raise Error_404
elif arg == 500:
raise Error_500
def lambda_handler(event, context):
# Other Code ...
# Request data from GH API
try:
res = request_gh_data(gh_secret, jobId)
status = res.status_code
gh_response = res.json()
status_codes(status)
except Error_401 as e:
message = generate_error(gh_response['message'], context)
create_sns_client(message)
return {
'statusCode': status,
'body': message
}
except Error_403 as e:
message = generate_error(gh_response['message'], context)
create_sns_client(message)
return {
'statusCode': status,
'body': message
}
except Error_404 as e:
message = generate_error(gh_response['message'], context)
create_sns_client(message)
return {
'statusCode': status,
'body': message
}
except Error_500 as e:
message = generate_error(gh_response['message'], context)
create_sns_client(message)
return {
'statusCode': status,
'body': message
}
except SNS_Send_Error as e:
code = e.reponse['ResponseMetadata']['HTTStatusCode']
message = e.response['Error']['Code']
print(message)
return {
"statusCode": code,
"body": message
}
# Additional Code
return {
'statusCode': 200,
'body': "Success!"
}
We can see that in each except block, I send a message based on the type of error that occurs. How should I handle the message sending error in the most "Pythonic" manner.
You can use except without specifying the exception type to handle any kind of Exception. Then you can get what was the cause of the exception with e.__class__.
try:
# code to be executed
except Exception as e:
# you can give your message
# or do the task you want
# based on the exception class
print('exception raised: ', e.__class__)

Handle message in python not working properly

I am working with rasa(latest version),but not able to send response to chatbot just because of handle_channel method,right now i am getting following error
"error": "Object of type coroutine is not JSON serializable"
Here is my code,where i am wrong ?
#app.route('/api/v1/<sender_id>/respond', methods=['GET', 'POST'])
def respond(self, request, sender_id):
request.setHeader('Content-Type', 'application/json')
request.setHeader('Access-Control-Allow-Origin', '*')
request_params = request_parameters(request)
if 'query' in request_params:
message = request_params.pop('query')
elif 'q' in request_params:
message = request_params.pop('q')
else:
request.setResponseCode(400)
return json.dumps({"error": "Invalid parse parameter specified"})
try:
out = CollectingOutputChannel()
response = self.agent.handle_message(message, output_channel=out, sender_id=sender_id)
request.setResponseCode(200)
return json.dumps(response)
except Exception as e:
request.setResponseCode(500)
logger.error("Caught an exception during "
"parse: {}".format(e), exc_info=1)
return json.dumps({"error": "{}".format(e)})
are you sure that you are not mixing methods up here? According to the documentation, you might either want to try:
handle_message(message, message_preprocessor=None, **kwargs)
or
handle_text(text_message, message_preprocessor=None, output_channel=None, sender_id='default')
Keep in mind to import the right libraries since there was a renaming since 1.0, just in case.

Give Flask error handler access to request

I'm trying to make a custom error page in Flask, and I'd like to give the error handler access to the request that generated the API call that caused the error so that the error page it returns can change depend on the circumstances. For instance, say there are two endpoints:
(1) #app.route('/get_item')
(2) #app.route('/submit_item')
If an error occurs during a call to get_item, I want to display a certain error page ("Sorry, an error occurred...") however, if an error occurs during a call to submit_item, I want it to say something more informative, like:
"An error occured! Please contact us.
Your user id: request.json['userid']
Your submission id: request.json['submission']"
Is it possible to allow the error handler to have access to this, or do I just have to wrap the whole of submit_item in try/except statements?
You can use the request context in the error handler function,
something along those lines:
from flask import request
def exception_handler(*args):
if request.url.endswith('submit_item'):
return "MyMoreDescriptiveErrorMessage", 500
else:
return "Something wrong happened", 500
I would probably create a custom exception and specify an error handler for it similar to this example.
class CustomException(Exception):
def __init__(self, message=None, status_code=None, payload=None):
Exception.__init__(self)
if message is None:
message = "Sorry, an error occurred..."
self.message = message
if status_code is not None:
self.status_code = status_code
self.payload = payload
#app.errorhandler(CustomException)
def handle_custom(error):
response = render_template('error.html', message=error.message)
response.status_code = error.status_code
return response
#app.route('/submit_item')
def submit_item():
message = "An error occured! Userid: %(userid)d, submission: %(submission_id)d"
message = message % (request.json)
raise CustomException(message)

How to return a more meaningful 500 error in a python-eve app

I have some code in a python-eve app that retrieves some data from a device and populates a resource when that resource is requested for the first time. Sometimes the code can't successfully connect to the device. In this case, I would like to return an error message that explains this better, rather than just a plain 500 error. Here is the on_fetch_item hook:
def before_returning_item(resource, _id, document):
if resource == "switches":
if "interfaces" not in document.keys():
# retrieve and store switch config if it hasn't been stored yet
r = app.data.driver.db[resource]
try:
switch = prepare_switch_from_document(document)
except socket.timeout:
# raise some more meaningful error with message
pass
interface_list = switch.get_formatted_interfaces()
r.update({'_id': _id}, {'interfaces': interface_list})
document['interfaces'] = interface_list
app.on_fetch_item += before_returning_item
Thanks in advance.
All you have to do is take advantage of Flask's abort method:
from flask import abort
def before_returning_item(resource, _id, document):
if resource == "switches":
if "interfaces" not in document.keys():
# retrieve and store switch config if it hasn't been stored yet
r = app.data.driver.db[resource]
try:
switch = prepare_switch_from_document(document)
except socket.timeout:
# raise some more meaningful error with message
abort(500)
interface_list = switch.get_formatted_interfaces()
r.update({'_id': _id}, {'interfaces': interface_list})
document['interfaces'] = interface_list
app.on_fetch_item += before_returning_item
If you want to add a custom description:
abort(500, description='My custom abort description')
I like to create custom exceptions, and raise those with meaning comments, Eg:
class MyExcept(Exception): pass
def before_returning_item():
...
if error_condition:
raise MyException('detailed explanation')
...
try:
before_returning_item()
except MyException, exc:
if 'device not ready' in str(exc):
print("Device was not rdy...Try agin?")
else:
raise

Exception Handling in google app engine

i am raising exception using
if UserId == '' and Password == '':
raise Exception.MyException , "wrong userId or password"
but i want print the error message on same page
class MyException(Exception):
def __init__(self,msg):
Exception.__init__(self,msg)
You are not using the Users API? Assuming you are handling a POST request, how about this:
class LoginError(Exception):
CODES = { 'mismatch': 'Wrong credentials', 'disabled': 'Account disabled' }
...
try:
// your authentication code
raise LoginError('mismatch')
...
raise LoginError('disabled')
except LoginError as e:
self.redirect(your_login_url + '?err=' + e)
# In login page you must not print arbitrary GET parameter directly
err_reason = LoginError.CODES[self.request.get('err')]`
(Login request should be using POST method because it changes the server's state, and it's good habit to redirect after a POST, thus a redirect.)
Why raising an exception instead of just stop function execution and redirect to new page using return statement

Categories