I am trying to use a global configuration when defining an ElasticSearch DSL model, which is more or less a regular Python class aka service class.
"""Define models"""
from elasticsearch_dsl import Document, Text
from flask import current_app
class Greeting(Document):
"""Define Greeting model"""
message = Text()
class Index:
name = current_app.config['GREETINGS_INDEX']
def save(self, ** kwargs):
return super().save(** kwargs)
Unfortunately, if my import statement is at the top of the view file, I get this error message:
RuntimeError: Working outside of application context.
This typically means that you attempted to use functionality that needed
to interface with the current application object in some way. To solve
this, set up an application context with app.app_context(). See the
documentation for more information.
The only way to get things to work is if I import the model/service class inside the request like this:
from elasticsearch_dsl import Search
from flask import Blueprint, current_app
# from .models import Greeting ### this will throw the application context error
greetings = Blueprint(
'greetings',
__name__,
url_prefix='/greetings/'
)
...
#greetings.route("/elasticsearch/new/")
def new_greeting_using_elasticsearch():
from .models import Greeting ### this avoids the application context error
Greeting.init()
greeting = Greeting(message="hello, elastic")
greeting.save()
return(
"a greeting was saved; "
"it is viewable from https://localhost:5000/greetings/elasticsearch/"
)
This seems like a code smell. Is there another way to accomplish using reusing configurations that can keep the import statement at the top of the file?
These questions/answers seem to suggest that this is the only way:
How to access config value outside view function in flask
Flask - RuntimeError: Working outside of application context
Am I missing something? Should I rearchitect my application to avoid this? Or is this just a Flask-way/thing?
Thank you for your help 🙏
Other questions/answers/articles that did not help me:
"RuntimeError: Working outside of application context " with Python Flask app ( Sending gmail using scheduler )
https://flask.palletsprojects.com/en/0.12.x/appcontext/#creating-an-application-context
Access config values in Flask from other files
RuntimeError: working outside of application context
Python #property in Flask configs?
Reading properties from config file with Flask and Python
I am trying something probably highly unorthodox: I need to pass an argument, that comes in via the url path to the class constructor of a class-based MethodView.
http://127.0.0.1:5000/child/my_id_string
I want to pass my_id_string to the following constructor as arg1.
My most promissing try was based on this question, but I need to use class-based views instead of functions. Unfortunately the logic behind this call is somewhat more complex than the example, i.e I cannot simply refactor the code to not use "my_id" in the constructor.
from flask import Flask, request
from flask.views import MethodView
BASE = 11
app = Flask('mybase')
class Child(MethodView):
def __init__(self, base, arg1=None):
self.base = base
print('some init process, where I need arg1...')
def get(self, arg1):
return f'Some operation with {str(arg1)}.'
app.add_url_rule(
'/child/<arg1>',
'child',
view_func=Child.as_view(
'child_view',
BASE,
arg1 = request.args.get('my_id')
),
methods=['GET',]
)
I get the following error with the snippet as is, because I figure the registration occurs before/without a specific request, is that correct?
Hope, someone is able to help. Thanks in advance!
RuntimeError: Working outside of request context.
This typically means that you attempted to use functionality that needed
an active HTTP request. Consult the documentation on testing for
information about how to avoid this problem.
I am a little confused about your syntax of :
app = flask("mybase") :i use app = flask(__name__)
But this is what i would do.
#app.route("/child/<arg1>")
def x(arg1):
varArg1 = arg1
#now you have whatever is in the url will be passed into varArg1.
Function description
In flask this code:
#app.route('/')
def index():
return ...
or
foo_router = Blueprint('foo', __name__)
#foo_router.route('/')
def index():
return ...
app.register_blueprint(foo_router, url_prefix='/api')
These ways you are able to use url_for('index') to retrieve url like https://hostname:8080/
or use url_for('foo.index') to retrive url like https://hostname:8080/api/foo
Desired function
Use url_for for jump link in fastapi like flask
Additional context
I have found a trick to do the implement
create url_naming.py and add the code below
from starlette.routing import Mount, Route
from .routers.foo.endpoints.area import index as foo_area_index
foo_router_for = Mount('/', routes=[
Route('/area', foo_area_index, name='foo_area.index'),
])
and in your main.py add
from .url_naming import foo_router_for
app.add_route('/foo', foo_router_for, name='foo_area.index')
In the codeblocks, I define name twice because which name I defined in a Route model cannot be detected by FastAPI, or it is overridden by FastAPI, and this impl make no sense because it is required to write down the real router name rather than the endpoint function name.
Despite that, what if I'd like to name my router in one file, which do not make effect on main.py?
I solve the problem with the following solution.
Just to name your handle function (endpoint) and the name can be normally used by url_for.
router = APIRouter(prefix='/api')
#router.get('/foo')
async def api_foo():
return ...
This way you can retrieve the url http://hostname:8080/api/foo/ using url_for('api_foo').
I guess this is because all the handle functions in FastAPI are named in the same space, and if they are named in FastAPI repeatedly, no error will be reported.
Take for example the following two routes.
app = Flask(__name__)
#app.route("/somewhere")
def no_trailing_slash():
#case one
#app.route("/someplace/")
def with_trailing_slash():
#case two
According to the docs the following is understood:
In case one, a request for the route "/somewhere/" will return a 404 response. "/somewhere" is valid.
In case two, "/someplace/" is valid and "/someplace" will redirect to "/someplace/"
The behavior I would like to see is the 'inverse' of the case two behavior. e.g. "/someplace/" will redirect to "/someplace" rather than the other way around. Is there a way to define a route to take on this behavior?
From my understanding, strict_slashes=False can be set on the route to get effectively the same behavior of case two in case one, but what I'd like to do is get the redirect behavior to always redirect to the URL without the trailing slash.
One solution I've thought of using would be using an error handler for 404's, something like this. (Not sure if this would even work)
#app.errorhandler(404)
def not_found(e):
if request.path.endswith("/") and request.path[:-1] in all_endpoints:
return redirect(request.path[:-1]), 302
return render_template("404.html"), 404
But I'm wondering if there's a better solution, like a drop-in app configuration of some sort, similar to strict_slashes=False that I can apply globally. Maybe a blueprint or url rule?
You are on the right tracking with using strict_slashes, which you can configure on the Flask app itself. This will set the strict_slashes flag to False for every route that is created
app = Flask('my_app')
app.url_map.strict_slashes = False
Then you can use before_request to detect the trailing / for a redirect. Using before_request will allow you to not require special logic to be applied to each route individually
#app.before_request
def clear_trailing():
from flask import redirect, request
rp = request.path
if rp != '/' and rp.endswith('/'):
return redirect(rp[:-1])
If you want both routes to be handled the same way, I would do this:
app = Flask(__name__)
#app.route("/someplace/")
#app.route("/someplace")
def slash_agnostic():
#code for both routes
You can also use the option strict_slashes=False in your route definition:
app.Flask(__name__)
#app.route("/someplace", strict_slashes=False)
# Your code goes here
My flask app layout is:
myapp/
run.py
admin/
__init__.py
views.py
pages/
index.html
main/
__init__.py
views.py
pages/
index.html
_init_.py files are empty. admin/views.py content is:
from flask import Blueprint, render_template
admin = Blueprint('admin', __name__, template_folder='pages')
#admin.route('/')
def index():
return render_template('index.html')
main/views.py is similar to admin/views.py:
from flask import Blueprint, render_template
main = Blueprint('main', __name__, template_folder='pages')
#main.route('/')
def index():
return render_template('index.html')
run.py is:
from flask import Flask
from admin.views import admin
from main.views import main
app = Flask(__name__)
app.register_blueprint(admin, url_prefix='/admin')
app.register_blueprint(main, url_prefix='/main')
print app.url_map
app.run()
Now, if I access http://127.0.0.1:5000/admin/, it correctly displays admin/index.html.
However, http://127.0.0.1:5000/main/ shows still admin/index.html instead of main/index.html. I checked app.url_map:
<Rule 'admin' (HEAD, OPTIONS, GET) -> admin.index,
<Rule 'main' (HEAD, OPTIONS, GET) -> main.index,
Also, I verified that index function in main/views.py is called as expected.
If I rename main/index.html to something different then it works. So, without
renaming, how can achieve that 1http://127.0.0.1:5000/main/1 shows main/index.html?
As of Flask 0.8, blueprints add the specified template_folder to the app's searchpath, rather than treating each of the directories as separate entities. This means that if you have two templates with the same filename, the first one found in the searchpath is the one used. This is admittedly confusing, and is poorly documented at this time (see this bug). It seems that you weren't the only one that was confused by this behavior.
The design reason for this behavior is so that blueprint templates can be easily overriden from the main app's templates, which are first-in-line in Flask's template searchpath.
Two options come to mind.
Rename each of the index.html files to be unique (e.g. admin.html
and main.html).
In each of the template folders, put each of the
templates in a subdirectory of the blueprint folder and then call
the template using that subdirectory. Your admin template, for example, would be yourapp/admin/pages/admin/index.html, and then called from within
the blueprint as render_template('admin/index.html').
In addition to linqq's good suggestions above, you can also override the default functionality if needed. There are a couple ways:
One can override create_global_jinja_loader in a subclassed Flask application (which returns a DispatchingJinjaLoader defined in flask/templating.py). This is not recommended, but would work. The reason that this is discouraged is that the DispatchingJinjaLoader has enough flexiblity to support the injection of custom loaders. And if you screw your own loader up, it'll be able to lean on default, sane functionality.
So, what is recommended is that one "override the jinja_loader function" instead. This is where lack of documentation comes in. Patching Flask's loading strategy requires some knowledge that doesn't seem to be documented, as well as a good understanding of Jinja2.
There are two components you need to understand:
The Jinja2 environment
The Jinja2 template loader
These are created by Flask, with sensible defaults, automatically. (You can specify your own Jinja2 options, by the way, by overriding app.jinja_options -- but bear in mind that you'll lose two extensions which Flask includes by default -- autoescape and with -- unless you specify them yourself. Take a look at flask/app.py to see how they reference those.)
The environment contains all of those context processors (e.g., so you can do var|tojson in a template), helper functions (url_for, etc) and variables (g, session, app). It also contains a reference to a template loader, in this case the aforementioned and auto-instantiated DispatchingJinjaLoader. So when you call render_template in your app, it finds or creates the Jinja2 environment, sets up all those goodies, and calls get_template on it, which in turn calls get_source inside of the DispatchingJinjaLoader, which tries a few strategies described later.
If all goes according to plan, that chain will resolve in finding a file and will return its contents (and some other data). Also, note that this is the same execution path that {% extend 'foo.htm' %} takes.
DispatchingJinjaLoader does two things: First it checks if the app's global loader, which is app.jinja_loader can locate the file. Failing that, it checks all application blueprints (in order of registration, AFAIK) for blueprint.jinja_loader in an attempt to locate the file. Tracing that chain to the very end, here is definition of jinja_loader (in flask/helpers.py, _PackageBoundObject, the base class of both the Flask application and Blueprints):
def jinja_loader(self):
"""The Jinja loader for this package bound object.
.. versionadded:: 0.5
"""
if self.template_folder is not None:
return FileSystemLoader(os.path.join(self.root_path,
self.template_folder))
Ah! So now we see. Obviously, the namespaces of both will conflict over the same directory names. Since the global loader is called first, it will always win. (FileSystemLoader is one of several standard Jinja2 loaders.) However, what this means is that there's no truly simple way to reorder the Blueprint and the application-wide template loader.
So, we need to modify the behavior of DispatchingJinjaLoader. For a while, I thought there was no good non-discouraged and efficient way of going about this. However, apparently if you override app.jinja_options['loader'] itself, we can get the behavior we want. So, if we subclass DispatchingJinjaLoader, and modify one small function (I suppose it might be better to reimplement it entirely, but this works for now), we have the behavior we want. In total, a reasonable strategy would be the following (untested, but should work with modern Flask applications):
from flask.templating import DispatchingJinjaLoader
from flask.globals import _request_ctx_stack
class ModifiedLoader(DispatchingJinjaLoader):
def _iter_loaders(self, template):
bp = _request_ctx_stack.top.request.blueprint
if bp is not None and bp in self.app.blueprints:
loader = self.app.blueprints[bp].jinja_loader
if loader is not None:
yield loader, template
loader = self.app.jinja_loader
if loader is not None:
yield loader, template
This modifies the strategy of the original loader in two ways: Attempt to load from the blueprint (and ONLY the currently executing blueprint, not all blueprints) first, and if that fails, only then load from the application. If you like the all-blueprint behavior, you can do some copy-pasta from flask/templating.py.
To tie it all together, you have to set jinja_options on the Flask object:
app = Flask(__name__)
# jinja_options is an ImmutableDict, so we have to do this song and dance
app.jinja_options = Flask.jinja_options.copy()
app.jinja_options['loader'] = ModifiedLoader(app)
The first time a template environment is needed (and thus instantiated), meaning the first time render_template is called, your loader should be used.
twooster's answer is interesting, but another problem is that Jinja by default caches a template based on its name. Because both templates are named "index.html", the loader won't run for subsequent blueprints.
Besides linqq's two suggestions, a third option is to ignore the blueprint's templates_folder option all together and place the templates in respective folders in the application's templates directory.
ie:
myapp/templates/admin/index.html
myapp/templates/main/index.html
Tks #linqq, your method really works well here, besides I made a better solution by the decorator.
Attention here, don't import the render_template function like this:
from flask import render_template
You should import the flask module like this:
import flask
Then, make this block of code at the top of your router file:
def render_decorate(path_prefix):
def decorate(func):
def dec_func(*args, **kw):
arg_list = list(args)
arg_list[0] = path_prefix + str(arg_list[0])
arg_tuple = tuple(arg_list)
return func(*arg_tuple, **kw)
return dec_func
return decorate
#render_decorate("%YOUR_DIRECTORY_NAME%/")
def render_template(template_name_or_list, **context):
return flask.render_template(template_name_or_list, **context)
Replace the %YOUR_DIRECTORY_NAME% with your actual path, and ensure your templates folder is like this:
Folder Structure
And all done! Just use the render_template function as usual.
I'm using something like this on fypress and fybb because I have a theme system.
# utils.templates
from jinja2 import Environment, PackageLoader
from flask.templating import _default_template_ctx_processor
from flask import current_app, url_for, get_flashed_messages
admin_env = Environment(
loader=PackageLoader('fypress', '/templates/admin/'),
extensions=['jinja2.ext.autoescape', 'jinja2.ext.with_'],
autoescape=True
)
def render_template(template, **kwargs):
kwargs.update(_default_template_ctx_processor())
kwargs.update({
'url_for': url_for,
'get_flashed_messages': get_flashed_messages # etc...
})
kwargs.update(dict(debug=current_app.config.get('DEBUG'), flask_config=current_app.config))
template = admin_env.get_template(template)
return template.render(**kwargs)
And then
# routes.admin.
from flask import Blueprint
from utils.templates import render_template
admin_bp = Blueprint('admin', __name__, url_prefix='/admin')
#admin_bp.route('/')
def root():
return render_template('index.html', title='Admin')
Currently this is what works for me. First search the template in the blueprint templates folder, if not found search in the app templates folder (for layout, etc.).
from jinja2 import BaseLoader, TemplateNotFound
from flask import Flask, current_app, request
class BlueprintLoader(BaseLoader):
def get_source(self, environment, template):
for loader in (current_app.blueprints[request.blueprint].jinja_loader, current_app.jinja_loader):
try:
if loader:
return loader.get_source(environment, template)
except TemplateNotFound:
pass
raise TemplateNotFound(template)
app = Flask(__name__)
app.jinja_env.loader = BlueprintLoader()
An easy fix would be to specify the template_folder = "templates" and then on rendering the template you would then specify the blueprint name as the parent directory as shown below
#users.route("/")
def users_index():
return render_template('users/index.html')
Note that the above solution works only if you have created a sub-folder with the name of the blueprint first inside the templates folder which will be under your blueprint