The usage of Flask-restful routing functions is correct? - python

I can access /v1/folder but cannot access /v1/folder/<folder-id>. Could you tell me the reason? In the flask-request document said add_resource() can route multiple URI. But I cannot. Maybe I misunderstand something. Please tell me if you find the clue.
from flask import request
from flask_restful import Resource, abort
class Folder(Resource):
def post(self, folder_id):
return { "message":"post with folder_id"}, 200
def post(self):
return { "message":"post without folder_id"}, 201
app = Flask(__name__)
.....
api_bp = Blueprint('api', __name__)
api = Api(api_bp, serve_challenge_on_401=True)
api.add_resource( Folder, '/v1/folder', '/v1/folder/<string:folder_id>')
app.register_blueprint(api_bp)
if __name__ == "__main__":
app.run(host='0.0.0.0', debug=True )
The error messages is "TypeError: post() got an unexpected keyword argument 'folder_id' ". What's wrong?

Python does not support function/method overloading, so the post method you declared last is always going to be the one that's used. Instead, you should use the tools Python does provide - default values for arguments.
I would personally do the following:
from flask import request
from flask_restful import Resource, abort
class Folder(Resource):
def post(self, folder_id=None):
if folder_id is None:
return self.__simple_post()
else:
return self.__parameter_post(folder_id)
def __parameter_post(self, folder_id):
return { "message":"post with folder_id"}, 200
def __simple_post(self):
return { "message":"post without folder_id"}, 201
app = Flask(__name__)
.....
api_bp = Blueprint('api', __name__)
api = Api(api_bp, serve_challenge_on_401=True)
api.add_resource( Folder, '/v1/folder', '/v1/folder/<string:folder_id>')
app.register_blueprint(api_bp)
if __name__ == "__main__":
app.run(host='0.0.0.0', debug=True )
Or you could handle the logic in the post method, if the logic is similar enough and not too long. If the logic ends up being unreadable, though, consider using the approach I suggested.

Related

flask before_request can't access request variable

I have a simple flask app:
def create_app():
config_name = 'testing_local'
app = Flask(__name__)
app.config.from_object(CONFIG_BY_NAME[config_name])
app.url_map.converters['bool'] = BooleanConverter
#app.before_request
def incoming_request_logging():
print(request)
return app
I get the error:
Undefined variable 'request'
I thought the wrapper included the request object when called?
In this example, first approved answer seems like the request object is inherited?
Can anyone link me to a full example? How can I retrieve this variable?
I guess declaring this at top of your program might solve your issue, if I think what you said it is:
from flask import request

Flask route at / returns 404 when used with Flask-Restplus

I have a Flask app which has a Flask-RestPlus API as well as a "/" route. When I try to access "/" however, I get a 404. If I remove the Flask-RestPlus extension, the route works. How do I make both parts work together?
from flask import Flask
from flask_restplus import Api
app = Flask(__name__)
api = Api(app, doc="/doc/") # Removing this makes / work
#app.route("/")
def index():
return "foobar"
This is an open issue in Flask-RestPlus. As described in this comment on that issue, changing the order of the route and Api solves the issue.
from flask import Flask
from flask_restplus import Api
app = Flask(__name__)
#app.route("/")
def index():
return "foobar"
api = Api(app, doc="/doc/")
flask-restplus defines a different way of assigning routes according to their docs:
#api.route('/')
class Home(Resource):
def get(self):
return {'hello': 'world'}
Notice that the api variable is used instead of the app. Moreover, a class is used although I am not 100% sure it is required.

Catch-All URL example in flask-restful also catching /

