I'm currently designing a webservice in Python with Flask. I now got very confused if it is a RESTful service or just a regular webservice. I've been reading quite a few sources about RESTful services, but still I'm not able to say if my service is a REST architekture or not.
The requests to my API are stateless.
Here is what I have:
from flask import Flask,request
if __name__ == "__main__":
appLogger.info("RestFul service initialized and started")
app.run(host="0.0.0.0",port=int("80"),debug=True)
#app.route('/',methods = ['POST'])
def add():
"""
This function is mapped to the POST request of the REST interface
"""
#check if a JSON object is declared in the header
if request.headers['Content-Type'] == 'application/json; charset=UTF-8' and request.data:
try:
data = json.dumps(request.json)
#check if recieved JSON object is valid according to the scheme
#if (validateJSON(data)):
try:
saveToMongo(data)
appLogger.info("Record saved to MongoDB")
return "JSON Message saved in MongoDB"
except:
appLogger.error("Could not write to MongoDB")
except:
appLogger.error("Recieved invalid JSON")
else:
appLogger.error("Content-Type not defined or empty content")
raise FailedRequest
I non of the possible responses, I'll return a json, which is actually the payload of a request. It's always a regular http-response with a custome text as a result description.
Is that right, that because of this fact, it is not a RESTful service, and if I want to call it a RESTful service, that I would need to return back a json object? Or am I completely wrong? Is my API just a simple RPC?
I see only one resource / to which a POST request can be made. There is no way to GET a collection of objects or a single object saved in this way.
One could argue that suche a trivial system does not violate any REST principle. But I think this is not enough to call a system RESTful. It is a trivial RPC system with a single anonymous 'save' method.
Related
newbie here. I'm working on the Stripe payment method using flask and it all works well on my local machine but when I deploy my code on the server and listen to webhook events in the stripe dashboard, I get this error"No signatures found matching the expected signature for payload". Already tried so many solutions but nothing worked. Any help will be appreciated.
def webhook_received(self, user_id):
payload = request.data
endpoint_secret = 'my_secret_key'
sig_header = request.headers.get('stripe-signature')
try:
event = stripe.Webhook.construct_event(
json.loads(payload), sig_header, endpoint_secret
)
data = event['data']
except Exception as e:
return str(e)
event_type = event['type']
if event_type == 'checkout.session.completed':
self.handle_checkout_session(data, user_id)
elif event_type == 'invoice.paid':
pass
Okay I think I see the problem but I'll try to cover both potential issues.
(Most Likely): Stripe requires the raw, unmodified request body to form the webhook signature. In your try: block you are using json.loads(payload) which converts it to a Python dict object. Try using the raw payload data instead.
If the problem only occurs when you deploy your code to a remote server then the most likely problem is with the endpoint_secret value. I would add some logging in your webhook_received() function to log the value after it's loaded and make sure the value matches the webhook signing secret you can view in your Stripe dashboard.
Lastly, it's important to return proper responses to avoid webhook delivery retries. I know Flask does some stuff implicitly (a pet peeve of mine) but I'm not seeing a 200 or 500 response being returned here. You'll want to make sure you respond appropriately to avoid headaches later. You can check the best practices here. There's also a handy webhook builder here so you can check your implementation against Stripe's Flask code.
My system architecture currently sends a form data blob from the frontend to the backend, both hosted on localhost on different ports. The form data is recieved in the backend via the FastAPI library as shown.
#app.post('/avatar/request')
async def get_avatar_request(request: Request, Authorize: AuthJWT = Depends()):
form = await request.form()
return run_function_in_jwt_wrapper(get_avatar_output, form, Authorize, False)
Currently, I am trying to relay the form data unmodified to another FASTApi end point from the backend using the request library, as follows:
response = requests.post(models_config["avatar_api"], data = form_data, headers = {"response-type": "blob"})
While the destination endpoint does receive the Form Data, it seemed to not have parsed the UploadFile component properly. Instead of getting the corresponding starlette UploadFile data structure, I instead receive the string of the classname, as shown in this error message:
FormData([('avatarFile', '<starlette.datastructures.UploadFile object at 0x7f8d25468550>'), ('avatarFileType', 'jpeg'), ('background', 'From Image'), ('voice', 'en-US-Wavenet-B'), ('transcriptKind', 'text'), ('translateToLanguage', 'No translation'), ('transcriptText', 'do')])
How should I handle this problem?
FileUpload is a python object, you'd need to serialize it somehow before using requests.post() then deserialize it before actually getting the content out of it via content = await form["upload-file"].read(). I don't think you'd want to serialize a FileUpload object though (if it is possible), rather you'd read the content of the form data and then post that.
Even better, if your other FastAPI endpoint is part of the same service, you might consider just calling a function instead and avoid requests altogether (maybe use a controller function that the route function calls in case you also need this endpoint to be callable from outside the service, then just call the controller function directly avoiding the route and the need for requests). This way you can pass whatever you want without needing to serialize it.
If you must use requests, then I'd read the content of the form then create a new post with with that form data. e.g.
form = await request.form() # starlette.datastructures.FormData
upload_file = form["upload_file"] # starlette.datastructures.UploadFile - not used here, but just for illustrative purposes
filename = form["upload_file"].filename # str
contents = await form["upload_file"].read() # bytes
content_type = form["upload_file"].content_type # str
...
data = {k: v for k, v in form.items() if k != "upload-file"} # the form data except the file
files = {"upload-file": (filename, contents, content_type)} # the file
requests.post(models_config["avatar_api"], files=files, data=data, headers = {"response-type": "blob"})
Im fairly new to AWS and its Cognito and API-Gateway services.
I have created in AWS a Cognito-specific User Pool and an AWS-specific API-Gateway API with some API-Endpoints to be accessed via REST API calls. The API-Gateway "Authorizer" is set to "Cognito".
After that, I have exported the Swagger document/OpenAPI2.0 using the AWS-Console specific export function and generated with the Swagger Editor a Python REST Client API.
The generated REST Client SDK generated the Model-specific "GET" function, e. g.:
# create an instance of the API class
api_instance = swagger_client.DefaultApi()
user_id = 'user_id_example' # str |
try:
api_response = api_instance.user_get(user_id)
pprint(api_response)
except ApiException as e:
print("Exception when calling DefaultApi->user_get: %s\n" % e)
In order to get a correct result from the function call api_instance.user_get(user_id)
I need somehow pass the access token to that function.
The question is now, how do I pass the access token - which I have successfully obtained after the User signed-in - to the Python REST Client API in order to invoke an API-Endpoint function which has an "Cognito" authorizer set?
I saw many expamples how to realize this with Postman or CURL, but this is not what I'm looking for. I want to invoke my "Cognito" protected API-Endpoint in AWS API-Gateway with the generated REST API Client. I assume, there must be a way to put the received access token to the "Authorization" Header in the HTTP-Request call, before the generated REST Client function is invoked.
Any help is very appreciated.
I'm not sure if I've understood you correctly, but this might help you.
import requests
endpoint = ".../api/ip"
data = {"ip": "1.1.2.3"}
headers = {"Authorization": "Bearer MyBearerAuthTokenHere"}
print(requests.post(endpoint, data=data, headers=headers).json())
#Edit
You don't need to parse the response as json if it isn't. This is just an Sample.
I am developing some basic REST APIs in python. I am expecting an authorization token in the header of all requests except some unsecured requests like login and register. I am validating the token in #app.before_request and then I want to pass the decoded payload to the corresponding endpoint view function. But, I am not to attach the decoded info to the request object as I get "TypeError: 'Request' object does not support item assignment".
#app.before_request
def before_request():
print(request.endpoint)
if request.endpoint=="register" or request.endpoint=="login":
pass
else:
auth_header = request.headers.get('Authorization')
if auth_header:
auth_token = auth_header.split(" ")[1]
token=decode_auth_token(auth_token)
request["token"]=token
else:
return jsonify({"result":"","error":"No token present in header !"})
I am thinking of this implementation like an auth filter where all requests pass this filter. I can strike off the ill requests at this layer itself and also, I can fetch the user specific info which is needed in the net middleware.
I have a similar usecase (surprisingly similar, actually). I got around it by setting a custom property in the request object, much like your approach, although instead of using direct assignment (i.e. request["token"]=token), I used setattr(request, "token", token).
I got the tip from a bottle plugin which does something very similar:
https://github.com/agile4you/bottle-jwt/blob/master/bottle_jwt/auth.py#L258
You may even wanna try that, or some other plugin to further improve your application.
Flask offers g to propagate data in application context.
from flask import g
#app.before_request
def before_request():
g.token = vaue
#bp.route("/path", methods=["GET"]):
g.token
Reference:
https://flask.palletsprojects.com/en/2.2.x/appcontext/
Apologies because the only web development I know is of the django/python kind and am probably guilty of mixing my code idioms ( REST vs django URL dispatch workflow)
I have a URL handler which serves as a callbackUrl to a subscription for my Glassware. I am getting a POST to the handler , but the request object seems empty.
I am sure I am understanding this wrong but can someone point me in the direction of getting the "REPLY" information from a POST notification to a callbackURL.
My URL Handler is
class A600Handler(webapp2.RequestHandler):
def post(self):
"""Process the value of A600 received and return a plot"""
# I am seeing this in my logs proving that I am getting a POST when glass replies
logging.info("Received POST to logA600")
# This is returning None
my_collection = self.request.get("collection")
logging.info(my_collection)
# I also tried this but self.sequest.POST is empty '[]' and of type UnicodeMultiDict
# json_request_data = json.loads(self.request.POST)
#util.auth_required
def get(self):
"""Process the value of A600 received and return a plot"""
logging.info("Received GET to this logA600")
I have the following URL Handler defined and can verify that the post function is getting a "ping" when the user hits reply by looking at the app-engine logs.
MAIN_ROUTES = [
('/', MainHandler),('/logA600',A600Handler),
]
How do I extract the payload in the form of the voice transcribed text sent by the user?. I am not understanding The "parse_notification" example given in the docs
Did you try request.body? The docs for request.POST state
"If you need to access raw or non-form data posted in the request, access this through the HttpRequest.body attribute instead."
If the API isn't using form data in its post, you'll likely find the contents in request.body. The docs to which you linked indicate that the content will be placed as JSON in the body instead of form data ("containing a JSON request body"). I would try json.loads(request.body).
I am also having this issue of Mirror API calling my application for notifications, and those notifications are empty. My app runs on tomcat so its a java stack. All the samples process the notification like this:
BufferedReader notificationReader = new BufferedReader(
new InputStreamReader(request.getInputStream()));
String notificationString = "";
// Count the lines as a very basic way to prevent Denial of Service
// attacks
int lines = 0;
while (notificationReader.ready()) {
notificationString += notificationReader.readLine();
lines++;
// No notification would ever be this long. Something is very wrong.
if (lines > 1000) {
throw new IOException(
"Attempted to parse notification payload that was unexpectedly long.");
}
}
log.info("got raw notification " + notificationString);
For me this is always logging as empty. Since a notification url must be https, and for testing I could not use an IP address, I have setup dyndns service to point to my localhost:8080 running service. This all seems to work but I suspect how dyndns works is some type of forward or redirect here post data is removed.
How can I work around this for local development?
Updated:
Solved for me.
I found closing the response before reading request caused issue that request.inputStream was already closed. MOving this
response.setContentType("text/html");
Writer writer = response.getWriter();
writer.append("OK");
writer.close();
To after I fully read in request notification into a String solved the issue.