How can I add app-wrappers endpoint to Restplus API documentation? - python

I have a Flask REST API. I use healthcheck.EnvironmentDump to make it easy to dump the environment where my service is running. Is it possible to add the endpoint to the Swagger documentation generated by Restplus?
Example
requirements.txt
flask
flask-restplus
gitpython
healthcheck
app.py
#!/usr/bin/env python
"""Simple Flask/Swagger/REST-API example."""
from flask import Flask
from flask_restplus import Resource, Api
from healthcheck import EnvironmentDump
app = Flask(__name__)
api = Api(app, doc='/doc')
# wrap the flask app and give a environment url
# TODO: Add this to API
envdump = EnvironmentDump(app, "/environment")
#api.route('/version')
class VersionAPI(Resource):
def get(self):
import git
repo = git.Repo(search_parent_directories=True)
sha = repo.head.object.hexsha
return sha
#api.route('/health')
class HealthAPI(Resource):
def get(self):
import datetime
return datetime.datetime.now().isoformat()
if __name__ == "__main__":
app.run(host='0.0.0.0')

EnvironmentDump and HealthCheck will each install their own handlers for the endpoint, so your get() instances within your defined Resources will not be reached. That being said, providing a stubbed resource is sufficient for it to show up in the Swagger doc generated by Flask-RESTPlus:
health = HealthCheck(app, '/healthcheck')
#api.route('/healthcheck')
class HealthCheckResource(Resource):
def get(self):
pass

Related

How can I add a prefix to all routes with the Flask REST JSON module?

The flask-rest-jsonapi quickstart shows that you can create a route() like this:
api.route(PostList, 'post_list', '/posts')
api.route(PostDetail, 'post_detail', '/posts/<int:id>')
But I want to have all of my routes to be something like /api/posts and /api/poss/<int:id> and I want to avoid repeating the /api part in every route(). When I try to use a blueprint here, like this:
api_bp = Blueprint('API', __name__, url_prefix='/api')
api = Api(app, api_bp)
api.route(PostList, 'post_list', '/posts')
api.route(PostDetail, 'post_detail', '/posts/<int:id>')
app.register_blueprint(api_bp)
The endpoint is still /posts and not /api/posts. How do I properly make a URL prefix for all the routes?
Reading the discussions on Github do the following:
# Create blueprint
api_bp = Blueprint('API', __name__, url_prefix='/api')
# Create Api instance only passing the blueprint
api = Api(blueprint=api_bp)
# register routes
api.route(PostList, 'post_list', '/posts')
api.route(PostDetail, 'post_detail', '/posts/<int:id>')
# initialize Api instance to App
api.init_app(app)
Don't forget that the view names will change. i.e view 'post_list' becomes 'API.post_list' so you have to adjust your schemes, but not your route declarations. This is also discussed in the linked Github discussion.
You can use flask and Flask-RESTful here. You just need to create flask APP first and configure Flask-RESTful API with prefix.
Here is the example
from flask import Flask, request
from flask_restful import Api
app = Flask(__name__)
api = Api(app, prefix="/api")
Now you can add resource to the api.route as below
import types
api.route = types.MethodType(api_route, api)
def api_route(self, *args, **kwargs):
"""Add resource to the api route"""
def wrapper(cls):
self.add_resource(cls, *args, **kwargs)
return cls

Flask Restplus - The requested URL was not found on the server

I have a flask application and I'm trying to use flask-restplus and blueprints. Unfortunately my api endpoint always returns The requested URL was not found on the server. even though I can see that it exists in the output of app.url_map.
The project is laid out as follows:
- app.py
- api
- __init__.py
- resources.py
app.py
from api import api, api_blueprint
from api.resources import EventListResource, EventResource
app = Flask(__name__)
app.register_blueprint(api_blueprint)
db.init_app(flask_app)
app.run()
api/__init__.py
from flask_restplus import Api
from flask import Blueprint
api_blueprint = Blueprint("api_blueprint", __name__, url_prefix='/api')
api = Api(api_blueprint)
api/resources.py
from flask_restplus import Resource
from flask import Blueprint
from . import api, api_blueprint
#api_blueprint.route('/events')
class EventListResource(Resource):
def get(self):
"stuff"
return items
def post(self):
"stuff"
db.session.commit()
return event, 201
The application starts without issue and I can see that '/api/events' appears in app.url_map so I'm not really sure why the url can't be found. Any help appreciated, thanks!
Flask-RESTPlus provides a way to use almost the same pattern as Flaskā€™s blueprint. The main idea is to split your app into reusable namespaces.
You can do it this way:
app.py
from flask_restplus import Api
from api import api_namespace
app = Flask(__name__)
api = Api(app)
db.init_app(flask_app)
from api import api_namespace
api.add_namespace(api_namespace, path='/api')
app.run()
api/init.py
from flask_restplus import Namespace
api_namespace = Namespace('api_namespace')
api/resources.py
from flask_restplus import Resource
from api import api_namespace
#api_namespace.route('/events')
class EventListResource(Resource):
def get(self):
"stuff"
return items
def post(self):
"stuff"
db.session.commit()
Here is the link to documentation:
https://flask-restplus.readthedocs.io/en/stable/scaling.html

