Blueprint error multiple dashapp into flask - flask

I'd like to host multiple dashapp into a flak server. Each dashapp shall be accessible with a login and password.
Some users can access different dashapps.
I tried the dash_auth.BasicAuth. It works perfectly but only for one dashapp.
So I tried to authenticate with flask_httpauth. Here again, it works well for one dashboard, but not for 2 and more because of blueprints.
My flask_app.py:
import dash
from flask import Flask, render_template, redirect, Blueprint
import dash_bootstrap_components as dbc
from flask_httpauth import HTTPDigestAuth
from apps.dashboard import Dashboard
app = Flask(__name__)
#app.route('/')
def hello_world():
return 'Hello from Flask!'
#others routes
auth = HTTPDigestAuth()
users = {
"john": "hello",
"susan": "bye"
}
#auth.get_password
def get_pw(username):
if username in users:
return users.get(username)
return None
url1 = '/dahsboard1'
dash_app1 = dash.Dash(__name__, server = app, external_stylesheets=[dbc.themes.BOOTSTRAP])
dash_app1.config.suppress_callback_exceptions = True
dash_app1.layout = Dashboard(dash_app1, 'data1', 'Title1', url1).layout
#app.route(url1)
#app.route(url1 + '/')
#app.route('/dash1')
#auth.login_required
def render_dashboard1():
return dash_app1.index()
url2 = '/dashboard2'
dash_app2 = dash.Dash(name='app2', server = app, external_stylesheets=[dbc.themes.BOOTSTRAP])
dash_app2.config.suppress_callback_exceptions = True
dash_app2.layout = Dashboard(dash_app2, 'data2', 'Title2', url2).layout
#app.route(url2)
#app.route(url2 + '/')
#app.route('/dash2')
#auth.login_required
def render_dashboard2():
return dash_app2.index()
if __name__ == '__main__':
app.run(debug=True)
The error:
ValueError: The name '_dash_assets' is already registered for a different blueprint. Use 'name=' to provide a unique name.
I undestand that a blueprint is created at each dashapp creation. After the first call :
print(app.blueprints)
returns
{'_dash_assets': <Blueprint '_dash_assets'>}
How can I add different blueprint names for each dashapp created ? Or more generally, how can I manage authentification for several dashapps running on one flask server ?
EDTIT:
I can solve this problem using this argument at dashboard creation
url_base_pathname = '/fake-url/'
But it leads to another problem: I can't protect this route with
#app.route('/fake-url/')
#auth.login_required(role=['admin'])
def render_dashboard():
return dash_app.app.index()
So the question is: how can I protect the route used in the dash creation with the argument url_base_pathname ?

You may have already solved this by now but will leave the solution here for the community. First you will need to set url_base_pathname for example:
dash_app2 = dash.Dash(
name='app2',
server = app,
url_base_pathname='/your_url_of_choice/'
external_stylesheets=[dbc.themes.BOOTSTRAP])
This will resolve that error.

Related

Separate routes in different files in Flask

I want to separate my /api endpoints from some others in another file.
Imagine I have this two files:
app.py ⤵
from flask import Flask
from routes.api_routes improt api
app = Flask(__name__)
app = app.register_blueprint(api)
app.route("/")
def index():
return "This is Index"
app.route("/about")
def about():
return "About page"
api_routes.py ⤵
from flask import Blueprint
api = Blueprint('api', __name__)
#api.route('/users')
def users():
return getUsers()
#api.route('/posts')
def posts():
return getPosts()
Let's say I want to access /api/users and /api/posts but I don't want to write the prefix "/api" before all of the endpoints that are on api_routes.py
How could I automatically add the prefix like you could do on Express.JS?
app.use("/api", apiRoute);
I thought on adding a PREFIX constant at the beggining of the file and appending the rest of the endpoint after it:
PREFIX = "/api"
...
#api.route(PREFIX+'/users')
What you can do is to set the Prefix when you are registering your blueprint like so:
app = app.register_blueprint(api, url_prefix='api')

flask-api-spec tags for grouping methods in swagger

