flask-jwt-extended: customizing error message - python

When the flask-jwt-extended token has expired, a HTTP request will result in this JSON response
{
"msg": "Token has expired"
}
My application has a fixed error response format:
{
"message": "Project name 'Test 8' already exist.",
"error": {
"resource": "Project",
"field": "project_name",
"code": "already_exists",
"stack_trace": "(psycopg2.IntegrityError) duplicate key value violates unique constraint \"project_name_idx\"\nDETAIL: Key (project_name)=(Test 8) already exists.\n [SQL: 'INSERT INTO project (project_name, project_root, deadline_id) VALUES (%(project_name)s, %(project_root)s, %(deadline_id)s) RETURNING project.project_id'] [parameters: {'project_name': 'Test 8', 'project_root': 'P:\\\\Test8', 'deadline_id': 2}]"
}
}
How do I customize flask-jwt-extended error response?

Examples of this are documented here: http://flask-jwt-extended.readthedocs.io/en/latest/changing_default_behavior.html
The API docs are here: http://flask-jwt-extended.readthedocs.io/en/latest/api.html#module-flask_jwt_extended

If you want to provide being able to change the standard JSON error response that is returned by Flask JWT so that you can send back your own standard error message format you would have to use JWTManager loader functions. Specifically the expired_token_loader
# Using the expired_token_loader decorator, we will now call
# this function whenever an expired but otherwise valid access
# token attempts to access an endpoint
#jwt.expired_token_loader
def my_expired_token_callback():
return jsonify({
'status': 401,
'sub_status': 42,
'msg': 'The token has expired'
}), 401
Doing this may end up being tedious having to use all the loader functions for all the different ways in validating a token though.
You could considered writing your own generic utility function that returns the value portion of any response object text attribute and then put that into your fixed error message format that needs to be returned.
Example:
def extract_response_text(the_response):
return the_response.json().get('msg')
Also I forgot to mention you could take the above example and use the #app.after_request decorator. This would allow you to configure all your apps endpoints to use this method before returning the response. In which you could alter or create your specific JSON response payload.

Related

How to do pass a Request Body while doing a 307 Redirect with a( POST Method and body ) from AWS Lambda , in python

I need to redirect to an another end point from my AWS Lambda function.
I have implemented below code in AWS Lambda function and trying to Redirect to an End Point implement in external system as a POST end point. I am not sure how to pass the Request Body.
Python code is trying to redirect, but I am not sure how to pass a "new" Request Body as it is a POST Endpoint, I don't want the original body to get transmitted.
Please see the Java error at the destination where it got redirected.
def lambda_handler(event, context):
# Create Dictionary
value = {
"language": "python33",
"greetings" : "Greetings... Hope you have been redirected"
}
# Dictionary to JSON Object using dumps() method
# Return JSON Object
return {
"headers": {"Location": "http://localhost:8080/urlcallback",'Content-Type':'application/json' ,},
'statusCode': 307,
'body': json.dumps(value)
}
The Redirected End Point is getting invoked, but with the below error in the JAVA Spring boot logs:
{
"timestamp": 1671820676750,
"status": 500,
"error": "Internal Server Error",
"exception": "org.springframework.http.converter.HttpMessageNotReadableException",
"message": "Required request body is missing: public java.util.Map<java.lang.String, java.lang.String> my.sample.service.controller.PingController.urlcallback(java.lang.String)",
"path": "/urlcallback"
}
I will greatly appreciate any suggestions or pointers. I could not find any when I checked on StackOverFlow
Note:
I tried first with basic redirect of 301 and 302, that works for a GET call, but now I get an error while doing a Redirect with POST end point and a request body as I have some sensitive data to transmit
I am not sure how to pass a "new" Request Body as it is a POST Endpoint, I don't want the original body to get transmitted.
Based on HTTP 307 docs this is not possible: "307 guarantees that the method and the body will not be changed when the redirected request is made"
You can try HTTP 303 and change the request type to GET to remove the body from the request.