Python Flask-restful multiple api endpoints

How to check what route has been used?
Using #api with Flask-restful and Python at the moment I'm not doing it in a clean way by checking api.endpoint value.
How do I do it correctly?
#api.route('/form', endpoint='form')
#api.route('/data', endpoint='data')
class Foobar(Resource):
def post(self):
if api.endpoint == 'api.form':
print('form')
elif api.endpoint == 'api.data':
print('data')
EDIT:
Should I split it into two classes?
I am in no way a professional with flask so please do take my answer with a grain of salt. First of all I would definitely split it into 2 different classes just to have a better overview of what you are doing. Also as a rule of thumb I would always split the apis and write its own logic for a higher degree of granularity.
Second if you want to have a look at https://flask-restful.readthedocs.io/en/latest/api.html#flask_restful.Api.owns_endpoint. This might be of assistance for you.
I am new with python and flask.
I think something like the following should work for you:
from flask import Flask, request
from flask_restful import Api, Resource
app = Flask(__name__)
api = Api(app)
class Data(Resource):
def post(self):
print("data")
return{"type": "data"}
class Form(Resource):
def post(self):
print("form")
return{"type": "form"}
api.add_resource(Form, '/form')
api.add_resource(Data, '/data')
if __name__ == "__main__":
app.run(port=8080)
Also you have use seperate files for the classes for a cleaner code like:
form.py
from flask_restful import Resource
class Form(Resource):
def post(self):
print("form")
return{"type": "form"}
data.py
from flask_restful import Resource
class Data(Resource):
def post(self):
print("data")
return{"type": "data"}
services.py
from flask import Flask, request
from flask_restful import Api
from data import Data
from form import Form
app = Flask(__name__)
api = Api(app)
api.add_resource(Form, '/form')
api.add_resource(Data, '/data')
if __name__ == "__main__":
app.run(port=8080)
Hope this helps.

How to generate a machine readable yaml specification of an existing API written in flask-restplus?

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)

How to protect custom endpoints using BasicAuth?

Say I have enabled authentication to the resources using BasicAuth:
class MyBasicAuth(BasicAuth):
def check_auth(self,username,password,allowed_roles,resource,method):
return username == 'secretusername' and password == 'secretpass'
I also have custom routes which are used to manage documents from a HTML view. How do I use the same MyBasicAuth to protect the all the custom routes? I also need to implement logic which authenticates using the above MyBasicAuth.
Please help me with this. It's for personal use, so I preferred hard coding the username and password.
If you are trying to use a custom end-point Authentication you will find it difficult as mentioned here:
https://github.com/pyeve/eve/issues/860
I ended up writing a wrapper to get around the issue of 'resource' not being passed to 'requires_auth':
def auth_resource(resource):
def fdec(f):
#wraps(f)
def wrapped(*args, **kwargs):
return f(resource=resource, *args, **kwargs)
return wrapped
return fdec
This way you can define in your DOMAIN an authentication class:
DOMAIN = {
'testendpoint'= {'authentication':MyCustomAuthetication},
'otherendpoints'=...
And in my app I have wrapped the requires_auth decorator and added this as a authentication resource.
#app.route('/testendpoint/<item>', methods=['GET'])
#auth_resource('testendpoint')
#requires_auth('item')
def my_end_point_function(*args, **kwargs):
dosomthinghere
As long as an authentication class is defined in the settings file for an endpoint, this also allows you to reuse any authentication defined in another endpoint which may be handy if you want to make sure all the endpoints use the same authentication.
You can leverage the requires_auth decorator which is used internally by Eve itself. That way, your auth class will also be used to protect your custom routes:
from eve import Eve
from eve.auth import requires_auth
app = Eve()
#app.route('/hello')
#requires_auth('resource')
def hello_world():
return 'Hello World!'
if __name__ == '__main__':
app.run()
If you are using flask blueprints for your custom routes, you can add a before request function for your blueprint to do that.
First, create a function to check authentication from blueprints. You need to get the Authorization header from the flask request by yourself, like this:
from flask import request, abort, current_app
from werkzeug.http import parse_authorization_header
def check_blueprint_auth():
if 'Authorization' not in request.headers:
print('Authorization header not found for authentication')
return abort(401, 'Authorization header not found for authentication')
header = parse_authorization_header(request.headers['Authorization'])
username = None if header is None else header['username']
password = None if header is None else header['password']
return username == 'secretusername' and password == 'secretpass'
Then, you can set this function to be called before each blueprint's request. Below is an example of a blueprint definition, setting the before_request function:
from flask import Blueprint, current_app as app
# your auth function
from auth import check_blueprint_auth
blueprint = Blueprint('prefix_uri', __name__)
# this sets the auth function to be called
blueprint.before_request(check_blueprint_auth)
#blueprint.route('/custom_route/<some_value>', methods=['POST'])
def post_something(some_value):
# something
Finally, you need to bind the blueprint with your eve app. An example on how to bind blueprints, taken in part from here:
from eve import Eve
# your blueprint
from users import blueprint
from flask import current_app, request
app = Eve()
# register the blueprint to the main Eve application
app.register_blueprint(blueprint)
app.run()
Hope that helps.

Categories

Resources