I try to group my app-services in swagger. In my project, I use FlaskApiSpec to generate Swagger documentation for my Python-Flask application.
There is a part of code, how I generate swagger docs for my app, using Flask Blueprint pattern:
application/init.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_apispec.extension import FlaskApiSpec
from apispec.ext.marshmallow import MarshmallowPlugin
from apispec import APISpec
db = SQLAlchemy()
docs = FlaskApiSpec()
def create_app():
app = Flask(__name__)
app.config.update({
'APISPEC_SPEC': APISpec(
title = 'D&D Master Screen',
version = 'v0.1',
openapi_version = '2.0',
plugins = [MarshmallowPlugin()],
),
'APISPEC_SWAGGER_URL': '/api/swagger/',
'APISPEC_SWAGGER_UI_URL': '/api/swagger-ui/'
})
# Import Blueprints
from .characters.views import characters_bp
from .main.views import main_bp
# Registrate Blueprints
app.register_blueprint(characters_bp, url_prefix='/api/characters')
app.register_blueprint(main_bp, url_prefix='/')
# Initialize Plugins
db.init_app(app)
docs.init_app(app)
return app
application/characters/views.py
from flask import Blueprint
# from application.characters import characters_bp
from flask import jsonify
from flask_apispec import use_kwargs, marshal_with
from application import docs
from application.models import db, db_add_objects, db_delete_objects, Character
from application.schemas import CharacterSchema, ErrorSchema, CharacterIdSchema
characters_bp = Blueprint('characters_bp', __name__)
#characters_bp.route("/", methods=['GET'])
#marshal_with(CharacterSchema(many = True), code=200)
def characters():
characters = Character.get_all()
if characters is None:
return {"message": "Characters not found"}, 404
return characters
#characters_bp.route("/<character_id>", methods=['GET'])
#marshal_with(CharacterSchema, code=200)
#marshal_with(ErrorSchema, code=404)
def character(character_id):
character = Character.get(character_id)
if character is None:
return {"message": str("Character with id=" + character_id + " not found")}, 404
return character
#characters_bp.route("/", methods=['POST'])
#use_kwargs(CharacterSchema)
#marshal_with(CharacterIdSchema, code=200)
def create_character(**kwargs):
new_character = Character(**kwargs)
db_add_objects(new_character)
return {"id": new_character.id}
#characters_bp.route("/<character_id>", methods=['DELETE'])
#marshal_with(ErrorSchema, code=200)
#marshal_with(ErrorSchema, code=404)
def delete_character(character_id):
character = Character.get(character_id)
if character is None:
return {"message": str("Character with id=" + character_id + " not found")}, 404
db_delete_objects(character)
return jsonify({"message": "success"})
# Swagger docs for Characters Module services
blueprint_name = characters_bp.name
docs.register(character, blueprint = blueprint_name)
docs.register(characters, blueprint = blueprint_name)
docs.register(create_character, blueprint = blueprint_name)
docs.register(delete_character, blueprint = blueprint_name)
And result is
enter image description here
I want to group my /api/characters methods in one group in swagger, and name it correctly. I try to find a lot on the Internet, and learn something about tags is Swagger. But i don't understand, how to use this functionality in FlaskApiSpec
I suppose that tags can be added somewhere here:
docs.register(character, blueprint = blueprint_name)"
but don't understand how...
I also try for long time...
For me that works: just decorate your functions
#docs(tags=['characters'])
Yes, you can assign multiple methods under the same tag by using :
#docs(tags=['characters'])
Use same tag name for all the methods you want to show under the tag name. You can also add a description for every method separately in the tags.
Try this:
#doc(description='Describing GET method', tags=['charavters'])
def get():
<code here>
#doc(description='Describing POST method', tags=['charavters'])
def post():
<code here>

Authentication with GitLab to a terminal