Odoo 13:- HTTP controller type ="http" request type application/json not accepted

I write a odoo program in python, this way request type text/plain is working. And application/json request work in any other odoo version(Odoo 15,Odoo 16) .But odoo 13 application/json request not working.
My odoo python controller code here:-
class CamsAttendance(http.Controller):
#http.route('/sample/sample_json/', method=["POST"], csrf=False, auth='public', type="http")
def generate_attendance(self, **params):
data = json.loads(request.httprequest.data)
json_object = json.dumps(data)
sample = json.loads(json_object)
print(sample['sample_code'])
return "process competed" //example
view the image on request and response data via postman:-
Request application/json data:-
(sample request)
{
"sample_code":"sample_code"
}
Response data:-
{
"jsonrpc": "2.0",
"id": null,
"error": {
"code": 200,
"message": "Odoo Server Error",
"data": {
"name": "werkzeug.exceptions.BadRequest",
"debug": "Traceback (most recent call last):\n File \"C:\\odoo 13\\Odoo 13.0\\server\\odoo\\http.py\", line 624, in _handle_exception\n return super(JsonRequest, self)._handle_exception(exception)\n File \"C:\\odoo 13\\Odoo 13.0\\server\\odoo\\http.py\", line 310, in _handle_exception\n raise pycompat.reraise(type(exception), exception, sys.exc_info()[2])\n File \"C:\\odoo 13\\Odoo 13.0\\server\\odoo\\tools\\pycompat.py\", line 14, in reraise\n raise value\n File \"C:\\odoo 13\\Odoo 13.0\\server\\odoo\\http.py\", line 669, in dispatch\n result = self._call_function(**self.params)\n File \"C:\\odoo 13\\Odoo 13.0\\server\\odoo\\http.py\", line 318, in _call_function\n raise werkzeug.exceptions.BadRequest(msg % params)\nwerkzeug.exceptions.BadRequest: 400 Bad Request: <function CamsAttendance.generate_attendance at 0x0644C930>, /cams/biometric-api3.0: Function declared as capable of handling request of type 'http' but called with a request of type 'json'\n",
"message": "400 Bad Request: <function CamsAttendance.generate_attendance at 0x0644C930>, /cams/biometric-api3.0: Function declared as capable of handling request of type 'http' but called with a request of type 'json'",
"arguments": [],
"exception_type": "internal_error",
"context": {}
}
}
}
Another way try :-
I note the error ** 'http' but called with a request of type 'json'"** in the response and i change the code contoroller code on
#http.route('/sample/sample_json/', method=["POST"], csrf=False, auth='public', type="json")
But,
Response:-
{
"jsonrpc": "2.0",
"id": null,
"result": "process competed"
}
**
but i want :-**
"process competed
Mainly this problem Odoo 13 only. And i what process the code and expecting text
message the response
Have you tried to change the type of the controller route to json?
Also see the official documentation
odoo.http.route(route=None, **kw)
Decorator marking the
decorated method as being a handler for requests. The method must be
part of a subclass of Controller.
Parameters
route – string or array. The route part that will determine
which http requests will match the decorated method. Can be a single
string or an array of strings. See werkzeug’s routing documentation
for the format of route expression (
http://werkzeug.pocoo.org/docs/routing/ ).
type – The type of request, can be 'http' or 'json'.
auth –
The type of authentication method, can on of the following:
user: The user must be authenticated and the current request will
perform using the rights of the user.
public: The user may or may not be authenticated. If she isn’t, the
current request will perform using the shared Public user.
none: The method is always active, even if there is no database.
Mainly used by the framework and authentication modules. There request
code will not have any facilities to access the database nor have any
configuration indicating the current database nor the current user.
methods – A sequence of http methods this route applies to. If not
specified, all methods are allowed.
cors – The Access-Control-Allow-Origin cors directive value.
csrf (bool) –
Whether CSRF protection should be enabled for the route.
Defaults to True. See CSRF Protection for more.
As CZoellner said, you need to change type to json.
You also need to include OPTIONS in request methods and enable cors for the request.
#http.route('/sample/sample_json/', type='json', auth='public', cors='*', csrf=False, methods=['POST', 'OPTIONS'])
Ensure your request body follows odoo's syntax
{
jsonrpc: 2.0,
params: {
sample_code: "sample_code"
}
}

Google calendar api acl

I was trying to use API to make some changes on my google calendar.
I have created a project on google cloud console, enable calendar API, and got the credential ready. The OAuth scope I set is:
scopes = ['https://www.googleapis.com/auth/calendar']
flow = InstalledAppFlow.from_client_secrets_file("client_secret.json", scopes=scopes)
And I got both credentials for my account.
credentials = flow.run_console()
I wanted to use ACL to gain access to the calendar, so I tried "get" and "insert" these two functions. Codes are as follows:
rule = service.acl().get(calendarId='primary', ruleId='ruleId').execute()
print('%s: %s' % (rule['id'], rule['role']))
rule = {
'scope': {
'type': 'group',
'value': 'default',
},
'role': 'owner'
}
created_rule = service.acl().insert(calendarId='primary', body=rule).execute()
print(created_rule)
However, the results show that I have some problems with the access part.
<HttpError 400 when requesting https://www.googleapis.com/calendar/v3/calendars/primary/acl/ruleId?alt=json
returned "Invalid resource id value.">
and
<HttpError 400 when requesting https://www.googleapis.com/calendar/v3/calendars/primary/acl?alt=json
returned "Invalid scope value.">
what step have I miss or do wrong?
The first error shows up in Acl.get whenever you specify an invalid ruleId. So make sure you are providing a valid ruleId in here:
rule = service.acl().get(calendarId='primary', ruleId='valid-rule-id').execute()
If you don't know the ruleId, you can look for it by calling Acl.list.
About the second error, the problem is that you are providing a wrong request body for Acl.insert. If you want to share this calendar with a group, you should provide a valid email address of the group in scope.value. default is not a valid value. Your request body should be something like this:
rule = {
'scope': {
'type': 'group',
'value': 'group-email-address',
},
'role': 'owner'
}
You will find the group email address if you click About in the corresponding group.
I hope this is of any help.

HTTP Triggering Cloud Function with Cloud Scheduler

I have a problem with a job in the Cloud Scheduler for my cloud function. I created the job with next parameters:
Target: HTTP
URL: my trigger url for cloud function
HTTP method: POST
Body:
{
"expertsender": {
"apiKey": "ExprtSender API key",
"apiAddress": "ExpertSender APIv2 address",
"date": "YYYY-MM-DD",
"entities": [
{
"entity": "Messages"
},
{
"entity": "Activities",
"types":[
"Subscriptions"
]
}
]
},
"bq": {
"project_id": "YOUR GCP PROJECT",
"dataset_id": "YOUR DATASET NAME",
"location": "US"
}
}
The real values has been changed in this body.
When I run this job I got an error. The reason is caused by processing body from POST request.
However, when I take this body and use it as Triggering event in Testing I don't get any errors. So I think, that problem in body representation for my job but I havn't any idea how fix it. I'll be very happy for any idea.
Disclaimer:
I have tried to solve the same issue using NodeJS and I'm able to get a solution
I understand that this is an old question. But I felt like its worth to answer this question as I have spent almost 2 hours figuring out the answer for this issue.
Scenario - 1: Trigger the Cloud Function via Cloud Scheduler
Function fails to read the message in request body.
Scenario - 2: Trigger the Cloud Function via Test tab in Cloud Function interface
Function call always executes fine with no errors.
What did I find?
When the GCF routine is executed via Cloud Scheduler, it sends the header content-type as application/octet-stream. This makes express js unable to parse the data in request body when Cloud scheduler POSTs the data.
But when the exact same request body is used to test the function via the Cloud Function interface, everything works fine because the Testing feature on the interface sends the header content-type as application/json and express js is able to read the request body and parses the data as a JSON object.
Solution
I had to manually parse the request body as JSON (explicitly using if condition based on the content-type header) to get hold of data in the request body.
/**
* Responds to any HTTP request.
*
* #param {!express:Request} req HTTP request context.
* #param {!express:Response} res HTTP response context.
*/
exports.helloWorld = (req, res) => {
let message = req.query.message || req.body.message || 'Hello World!';
console.log('Headers from request: ' + JSON.stringify(req.headers));
let parsedBody;
if(req.header('content-type') === 'application/json') {
console.log('request header content-type is application/json and auto parsing the req body as json');
parsedBody = req.body;
} else {
console.log('request header content-type is NOT application/json and MANUALLY parsing the req body as json');
parsedBody = JSON.parse(req.body);
}
console.log('Message from parsed json body is:' + parsedBody.message);
res.status(200).send(message);
};
It is truly a feature issue which Google has to address and hopefully Google fixes it soon.
Cloud Scheduler - Content Type header issue
Another way to solve the problem is this:
request.get_json(force=True)
It forces the parser to treat the payload as json, ingoring the Mimetype.
Reference to the flask documentation is here
I think this is a bit more concise then the other solutions proposed.
Thank you #Dinesh for pointing towards the request headers as a solution! For all those who still wander and are lost, the code in python 3.7.4:
import json
raw_request_data = request.data
# Luckily it's at least UTF-8 encoded...
string_request_data = raw_request_data.decode("utf-8")
request_json: dict = json.loads(string_request_data)
Totally agree, this is sub-par from a usability perspective. Having the testing utility pass a JSON and the cloud scheduler posting an "application/octet-stream" is incredibly irresponsibly designed.
You should, however, create a request handler, if you want to invoke the function in a different way:
def request_handler(request):
# This works if the request comes in from
# requests.post("cloud-function-etc", json={"key":"value"})
# or if the Cloud Function test was used
request_json = request.get_json()
if request_json:
return request_json
# That's the hard way, i.e. Google Cloud Scheduler sending its JSON payload as octet-stream
if not request_json and request.headers.get("Content-Type") == "application/octet-stream":
raw_request_data = request.data
string_request_data = raw_request_data.decode("utf-8")
request_json: dict = json.loads(string_request_data)
if request_json:
return request_json
# Error code is obviously up to you
else:
return "500"
One of the workarounds that you can use is to provide a header "Content-Type" set to "application/json". You can see a setup here.

Python Connexion — Control "Type" Key in 400 Response Errors

I'm using connexion, a python library for REST API's, with a swagger definition. It's working properly for the actual requests, but when there is an error condition, such as validation fails, it returns a response like:
{
"type": "about:blank",
"title": "Bad Request",
"status": 400,
"detail": "None is not of type 'string'"
}
The title, status and detail all are good and make sense, but is there a way for me to control the type key's value so that I can provide more helpful information rather than simply having about:blank in there?
Under the hood, it appears that connexion uses requests and flask, so maybe there is something I can leverage from them?
I have never worked with the underlying framework, but with a quick scan, the module exposes the Flask application constructor. With that, you can define a new app with your swagger file as
app = connexion.App(__name__, specification_dir='swagger/')
and then add custom error handlers. For example for the 400 error you can do
from flask import jsonify
#app.errorhandler(400)
def page_not_found(e):
custom_data = {
'type': 'Advanced type'
# etc
}
return jsonify(custom_data)
Read more about Flask Error handlers here
I went through this problem also by imputing null to some model property. If you do not want to create an error handler just add the tag
x-nullable: true
in the property that the validation is occurring.

Categories