There is a catch-all URL example for Flask:
from flask import Flask
app = Flask(__name__)
#app.route('/', defaults={'path': ''})
#app.route('/<path:path>')
def catch_all(path):
return 'You want path: %s' % path
if __name__ == '__main__':
app.run()
Decorators can be translated to the following to code look more similar to Flask-RESTful achieving the same functionality:
app.add_url_rule('/', 'catch_all', catch_all, defaults={'path': ''})
app.add_url_rule('/<path:path>', 'catch_all', catch_all, defaults={'path': ''})
If I'm right, this can be further translated to an equivalent Flask-RESTful app (at least debuging shows it creates the same URL routes):
class RESTapp(Resource):
def get(self, path):
# do get something
api.add_resource(RESTapp, '/', '/<path:path>', defaults={'path': ''})
The problem is that this app redirects all URLs to / and I can not get the requested path in get() function. I want to handle all paths ( / and '/') in the same function as in Flask, but using Flask-RESTful.
Similar questions:
Catch-All URL in flask-restful The Asker does not want to catch / or at least not in the same functions as other URL-s.
Flask restful API urls The Answerer proposes two classess as two resources. I have to initialize the class through resource_class_kwargs keyword argument and I want to keep only one instance, so it will not be good for me.
What I've tried:
Create two add_resource calls for the same class. It end with error.
Debug add_resource. It shows that it creates a resource view function from the Endpoint and that is given to the add_url_rule function. Else it works the same as the two subsequent add_url_rule functions.
By trial and error I've figured out the solution, which is neither documented nor looks like to the expected way of doing it (simmilarly as in Flask, showed in the question).
One must supply a Pythonic default argument to get() and other functions: get(stuff='DEF_VAL')
Full example which is working:
from flask import Flask
from flask_restful import Api, Resource
app = Flask(__name__)
api = Api(app)
class RESTapp(Resource):
#staticmethod
def get(path=''): # <-- You should provide the default here not in api.add_resource()!
return 'You want path: \'%s\'' % path # do get something
api.add_resource(RESTapp, '/', '/<path:path>') # Here just the URLs must be the arguments!
if __name__ == '__main__':
app.run()

Injecting a Flask Request into another Flask App

Is there a way to inject a Flask request object into a different Flask app. This is what I'm trying to do:
app = flask.Flask(__name__)
#app.route('/foo/<id>')
def do_something(id):
return _process_request(id)
def say_hello(request):
# request is an instance of flask.Request.
# I want to inject it into 'app'
I'm trying this with Google Cloud Functions, where say_hello() is a function that is invoked by the cloud runtime. It receives a flask.Request as the argument, which I want to then process through my own set of routes.
I tried the following, which doesn't work:
def say_hello(request):
with app.request_context(request.environ):
return app.full_dispatch_request()
This responds with 404 errors for all requests.
Edit:
The simple way to implement say_hello() is as follows:
def say_hello(request):
if request.method == 'GET' and request.path.startswith('/foo/'):
return do_something(_get_id(request.path))
flask.abort(404)
This essentially requires me to write the route matching logic myself. I'm wondering if there's a way to avoid doing that, and instead use Flask's built-in decorators and routing capabilities.
Edit 2:
Interestingly, dispatching across apps work locally:
app = flask.Flask(__name__)
# Add app.routes here
functions = flask.Flask('functions')
#functions.route('/', defaults={'path': ''})
#functions.route('/<path:path>', methods=['GET', 'POST', 'PUT', 'DELETE'])
def catch_all(path):
with app.request_context(flask.request.environ):
return app.full_dispatch_request()
if __name__ == '__main__':
functions.run()
But the same technique doesn't seem to work on GCF.
I wouldn't recommend this method, but this is technically possible by abusing the request stack and rewriting the current request and re-dispatching it.
However, you'll still need to do some type of custom "routing" to properly set the url_rule, as the incoming request from GCF won't have it (unless you explicitly provide it via the request):
from flask import Flask, _request_ctx_stack
from werkzeug.routing import Rule
app = Flask(__name__)
#app.route('/hi')
def hi(*args, **kwargs):
return 'Hi!'
def say_hello(request):
ctx = _request_ctx_stack.top
request = ctx.request
request.url_rule = Rule('/hi', endpoint='hi')
ctx.request = request
_request_ctx_stack.push(ctx)
return app.dispatch_request()

Bad Request Error with flask, python, HTML, unusual initialization behavior with flask.request.form

