How to make customized middleware in CKAN - python

I've met a scenario which I have to override a common middleware in CKAN. And in CKAN default plugins/interface.py:
class IMiddleware(Interface):
u'''Hook into CKAN middleware stack
Note that methods on this interface will be called two times,
one for the Pylons stack and one for the Flask stack (eventually
there will be only the Flask stack).
'''
def make_middleware(self, app, config):
u'''Return an app configured with this middleware
When called on the Flask stack, this method will get the actual Flask
app so plugins wanting to install Flask extensions can do it like
this::
import ckan.plugins as p
from flask_mail import Mail
class MyPlugin(p.SingletonPlugin):
p.implements(p.I18nMiddleware)
def make_middleware(app, config):
mail = Mail(app)
return app
'''
return app
It shows that I have to define "MyMiddleWare" class under a plugin that I want to implement in an extension. However, as it shows in the example, the actual middleware Mail is imported from a different class. I want to override TrackingMiddleware especially the __call__(self, environ, start_response) method, which environ and start_response are passed in when make_pylons_stack are invoked during the configuration phase. If I want to override TrackingMiddleware Should I create another config/middleware/myTrackingMiddleware.py under ckanext-myext/ and then in plugin.py I implement the following?:
from myTrackingMiddleware import MyTrackingMiddleware
class MyPlugin(plugins.SingletonPlugin):
plugins.implements(plugins.IMiddleware, inherit=True)
def make_middleware(self, app, config):
tracking = MytrackingMiddleware(app, config)
return app
Update:
I tried to make the myTrackingMiddleware in an hierarchy and imported it in plugin.py, but I didn't received any request to '/_tracking' in myTrackingMiddleware.

I have implemented a set of process, and it works for myself. Basically, I kept what I have done as what I have mentioned in my own question. Then, if your middleware has some conflict with CKAN default Middleware, you probably have to completely disable the default one. I discussed with some major contributors of CKAN here: https://github.com/ckan/ckan/issues/4451. After I disabled CKAN ckan.tracking_enabled in dev.ini, I have the flexibility to get values from environ and handle tracking with my customized logic.

Related

Is there a way to move `import` statement to the top of Flask view file when service class references `current_app`?

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

How can a flask Addon overwrite a template?