I have a terminal that served in webbrowser with wetty. I want to authenticate the user from gitlab to let user with interaction with the terminal(It is inside docker container. When user authenticated i ll allow him to see the containers terminal).
I am trying to do OAuth 2.0 but couldn't manage to achieve.
That is what i tried.
I created an application on gitlab.
Get the code and secret and make a http call with python script.
Script directed me to login and authentication page.
I tried to get code but failed(Their is no mistake on code i think)
Now the problem starts in here. I need to get the auth code from redirected url to gain access token but couldn't figure out. I used flask library for get the code.
from flask import Flask, abort, request
from uuid import uuid4
import requests
import requests.auth
import urllib2
import urllib
CLIENT_ID = "clientid"
CLIENT_SECRET = "clientsecret"
REDIRECT_URI = "https://UnrelevantFromGitlabLink.com/console"
def user_agent():
raise NotImplementedError()
def base_headers():
return {"User-Agent": user_agent()}
app = Flask(__name__)
#app.route('/')
def homepage():
text = 'Authenticate with gitlab'
return text % make_authorization_url()
def make_authorization_url():
# Generate a random string for the state parameter
# Save it for use later to prevent xsrf attacks
state = str(uuid4())
save_created_state(state)
params = {"client_id": CLIENT_ID,
"response_type": "code",
"state": state,
"redirect_uri": REDIRECT_URI,
"scope": "api"}
url = "https://GitlapDomain/oauth/authorize?" + urllib.urlencode(params)
print get_redirected_url(url)
print(url)
return url
# Left as an exercise to the reader.
# You may want to store valid states in a database or memcache.
def save_created_state(state):
pass
def is_valid_state(state):
return True
#app.route('/console')
def reddit_callback():
print("-----------------")
error = request.args.get('error', '')
if error:
return "Error: " + error
state = request.args.get('state', '')
if not is_valid_state(state):
# Uh-oh, this request wasn't started by us!
abort(403)
code = request.args.get('code')
print(code.json())
access_token = get_token(code)
# Note: In most cases, you'll want to store the access token, in, say,
# a session for use in other parts of your web app.
return "Your gitlab username is: %s" % get_username(access_token)
def get_token(code):
client_auth = requests.auth.HTTPBasicAuth(CLIENT_ID, CLIENT_SECRET)
post_data = {"grant_type": "authorization_code",
"code": code,
"redirect_uri": REDIRECT_URI}
headers = base_headers()
response = requests.post("https://MyGitlabDomain/oauth/token",
auth=client_auth,
headers=headers,
data=post_data)
token_json = response.json()
return token_json["access_token"]
if __name__ == '__main__':
app.run(host="0.0.0.0",debug=True, port=65010)
I think my problem is on my redirect url. Because it is just an irrelevant link from GitLab and there is no API the I can make call.
If I can fire
#app.route('/console')
that line on Python my problem will probably will be solved.
I need to make correction on my Python script or different angle to solve my problem. Please help.
I was totally miss understand the concept of auth2. Main aim is to have access_token. When i corrected callback url as localhost it worked like charm.

How to access app.config in a blueprint?