I'm writing a web-app using flask, python and HTML. My issue is that the first time I load the a webpage, I get the following error
Bad Request The browser (or proxy) sent a request that this server
could not understand.
I'm able to get the page to load eventually by "tricking" first running it without any flask.request.form calls, and then putting them back in (details below). Something must be going wrong in my initialization. I'm new to flask and using python with HTML.
Assume I'm working from a directory called example. I have a python script called test.py and an HTML template called test.html with the following directory structure:
\example\test.py
\example\templates\test.html
My python script test.py is:
import sys
import flask, flask.views
app = flask.Flask(__name__)
app.secret_key = "bacon"
class View(flask.views.MethodView):
def get(self):
result = flask.request.form['result']
return flask.render_template('test.html', result=result)
# return flask.render_template('test.html')
def post(self):
return self.get()
app.add_url_rule('/', view_func=View.as_view('main'), methods=['GET', 'POST'])
app.debug = True
app.run()
and my HTML in test.html is
<html>
<head>
</head>
<body>
<form action="/" method="post">
Enter something into the box:
<input type="text" name="result"/><br>
<input type="submit" value="Execute!"/>
</form>
</body>
</html>
Steps to reproduce the error
1: Run the test.py script, and open up the URL in a browser
Running on http://127.0.0.1:5000/
You should see the following error
Bad Request The browser (or proxy) sent a request that this server
could not understand.
2: Comment out the first 2 lines of the def get(self) function and uncomment the 3rd line of the def get(self) function so that test.py looks like this
import sys
import flask, flask.views
app = flask.Flask(__name__)
app.secret_key = "bacon"
class View(flask.views.MethodView):
def get(self):
# result = flask.request.form['result']
# return flask.render_template('test.html', result=result)
return flask.render_template('test.html')
def post(self):
return self.get()
app.add_url_rule('/', view_func=View.as_view('main'), methods=['GET', 'POST'])
app.debug = True
app.run()
3: Refresh the URL, and you will see that things work (though I ultimately want to be able to return the value of result
4: Now, switch the lines that are commented out again. I.e, uncomment the first 2 lines of the def get(self) function and comment out the 3rd line of the def get(self) function so that test.py looks like this
import sys
import flask, flask.views
app = flask.Flask(__name__)
app.secret_key = "bacon"
class View(flask.views.MethodView):
def get(self):
result = flask.request.form['result']
return flask.render_template('test.html', result=result)
# return flask.render_template('test.html')
def post(self):
return self.get()
app.add_url_rule('/', view_func=View.as_view('main'), methods=['GET', 'POST'])
app.debug = True
app.run()
5: Refresh the URL and now you see things will be working as desired.
This is just a toy example illustrating the real problem exhibiting this weird behavior of how I have to "trick" my browser into showing me this webpage. The
The issue here is that you are attempting to access POSTed variables in a method that will only handle GET requests. When you attempt to access a query string or POST parameter that is not set Flask will, by default, raise a BadRequest error (because you are asking for something that the person hitting the page did not supply).
What happens if the key does not exist in the form attribute? In that case a special KeyError is raised. You can catch it like a standard KeyError but if you don’t do that, a HTTP 400 Bad Request error page is shown instead. So for many situations you don’t have to deal with that problem.
If you need to access a variable from either request.args (GET) or request.form (POST) and you don't need it to be set use the get method to get the value if it is there (or None if it is not set.
# Will default to None
your_var = request.form.get("some_key")
# Alternately:
your_var = request.form.get("some_key", "alternate_default_value")
Here's an alternate way of structuring your code:
import sys
import flask, flask.views
app = flask.Flask(__name__)
app.secret_key = "bacon"
app.debug = True
class View(flask.views.MethodView):
def get(self):
"""Enable user to provide us with input"""
return self._default_actions()
def post(self):
"""Map user input to our program's inputs - display errors if required"""
result = flask.request.form['result']
# Alternately, if `result` is not *required*
# result = flask.request.form.get("result")
return self._default_actions(result=result)
def _default_actions(self, result=None):
"""Deal with the meat of the matter, taking in whatever params we need
to get or process our information"""
if result is None:
return flask.render_template("test.html")
else:
return flask.render_template("test.html", result=result)
app.add_url_rule('/', view_func=View.as_view('main'), methods=['GET', 'POST'])
if __name__ == "__main__":
app.run()

Categories