I created a flask addon using "flask fab create-addon".
I would like to change the template appbuilder/general/security/login_oauth.html so I have:
templates
appbuilder
general
security
login_oauth.html
But when I load the host application my version of login_oauth.html is not loaded. I tried registering a blueprint as in this post with the following code:
from flask import Blueprint
bp = Blueprint('fab_addon_fslogin', __name__, template_folder='templates')
class MyAddOnManager(BaseManager):
def __init__(self, appbuilder):
"""
Use the constructor to setup any config keys specific for your app.
"""
super(MyAddOnManager, self).__init__(appbuilder)
self.appbuilder.get_app.config.setdefault("MYADDON_KEY", "SOME VALUE")
self.appbuilder.register_blueprint(bp)
def register_views(self):
"""
This method is called by AppBuilder when initializing, use it to add you views
"""
pass
def pre_process(self):
pass
def post_process(self):
pass
But register_blueprint(bp) return:
File "/home/cquiros/data/projects2017/personal/software/superset/addons/fab_addon_fslogin/fab_addon_fslogin/manager.py", line 24, in __init__
self.appbuilder.register_blueprint(bp)
File "/home/cquiros/data/projects2017/personal/software/superset/env_superset/lib/python3.8/site-packages/Flask_AppBuilder-3.3.0-py3.8.egg/flask_appbuilder/base.py", line 643, in register_blueprint
baseview.create_blueprint(
AttributeError: 'Blueprint' object has no attribute 'create_blueprint'
Not much information out there on how to do this. Any clue is appreciated
If you want to customize login_oauth.html, The easiest way is that adding it into your app directly not addon.
That means the login_oauth.html should be put in this path.
YourApp
- app
-- templates
--- appbuilder
---- general
----- security
------ login_oauth.html
For solution of addons, the function self.appbuilder.register_blueprint is for views not for the object which Blueprint fuction returns. It should be replaced with
self.appbuilder.get_app.register_blueprint(Blueprint('fab_addon_fslogin', __name__, template_folder='templates'))
Keep the blueprint registration and
try to rename the login_oauth.html to login_oauth_xxxx.html which at
{your python packages root path}\site-packages\flask_appbuilder\templates\appbuilder\general\security
That will let the template be overwrote as you need. I guess that the template searching order of app-builder package is greater than addons. The searching order of blueprints depend on order of registrations
Finally, I have found a trick without renaming the file, you could try the following
class MyAddOnManager(BaseManager):
def __init__(self, appbuilder):
"""
Use the constructor to setup any config keys specific for your app.
"""
super(MyAddOnManager, self).__init__(appbuilder)
self.appbuilder.get_app.config.setdefault('MYADDON_KEY', 'SOME VALUE')
self.static_bp = Blueprint('fab_addon_fslogin', __name__, template_folder='templates')
def register_views(self):
"""
This method is called by AppBuilder when initializing, use it to add you views
"""
pass
def pre_process(self):
self.appbuilder.get_app.register_blueprint(self.static_bp)
blueprint_order = self.appbuilder.get_app._blueprint_order
# move blueprint order of addon to top
blueprint_order.insert(0, blueprint_order.pop())
def post_process(self):
pass
Reference:
flask-appbuilder Customizing

How to override python pyramid app config settings?

I am a newbie at Python Pyramid and working on improving an existing app that we have.
I have an app main function defined like below:
def web_main(global_config, **settings):
config = Configurator(settings=settings, root_factory=RootFactory)
...
...
config.add_request_method(get_user, "user", reify=True)
config.set_authentication_policy(authn_policy)
config.set_authorization_policy(authz_policy)
...
app = config.make_wsgi_app()
return app
I want to override get_user request method with my implementation and also want to use my own authentication policy.
With that I was thinking to do write a function like below:
def my_web_main(global_config, **settings):
app = web_main(global_config, **settings)
<Set Overrides here>
return app
Inside the config.ini file I will call my_web_main to start this app.
I have not been able to figure out how to set the overrides. Would appreciate some inputs on this.
The configurator is where you should perform overrides. So the answer is to modify web_main or define your own. Pyramid has an override mechanism via config.include(), but it does not work one level higher where you're attempting to override things with a built wsgi-app. You have to do it at the config level.

flask blueprint template folder

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

Where to put Django startup code?

I'd like to have these lines of code executed on server startup (both development and production):
from django.core import management
management.call_command('syncdb', interactive=False)
Putting it in settings.py doesn't work, as it requires the settings to be loaded already.
Putting them in a view and accessing that view externally doesn't work either, as there are some middlewares that use the database and those will fail and not let me access the view.
Putting them in a middleware would work, but that would get called each time my app is accessed. An possible solution might be to create a middleware that does all the job and then removes itself from MIDDLEWARE_CLASSES so it's not called anymore. Can I do that without too much monkey-patching?
Write middleware that does this in __init__ and afterwards raise django.core.exceptions.MiddlewareNotUsed from the __init__, django will remove it for all requests :). __init__ is called at startup by the way, not at the first request, so it won't block your first user.
There is talk about adding a startup signal, but that won't be available soon (a major problem for example is when this signal should be sent)
Related Ticket: https://code.djangoproject.com/ticket/13024
Update: Django 1.7 includes support for this. (Documentation, as linked by the ticket)
In Django 1.7+ if you want to run a startup code and,
1. Avoid running it in migrate, makemigrations, shell sessions, ...
2. Avoid running it twice or more
A solution would be:
file: myapp/apps.py
from django.apps import AppConfig
def startup():
# startup code goes here
class MyAppConfig(AppConfig):
name = 'myapp'
verbose_name = "My Application"
def ready(self):
import os
if os.environ.get('RUN_MAIN'):
startup()
file: myapp/__init__.py
default_app_config = 'myapp.apps.MyAppConfig'
This post is using suggestions from #Pykler and #bdoering
If you were using Apache/mod_wsgi for both, use the WSGI script file described in:
http://blog.dscpl.com.au/2010/03/improved-wsgi-script-for-use-with.html
Add what you need after language translations are activated.
Thus:
import sys
sys.path.insert(0, '/usr/local/django/mysite')
import settings
import django.core.management
django.core.management.setup_environ(settings)
utility = django.core.management.ManagementUtility()
command = utility.fetch_command('runserver')
command.validate()
import django.conf
import django.utils
django.utils.translation.activate(django.conf.settings.LANGUAGE_CODE)
# Your line here.
django.core.management.call_command('syncdb', interactive=False)
import django.core.handlers.wsgi
application = django.core.handlers.wsgi.WSGIHandler()
You can create a custom command and write your code in the handle function. details here https://docs.djangoproject.com/en/dev/howto/custom-management-commands/
Then you can create a startup script that runs the django server then executes your new custom command.
If you are using mod_wsgi you can put it in the wsgi start app
Here is how I work around the missing startup signal for Django:
https://github.com/lsaffre/djangosite/blob/master/djangosite/models.py
The code that is being called there is specific to my djangosite project, but the trick to get it called by writing a special app (based on an idea by Ross McFarland) should work for other environments.
Luc

Categories

Resources