I have a flask restful resource like this:
api.add_resource(TrainerById, '/api/trainer/<int:uuid>')
with the source code like this:
class TrainerById(Resource):
def get(self):
data = trainer_by_id_parser.parse_args()
trainer_uuid = data['uuid']
new_trainer = Trainer.find_by_uuid(trainer_uuid)
if not new_trainer:
return {'msg': f"Trainer with uuid {trainer_uuid} not found"}, 401
else:
return {'msg': to_json_trainer(new_trainer)}
I want to return the trainer profile of the trainer with the UUID from the path param, however the issue is that, it returns a 404 whenever I try to access the endpoint like such:
localhost:5000/api/trainer/profile/886313e1-3b8a-5372-9b90-0c9aee199e5d #gives 404
You mixed resourceful routing with argument parsing.
Resourceful Routing are endpoints of an app.
Listed below are the examples of different routes:
localhost:5000/api/trainer/
localhost:5000/api/trainer/profile
localhost:5000/api/trainer/profile/6385d786-ff51-455e-a23f-0699c2c9c26e
localhost:5000/api/trainer/profile/4385d786-ef51-455e-a23f-0c99c2c9c26d
Note that last two can be grouped by using resourceful routing.
RequestParser is Flask-RESTPlus built-in support for request data validation. Those can be querystring or POST form encoded data etc.
With incomplete code you gave, the functionality you want can be implemented like so:
from flask import Flask
from flask_restplus import Resource, Api
app = Flask(__name__)
api = Api(app)
# List of trainers, just basic example instead of DB.
trainers = [
'6385d786-ff51-455e-a23f-0699c2c9c26e',
'7c6d64ae-8334-485f-b402-1bf08aee2608',
'c2a427d5-5294-4fad-bf10-c61018ba49e1'
]
class TrainerById(Resource):
def get(self, trainer_uuid):
# In here, trainer_uuid becomes <class 'uuid.UUID'>, so you can
# convert it to string.
if str(trainer_uuid) in trainers:
return {'msg': f"Trainer with UUID {trainer_uuid} exists"}, 200
else:
return {'msg': f"Trainer with uuid {trainer_uuid} not found"}, 404
# This means after profile/, next expected keyword is UUID with name in route
# as trainer_uuid.
api.add_resource(TrainerById, '/api/trainer/profile/<uuid:trainer_uuid>')
if __name__ == '__main__':
app.run(debug=True)
Related
from flask import Flask, jsonify, request
from flask_restful import Api, Resource
app = Flask(__name__)
api = Api(app)
user_dict = {}
class User(Resource):
def __init__(self):
user_id = 0
def get(self):
return jsonify(user_dict[id])
api.add_resource(User, "/user")
if __name__ == "__main__":
app.run(debug=True)
The idea is that when a GET request is made to /user/1, then the get method returns that key/value pair of the user_dict. How do I do path variables in Python? Please assume that the dictionary is not empty.
Flask uses <variable_name> or <converter:variable_name> placeholders in URL path registrations.
This is used in the examples shown in the Flask-Restful Quickstart documentation:
class TodoSimple(Resource):
def get(self, todo_id):
return {todo_id: todos[todo_id]}
def put(self, todo_id):
todos[todo_id] = request.form['data']
return {todo_id: todos[todo_id]}
api.add_resource(TodoSimple, '/<string:todo_id>')
Here <string:todo_id> is a path variable, passed to the TodoSimple.get() and TodoSimple.put() methods as an argument.
Flask-Restful otherwise assumes a general familiarity with Flask's patterns, I strongly recommend you read through at least the Flask Quickstart document, and I recommend you also work through the tutorial, if nothing else.
For your specific example, if user ids are always integers, use:
class User(Resource):
def get(self, user_id):
return jsonify(user_dict[user_id])
api.add_resource(User, "/user/<int:user_id>")
I'm writing a flask application.
It makes sense to have multiple endpoints, like that:
prefix + '/'
prefix + '/<id>'
prefix + '/<id>/parameters'
prefix + '/<id>/parameters/<param>'
However, if I try to declare them all inside a blueprint, I'm getting a AssertionError: Handler is overwriting existing for endpoint _blueprintname_._firsthandlername_
Is there any way around this? I know it's been straight-forwardly done before, in technologies like .net core. Thanks in advance.
If you plan to add many parameters in your routes, you could have a look at this module for flask.
It helps you assigning routes to resources.
You could build up a set of routes as follows:
from flask import Flask, request
from flask_restful import Resource, Api, reqparse
app = Flask(__name__)
api = Api(app)
class Some(Resource):
def get(self, id=None, params=None):
"""
In order to allow empty params for routes, the named arguments
must be initialized
"""
if id and params:
return {'message':'this is get with id and params'}
if id:
return {'message':'this is get with id'}
return {'message':'this is get'}
def post():
"""
One can use here reqparse module to validate post params
"""
return {'message':'this is post'}
# Add the resource to the service
api.add_resource(Some, '/', '/<string:id>','/<string:id>/<string:params>', ...)
# start the service
if __name__ == '__main__':
app.run(debug=True)
I have a simple API written with the help of flask-restplus:
from flask import Flask
from flask_restplus import Resource, Api
app = Flask(__name__) # Create a Flask WSGI application
api = Api(app) # Create a Flask-RESTPlus API
#api.route('/hello') # Create a URL route to this resource
class HelloWorld(Resource): # Create a RESTful resource
def get(self): # Create GET endpoint
return {'hello': 'world'}
if __name__ == '__main__':
app.run(debug=True)
When I navigate to loacalhost:5000/ in the browser I get a basic Swagger documentation, but I can't find where do I get a machine readable plain yaml representation of the API, should't it be also generated automatically?
I could not find any information around "Swagger Yaml documentation generation" in the official flask-restplus docs. So, I decided to examine the source code and found that the Swagger class implements the Swagger documentation generation for the API instance.
The Swagger class in the flask-restplus source-code is the Swagger documentation wrapper for an API instance. All methods in this class suggest that the API data is serialized as a JSON dictionary. For instance, consider the as_dict() function of this class which serializes the full Swagger specification as a serializable dict. Take a look at the docstring for this function:
from flask import Flask
from flask_restplus import Resource, Api
from flask_restplus.api import Swagger
app = Flask(__name__)
api = Api(app)
swag = Swagger(api)
print(swag.as_dict.__doc__)
#Output:
Output the specification as a serializable ``dict``.
:returns: the full Swagger specification in a serializable format
:rtype: dict
I maybe wrong but the source code suggests that the API documentation is only returned as JSON which is available at http://localhost:5000/swagger.json by default. I could not find anything for YAML.
But there is a workaround to generate YAML documentation for your API. I used the json and yaml libraries to dump the json response from /swagger.json into YAML and save it to yamldoc.yml. You can invoke this by going to http://localhost:5000/swagger.yml. The full code:
from flask import Flask
from flask_restplus import Resource, Api
from flask_restplus.api import Swagger
import requests
import json, yaml
app = Flask(__name__) # Create a Flask WSGI application
api = Api(app) # Create a Flask-RESTPlus API
#api.route('/hello') # Create a URL route to this resource
class HelloWorld(Resource): # Create a RESTful resource
def get(self):
return {'hello': 'world'}
#api.route('/swagger.yml')
class HelloWorld(Resource):
def get(self):
url = 'http://localhost:5000/swagger.json'
resp = requests.get(url)
data = json.loads(resp.content)
with open('yamldoc.yml', 'w') as yamlf:
yaml.dump(data, yamlf, allow_unicode=True)
return {"message":"Yaml document generated!"}
if __name__ == '__main__':
app.run(debug=True)
I hope this helps.
From #amanb answer I made my api return a yaml file, without making any requests.
According to documentation of flask restplus (or, the most recent fork, flask restx) it is possible to export the Swagger specififcations corresponding to your API using:
from flask import json
from myapp import api
print(json.dumps(api.__schema__))
So, instead of using requests I preferred to use api.__schema__.
As my goal is to provide the file for download at the time of the request, it is necessary to use the send_file function of Flask. In addition, this file can be later deleted from the directory so we can use the after_this_request decorator of Flask to invoke the annotated function that will delete the file. The full code:
import os
import json
import yaml
from flask import Flask, after_this_request, send_file, safe_join, abort
from flask_restplus import Resource, Api
from flask_restplus.api import Swagger
app = Flask(__name__) # Create a Flask WSGI application
api = Api(app) # Create a Flask-RESTPlus API
#api.route('/hello') # Create a URL route to this resource
class HelloWorld(Resource): # Create a RESTful resource
def get(self):
return {'hello': 'world'}
#api.route('/swagger.yml')
class HelloWorld(Resource):
def get(self):
data = json.loads(json.dumps(api.__schema__))
with open('yamldoc.yml', 'w') as yamlf:
yaml.dump(data, yamlf, allow_unicode=True, default_flow_style=False)
file = os.path.abspath(os.getcwd())
try:
#after_this_request
def remove_file(resp):
try:
os.remove(safe_join(file, 'yamldoc.yml'))
except Exception as error:
log.error("Error removing or closing downloaded file handle", error)
return resp
return send_file(safe_join(file, 'yamldoc.yml'), as_attachment=True, attachment_filename='yamldoc.yml', mimetype='application/x-yaml')
except FileExistsError:
abort(404)
if __name__ == '__main__':
app.run(debug=True)
I'm currently working on creating a Cookie from an endpoint. As my backend and frontend only interacts via RESTful endpoints, is there anyway I can create a cookie when the frontend calls my backend's endpoint?
flask.make_response.set_cookie() doesn't seem to work for me. Also, I can't use app.route('/') to set my cookie either.
You can do this with Set-Cookie header returning with a response.
from flask import Flask
from flask_restful import Resource, Api
app = Flask(__name__)
api = Api(app)
class HelloWorld(Resource):
def get(self):
return {'task': 'Hello world'}, 200, {'Set-Cookie': 'name=Nicholas'}
api.add_resource(HelloWorld, '/')
if __name__ == '__main__':
app.run(debug=True)
Setting the header in the response tuple is one of the standard approaches. However, keep in mind that the Set-Cookie header can be specified multiple times, which means that a python Dictionary won't be the most effective way to set the cookies in the response.
According to the flask docs the header object can also be initialized with a list of tuples, which might be more convenient in some cases.
Example:
from flask import Flask
from flask_restful import Api, Resource
app = Flask(__name__, static_url_path='')
api = Api(app)
class CookieHeaders(Resource):
def get(self):
# Will only set one cookie "age = 23"
return { 'message' : 'Made with dict'}, 200, { 'Set-Cookie':'name=john', 'Set-Cookie':'age=23' }
def post(self):
# Will set both cookies "name = john" and "age = 23"
headers = [ ('Set-Cookie', 'name=john'), ('Set-Cookie', 'age=23') ]
return { 'message' : ' Made with a list of tuples'}, 200, headers
api.add_resource(CookieHeaders, '/')
if __name__ == '__main__':
app.run(debug=True)
The GET call will only set 1 cookie (due to the lack of multi-key support in python dictionaries), but the POST call will set both.
Flask has a #after_this_request callback decorator. (see: http://flask.pocoo.org/docs/1.0/api/#flask.after_this_request)
so you can set your cookies in it
from flask import after_this_request
from flask_restful import Resource
class FooResource(Resource):
def get(self):
#after_this_request
def set_is_bar_cookie(response):
response.set_cookie('is_bar', 'no', max_age=64800, httponly=True)
return response
return {'data': 'foooo'}
or even
from flask import after_this_request, request
from flask_restful import Resource, abort
class FooResource(Resource):
def get(self):
self._check_is_bar()
return {'data': 'foooo'}
def _check_is_bar(self)
if request.cookies.get('is_bar') == 'yes':
abort(403)
#after_this_request
def set_is_bar_cookie(response):
response.set_cookie('is_bar', 'no', max_age=64800, httponly=True)
return response
I am adding Swagger UI to my Python Flask application using Flasgger. Most common examples on the Internet are for the basic Flask style using #app.route:
from flasgger.utils import swag_from
#app.route('/api/<string:username>')
#swag_from('path/to/external_file.yml')
def get(username):
return jsonify({'username': username})
That works.
In my application however, I am not using #app.route decorators to define the endpoints. I am using flask Blueprints. Like following:
from flask import Flask, Blueprint
from flask_restful import Api, Resource
from flasgger.utils import swag_from
...
class TestResourceClass(Resource):
#swag_from('docs_test_get.yml', endpoint='test')
def get() :
print "This is the get method for GET /1.0/myapi/test endpoint"
app = Flask(__name__)
my_api_blueprint = Blueprint('my_api', __name__)
my_api = Api(my_api_blueprint)
app.register_blueprint(my_api_blueprint, url_prefix='/1.0/myapi/')
my_api.add_resource(TestResourceClass, '/test/'
endpoint='test',
methods=['GET', 'POST', 'PUT', 'PATCH', 'DELETE'])
....
As seen above, I used #swag_from decorator on the TestResourceClass.get() method which is bound to the GET method endpoint. I also have the endpoint=test matching in the two places.
But I am not getting anything on the Swagger UI, it is all blank. The docs_test_get.yml file does contain the valid yaml markup to define the swagger spec.
What am I missing? How can I get Flasgger Swagger UI working with Flask Blueprint based setup?
Now there is an example of blueprint app in https://github.com/rochacbruno/flasgger/blob/master/examples/example_blueprint.py
"""
A test to ensure routes from Blueprints are swagged as expected.
"""
from flask import Blueprint, Flask, jsonify
from flasgger import Swagger
from flasgger.utils import swag_from
app = Flask(__name__)
example_blueprint = Blueprint("example_blueprint", __name__)
#example_blueprint.route('/usernames/<username>', methods=['GET', 'POST'])
#swag_from('username_specs.yml', methods=['GET'])
#swag_from('username_specs.yml', methods=['POST'])
def usernames(username):
return jsonify({'username': username})
#example_blueprint.route('/usernames2/<username>', methods=['GET', 'POST'])
def usernames2(username):
"""
This is the summary defined in yaml file
First line is the summary
All following lines until the hyphens is added to description
the format of the first lines until 3 hyphens will be not yaml compliant
but everything below the 3 hyphens should be.
---
tags:
- users
parameters:
- in: path
name: username
type: string
required: true
responses:
200:
description: A single user item
schema:
id: rec_username
properties:
username:
type: string
description: The name of the user
default: 'steve-harris'
"""
return jsonify({'username': username})
app.register_blueprint(example_blueprint)
swag = Swagger(app)
if __name__ == "__main__":
app.run(debug=True)
You just need to add your blueprint's name when you reference endpoint. Blueprints create namespaces. Example below. And useful tip: use app.logger.info(url_for('hello1')) for debugging problems with endpoint - it shows very useful error messages like this Could not build url for endpoint 'hello1'. Did you mean 'api_bp.hello1' instead?.
from flask import Flask, Blueprint, url_for
from flask_restful import Api, Resource
from flasgger import Swagger, swag_from
app = Flask(__name__)
api_blueprint = Blueprint('api_bp', __name__)
api = Api(api_blueprint)
class Hello(Resource):
#swag_from('hello1.yml', endpoint='api_bp.hello1')
#swag_from('hello2.yml', endpoint='api_bp.hello2')
def get(self, user=''):
name = user or 'stranger'
resp = {'message': 'Hello %s!' % name}
return resp
api.add_resource(Hello, '/hello', endpoint='hello1')
api.add_resource(Hello, '/hello/<string:user>', endpoint='hello2')
app.register_blueprint(api_blueprint)
swagger = Swagger(app)
app.run(debug=True)
swag_from function have some error to parse file path.you can use doc string to define api in get() first.
flasgger will parse MethodView() method like get,post.
Seems flasgger does not work or has no proper support for blue print style Flask definitions (yet). I used https://github.com/rantav/flask-restful-swagger which worked great!