I am trying to access access application configuration inside a blueprint authorisation.py which in a package api. I am initializing the blueprint in __init__.py which is used in authorisation.py.
__init__.py
from flask import Blueprint
api_blueprint = Blueprint("xxx.api", __name__, None)
from api import authorisation
authorisation.py
from flask import request, jsonify, current_app
from ..oauth_adapter import OauthAdapter
from api import api_blueprint as api
client_id = current_app.config.get('CLIENT_ID')
client_secret = current_app.config.get('CLIENT_SECRET')
scope = current_app.config.get('SCOPE')
callback = current_app.config.get('CALLBACK')
auth = OauthAdapter(client_id, client_secret, scope, callback)
#api.route('/authorisation_url')
def authorisation_url():
url = auth.get_authorisation_url()
return str(url)
I am getting RuntimeError: working outside of application context
I understand why that is but then what is the correct way of accessing those configuration settings?
----Update----
Temporarily, I have done this.
#api.route('/authorisation_url')
def authorisation_url():
client_id, client_secret, scope, callback = config_helper.get_config()
auth = OauthAdapter(client_id, client_secret, scope, callback)
url = auth.get_authorisation_url()
return str(url)
Use flask.current_app in place of app in the blueprint view.
from flask import current_app
#api.route("/info")
def get_account_num():
num = current_app.config["INFO"]
The current_app proxy is only available in the context of a request.
Overloading record method seems to be quite easy:
api_blueprint = Blueprint('xxx.api', __name__, None)
api_blueprint.config = {}
#api_blueprint.record
def record_params(setup_state):
app = setup_state.app
api_blueprint.config = dict([(key,value) for (key,value) in app.config.iteritems()])
To build on tbicr's answer, here's an example overriding the register method example:
from flask import Blueprint
auth = None
class RegisteringExampleBlueprint(Blueprint):
def register(self, app, options, first_registration=False):
global auth
config = app.config
client_id = config.get('CLIENT_ID')
client_secret = config.get('CLIENT_SECRET')
scope = config.get('SCOPE')
callback = config.get('CALLBACK')
auth = OauthAdapter(client_id, client_secret, scope, callback)
super(RegisteringExampleBlueprint,
self).register(app, options, first_registration)
the_blueprint = RegisteringExampleBlueprint('example', __name__)
And an example using the record decorator:
from flask import Blueprint
from api import api_blueprint as api
auth = None
# Note there's also a record_once decorator
#api.record
def record_auth(setup_state):
global auth
config = setup_state.app.config
client_id = config.get('CLIENT_ID')
client_secret = config.get('CLIENT_SECRET')
scope = config.get('SCOPE')
callback = config.get('CALLBACK')
auth = OauthAdapter(client_id, client_secret, scope, callback)
Blueprints have register method which called when you register blueprint. So you can override this method or use record decorator to describe logic which depends from app.
The current_app approach is fine but you must have some request context. If you don't have one (some pre-work like testing, e.g.) you'd better place
with app.test_request_context('/'):
before this current_app call.
You will have RuntimeError: working outside of application context , instead.
You either need to import the main app variable (or whatever you have called it) that is returned by Flask():
from someplace import app
app.config.get('CLIENT_ID')
Or do that from within a request:
#api.route('/authorisation_url')
def authorisation_url():
client_id = current_app.config.get('CLIENT_ID')
url = auth.get_authorisation_url()
return str(url)
You could also wrap the blueprint in a function and pass the app as an argument:
Blueprint:
def get_blueprint(app):
bp = Blueprint()
return bp
Main:
from . import my_blueprint
app.register_blueprint(my_blueprint.get_blueprint(app))
I know this is an old thread. But while writing a flask service, I used a method like this to do it. It's longer than the solutions above but it gives you the possibility to use customized class yourself. And frankly, I like to write services like this.
Step 1:
I added a struct in a different module file where we can make the class structs singleton. And I got this class structure from this thread already discussed. Creating a singleton in Python
class Singleton(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
else:
cls._instances[cls].__init__(*args, **kwargs)
return cls._instances[cls]
Step 2:
Then I created a Singleton EnvironmentService class from our Singleton class that we defined above, just for our purpose. Instead of recreating such classes, create them once and use them in other modules, routes, etc. import. We can access the class with the same reference.
from flask import Config
from src.core.metaclass.Singleton import Singleton
class EnvironmentService(metaclass=Singleton):
__env: Config = None
def initialize(self, env):
self.__env = env
return EnvironmentService()
def get_all(self):
return self.__env.copy()
def get_one(self, key):
return self.__env.get(key)
Step 3:
Now we include the service in the application in our project root directory. This process should be applied before the routes.
from flask import Flask
from src.services.EnvironmentService import EnvironmentService
app = Flask(__name__)
# Here is our service
env = EnvironmentService().initialize(app.config)
# Your routes...
Usage:
Yes, we can now access our service from other routes.
from src.services.EnvironmentService import EnvironmentService
key = EnvironmentService().get_one("YOUR_KEY")

unique session id for different dash sessions

I would like to create unique session ids for each time a user opens the dash app in browser.
I have been following the tutorial here:
https://dash.plot.ly/sharing-data-between-callbacks
This is my code:
import dash
import dash_html_components as html
import dash_core_components as dcc
import flask
import datetime
import uuid
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = flask.Flask(__name__)
dash_app = dash.Dash(__name__,server=app,url_base_pathname="/",external_stylesheets=external_stylesheets)
def serve_layout():
session_id = str(uuid.uuid4())
return html.Div([
html.Div(session_id, id='session-id', style={'display': 'none'}),
html.Div(dcc.Input(id="input_session_id",type="text",value=session_id))
])
dash_app.layout = serve_layout()
if __name__ == '__main__':
app.run(host='0.0.0.0', debug=True, port=80)
It seems like the session ids are different if I use different computers but if I use the same computer, it will stay the same.
Is there a way to generate an unique session each time an user opens the url for the dash app?
Obviously problem was in this line: dash_app.layout = serve_layout()
You had to use it without parenthesis:
dash_app.layout = serve_layout
In fact you were assigning not a function, but a result of the function called once at the first page load.