How to manage multiple similar app routes in flask application? - python

Alt: How to use a variable and loop it in Flask app route such that it handles multiple webpages which have similar permalinks (i'm not sure what that is though).
This is a snippet of a simple example to demostrate what I mean:
#app.route('/say-hii-1')
def say_hii_1():
return """<h1>Hello 1!</h1>"""
#app.route('/say-hii-2')
def say_hii_2():
return """<h1>Hello 2!</h1>"""
#app.route('/say-hii-3')
def say_hii_3():
return """<h1>Hello 3!</h1>"""
You see, only the number changes in all routes, and the return values also have a pattern. My project is much more complex and has 10-20 such routes.Is there a way, in which I can reduce all of them to just one route?
Some information that you might need:
OS: Ubuntu 19.10
Python version: 3.7.5

What you're looking for is flask route parameters/dynamic routes/variable rules
Route parameters allow you to match your application's route to a pattern, by inserting variables with carats <>. If the url matches the pattern, it'll pass the variable into the function your route is linked to.
From there, you can do whatever sort of dyanmic behaviour you want, as per usual python.
As an example, you could implement what you're asking for in your example as follows:
#app.route('/say-hii-<int:hi_number>')
def say_hii(hi_number):
return "<h1>Hello " + str(hi_number) + "</h1>"
For more information, you can have a look at the flask quickstart:
https://flask.palletsprojects.com/en/1.1.x/quickstart/#routing
This is a fairly common use case, so be sure to look through the flask quickstart and guides next time!

Related

Web2Py Production - making redirections on default index with parameters (maybe with nginx)

I am trying to create redirects using web2py from effectively the default index page (or just the route of the domain/package).
Some keywords (such as 'about', stored in a list) wouldn't redirect. However, all not in that list would redirect.
The desired behaviour is:
https://startbean.com/about -> No redirect
https://startbean.com/myc -> https://startbean.com/company/myc
The default page that is shown at startbean.com is from the package 'default' and is called 'index'. If the redirect was as in the below, it would be easy:
https://startbean.com/default/about -> No redirect
https://startbean.com/default/index/myc -> https://startbean.com/default/company/myc
because the myc is a URL argument. But when it is from the root, Web2Py tries to open a package called 'myc' and then finds no page (index or controller function) so errors.
What is the best way of handling this? I was trying with routes.py, but couldn't figure out a way to do this (am pretty sure it is not supported). I thought about a redirect for the token after / to a page called /default/redirect/<token> which would then decide about the redirect, but there's no way to stop the infinite loop. Another possible solution was a tweak to the nginx config so redirect when there is one token after the /, but again I think this causes a problem with the about.
Maybe there is a catch-all function for controllers that I haven't found? I've gone through the web2py book and found nothing - any ideas very welcome!
Thanks,
Sam
You can use the parameter-based rewrite system. In routes.py include:
routers = dict(
BASE=dict(
default_application='yourapp',
default_controller='default',
default_function='company',
functions=['index', 'company', 'user', 'download', 'call']
),
)
Note, functions should be a list of all functions in the default.py controller.
If you go to /some_token, it will route to /yourapp/default/company/some_token, unless some_token is "yourapp", "default", "company", or any of the functions in the functions list (in which case, it will assume you are actually requesting the "yourapp" app, the "default" controller, or the particular function from the controller).
Note, if you simply go to the root URL (i.e., http://yourdomain.com/), it will route to /yourapp/default/company/, with no URL arg, so you should be prepared for that case.
So I found the solution (sorry for the delay in posting):
In the routes.py at the route of web2py directory, I added a rule in routes_in, so that this was in the my file:
routes_in = (
('/(?!about)$token', '/company/$token'),
)
to manage the default app (removing the application name and the default package name from the URL), I did this (not necessary for the redirects to work):
routers = dict(
BASE = dict(default_application='startbean'),
)
And it all worked :)

Contents of request.endpoint[:5]

I'm going through Miguel Grinbergs Flask book and at one point he uses the following line:
request.endpoint[:5] != 'auth.'
I know [:5] is a slice operation, but I'm not sure why it is being used here. What does the list consist of that we only want elements 0-5?
What does the list consist of that we only want elements 0-5?
To be precise, request.endpoint is not a list, it's a string. And it doesn't matter what the rest of it contains, the code is only concerned with it beginning with 'auth.':
('auth.somethingsomething'[:5] == 'auth.') is True
request.endpoint is the name the current view function was registered as, for example auth.login is the name of the def login(): view. Views that have a prefix like prefix. were registered on a blueprint, which groups related views. So the code is checking if the current view being handled is part of the auth blueprint.
If you're curious about what value it contains, you can add a debugging breakpoint to the code and inspect it:
# ... previous app code ...
import pdb; pdb.set_trace()
request.endpoint[:5] != 'auth.'
Then run and test the code. When it hits that point, it'll pause execution and give you a pdb shell, which will let you look at the request object and its endpoint attribute.
you can checking on terminal by
venv $ python manage.py shell
import flask from request
print(request.endpoint)

Unit testing Flask Babel translations

I'd like to do some unit tests to check my flask app translations. I have tried this piece of code:
def test_pt_br(self):
with app.test_request_context():
app.config['BABEL_DEFAULT_LOCALE'] = 'pt_BR'
rv = app.test_client().get('/')
assert 'Execute, melhore' in str(rv.data)
However, it does not work/pass although the app runs fine. What am I doing wrong?
The code you have shown seems to work for me. Please see here complete example based on your description: https://github.com/loomchild/flask_babel_test. When I run ./flask_babel_test_test.py both tests pass.
Could you provide complete source code that allows to reproduce the problem?
Currently I can imagine the following solutions (both of them are present in commented-out sections in the example code linked above):
There is some caching involved - try to execute flask.ext.babel.refresh() after updating default locale during the test and see if it helps.
If you retrieve browser language automatically from Accept-Language HTTP header using localeselector, for example like this:
#babel.localeselector
def get_locale():
translations = [str(translation) for translation in babel.list_translations()]
return request.accept_languages.best_match(translations)
Then instead of modifying app config during the test, specify a header:
rv = app.test_client().get('/', headers=[("Accept-Language", "pt_BR")])
Flask-Babel cannot find the translations directory during testing. It looks for them in app["BABEL_TRANSLATION_DIRECTORIES"] config setting (translations by default). The path can be absolute or relative to app.root_path (print this variable in your test if you are not sure where it points to). You can specify multiple paths separated by ;.

How to abstract 3rd party implementation details from the core functionality?

Here is an example of the problem and requirement .
Scenario : We have a Web App , The Controller has a function that does HTML to PDF conversion . We have to switch to various engines depending on the requirement and advancement in Technology . This means we need to keep changing the core file . It can even be a web service that does my job and not implemented locally .
Overall the current architecture in pseudo Python is :
Controller
def requestEntry():
"""Entry point"""
html = REQUEST['html'] , css = REQUEST['css']
createPDF(html,css)
def createPDF(html,css):
"""Sigh"""
#Hardcoding Begines
configured_engine = getXMLOption('configured_engine')
if configured_engine == "SuperPDFGen":
#Returns SuperGen's PDF Blob
supergen = SuperGen(html,css)
return supergen.pdfout()
if configured_engine = "PISA":
pisa = PISA(html,css)
return pisa.pdf_out()
The problem here is every new engine requirement is a code change in the controller .And suppose we find a new technology we have to upgrade the core software version .
Solving it :
One way I can think of is to define a simple class like this :
def createPdf(data,engine):
pdf_render = PDFRender(html,css,engine)
return pdf_render.pdf_out()
So now we keep out PDFRender astracted - core version need not be changed any more for a implemetation change , but we need to code the engines with a If ladder in the PDFRender Class .
To extend this idea , i could have a convention engine would be the module name too . But this own't adapt to a URL if its given as Engine .
def createPdf(data,engine):
#Convert the string called engine to a Module ! Sigh
import engine Engine!!
pdf_render = engine.Engine(data)
return pdf_render()
Are there any suggested paradigm's or a plugin adapter mechanism for this? It should take a URL or a Python Module as input and get the job done . New implementations should be standalone and pluggable to the existing code with no version changes in core feature . How can I do that ? I think the core should talk in terms of service (either a python module , sub process or a url ) this is what I trying to achieve .
Add a package where you stick your renderers, one Python module for each, the module being named after the "engine" and the renderer class being named "PDFRenderer". Then in the package's __init__ scan the modules in the directory, import the PDFRenderer class from each module, and build a enginename:PDFRenderer mapping, and use it to get the PDFRenderer class for the engine.
It looks like right now you have to change a couple of lines of code each time you change pdf renderer. The code is simple and easy to understand.
Sure, you can abstract it all out, and use configuration files, and add a plugin architecture. But what will you gain? Instead of changing a couple of lines of code, you'll instead change a plugin or a configuration file, and that machinery will need to be maintained: for example, if you decide you need to pass in some flags to one of the renderers, then you have to add that functionality to your configuration file or whatever abstraction you've chosen.
How many times do you think you'll change pdf renderer? My guess would be at most once a year. Keep your code simple, and don't build complex solutions until you really need them.

Python: Mako template lookups per app

I'm using cherrypy with Mako as a template engine.
I want Mako to lookup different directories based on what app is being requested.
I.e.
I have three 'apps': Site, Admin and Install.
They all have their own template folder, structure looking something like:
/template
/template/site
/template/admin
/template/install
/template/system
/system contains some system wide templates, like 404 pages, etc.
I'm using Twiseless as a reference whilst trying to get to grips with cherrypy / mako, but I'm stuck with how to do this.
Read on for a brief overview of how I've tried to do this, but a warning: I think I'm going about this completely the wrong way! :) So, if you have any ideas/pointers, it might be a good idea to save yourself the trouble of reading any further than this.
In my main file, server.py, I do something like:
from libs.plugins.template import MakoTemplatePlugin
engine = cherrypy.engine
makoTemplate = MakoTemplatePlugin(engine, self.base_dir)
setTemplateDirs(makoTemplate, self.template_path)
MakoTemplatePlugin is a slightly modified version of the plugin by the same name found in Twiseless, linked above.
What this code does is set the TemplateLookup to use the default template directories from my global config file. i.e.
/template
/template/system
Then, each time an app is loaded, I call a function (setTemplateDirs) to update the directories where Mako searches.
I thought this would work, but it doesn't. Initially I made the error of creating a new instance of MakoTemplatePlugin for each app. This just resulted in them all being called on each page load, starting with the first one instantiated, containing just the basic, non-app specific directories.
As this was called first, it was triggering a 404 error, as it was searching in the wrong folders.
I instead made sure to pass a reference to the MakeTemplatePlugin to all of my apps. I thought if I ran setTemplateDirs each time each app is called, this would solve the problem... but it doesn't.
I don't know where to put the function so it will run every time a page is requested...
e.g.
# /apps/site/app.py
import somemodule.setTemplateDirs
class Site(object, params):
def __init__(self):
self.params = params
self.makoTemplate = params['makoTemplate']
self.base_path = params['base_path']
setTemplateDirs(self.makoTemplate, self.base_path, '', '/')
#cherrypy.expose
#cherrypy.tools.render(template='index.html')
def index(self):
pass
This obviously just works when the application is first loaded... I tried moving the update function call into a seperate method update and tried calling that for each page, e.g:
#cherrypy.exposed
#cherrypy.tools.render(template='index.html')
#update
def index(self):
pass
But this just gives me config related errors.
Rather than to continue to mess about with this, there must be an easier way.
How would you do it?
Thanks a lot,
Tom
I got this working. Thanks to stephan for providing the link to the mako tool example: http://tools.cherrypy.org/wiki/Mako.
I just modified that slightly to get it working.
If anyone's wondering, the basis of it is that you define tools.mako.directories in your global config, you can then override that in individual app config files.
e.g.
server.conf
...
tools.mako.directories: ['', 'system']
...
site.conf
...
tools.mako.directories: ['site', 'system']
...
I did some extra work to translate the relative URIs to absolute paths, but the crux of it is explained above.

Categories