Optional URL variables - python

Is there a way to define URLs with optional URL params in Flask? Essentially, what I'd like to do is define rules that allow for optionally specified languages:
/
/de -> matches / (but doesn't collide with /profile)
/profile
/de/profile
I think I've figured out a way to do it, but it involves either making a change to how Werkzeug and Flask handles the request (either monkey patching or forking the framework source). This seems like an overly complex way to deal with this problem though.. Is there an easier way to do this that I'm overlooking?
Edit:
Based on Brian's answer, here's what I came up with:
app.py:
from loc import l10n
def create_app(config):
app = Flask(__name__)
app.config.from_pyfile(config)
bp = l10n.Blueprint()
bp.add_url_rule('/', 'home', lambda lang_code: lang_code)
bp.add_url_rule('/profile', 'profile', lambda lang_code: 'profile: %s' %
lang_code)
bp.register_app(app)
return app
if __name__ == '__main__':
create_app('dev.cfg').run()
loc/l10ln.py
class Blueprint(Blueprint_):
def __init__(self):
Blueprint_.__init__(self, 'loc', __name__)
def register_app(self, app):
app.register_blueprint(self, url_defaults={'lang_code': 'en'})
app.register_blueprint(self, url_prefix='/<lang_code>')
self.app = app
(I haven't gotten to pulling lang_code from the variable list yet, but will be doing that shortly)
Now that's just hot imho.

Just in case you didn't know, you can register multiple routes for a view. Might be a pain to do it for every view, but it's doable...
DEFAULT_LANG = 'en'
#app.route('/profile')
#app.route('/<lang>/profile')
def profile(lang=DEFAULT_LANG):
pass
Or, perhaps you could implement your own route decorator that automatically invokes app.route for both scenarios...
from flask import Flask
app = Flask(__name__)
DEFAULT_LANG = 'en'
def lang_route(rule, **options):
def decorator(f):
endpoint = options.pop('endpoint', None)
app.add_url_rule(rule, endpoint, f, **options)
app.add_url_rule('/<lang>%s' % rule, endpoint, f, **options)
return f
return decorator
#lang_route('/profile') # also accepts '/<lang>/profile' automatically
def profile(lang=DEFAULT_LANG):
return lang
if __name__ == '__main__':
app.run(debug=True)

Blueprints might work for this, since they can be registered multiple times.
from flask import Flask, Blueprint
app = Flask(__name__)
bp = Blueprint('main', __name__)
#bp.route('/')
def hello(lang):
return 'Hello ' + lang + '!'
app.register_blueprint(bp, url_defaults={'lang': 'en'})
app.register_blueprint(bp, url_prefix='/<lang>')
if __name__ == '__main__':
app.run()
If that works, see Internationalized Blueprint URLs in the Flask documentation for a way to avoid specifying a lang argument in every view function.

Related

How do I access a context_processor from within a method route in Flask?

I'd like to setup variables available to both my views and my methods without polluting the request object in a before_request decorator.
Context processors seems like a nice way to do this however, I can't figure out how to actually access them from within my methods.
The best solution I have come up with is to memoize the context function so that it doesn't get called twice, once by me in my method and then again when Flask injects it into the template.
However, this will cache the method for all future requests and I only want it cached per request.
Here is my working example
from functools import cache
#app.context_processor
#cache
def setup_context():
return {
'planet': db.query('select planet from planets order by random()').first()
}
#app.route("/")
def index():
ctx = setup_context()
if ctx['planet'] == 'pluto':
return redirect('/pluto-is-not-a-planet')
return render_template('planet_greeting.html')
Any ideas on how to accomplish this without using functools.cache?
There may be a more elegant way to do this but here is what I have come up with so far.
The basic idea is to use the extensions pattern and create an object "Current" that the app gets passed to.
I can then use properties on this object to access the _app_ctx_stack as well as populate templates with context variables using the context_processor hook.
This approach will allow me to have templates that don't use "g" and a nice object to work with in my routes.
from flask import (
Flask, current_app, _app_ctx_stack,
render_template as template
)
from random import shuffle
planets = ['earth', 'pluto', 'mars']
class Current(object):
def __init__(self, app=None):
self.app = app
self.app.context_processor(self.context_processor)
def context_processor(self):
return _app_ctx_stack.top.__dict__
#property
def planet(self):
ctx = _app_ctx_stack.top
if not hasattr(ctx, 'planet'):
shuffle(planets)
ctx.planet = {
'name': planets[0]
}
return ctx.planet
app = Flask(__name__)
current = Current(app)
#app.route("/")
def index():
if current.planet['name'] == 'pluto':
return "Pluto is not a planet!"
return template("planet.html")
if __name__ == '__main__':
app.run(debug=True)
And in my template
{%# in my template %}
The planet is {{ planet.name }}!

generating flask route from class method

i am trying to generate Flask route using a basic DI i.e mapping methods as route handlers, i am a total beginner at Flask so mind my basic skills
class myClass():
def __init__(self):
self.dbConnObj = DbToolsMySql('someconnection', 'slave')
self.dbConnObj.connect()
self.blueprint = Blueprint('myClass', __name__)
self.blueprint.add_url_rule('/my_method', view_func=self.my_method)
def my_method(self, event):
retun "hello"
and then in my handler file
from flask import Flask
from flask_restful import Api, Resource
from src.app.services.myClassimport myClass
app = Flask(__name__)
app.register_blueprint(myClass.blueprint)
if __name__ == "main":
app.run()
Quite simple ehh???? but not working... i am getting following message
Not Found The requested URL was not found on the server. If you
entered the URL manually please check your spelling and try again.
typically you add routes to the Flask app with decorators like so:
app = Flask(__name__)
#app.route('/some-endpoint')
def some_endpoint_handler():
# do something
pass
Or you can add without a decorator like so:
def some_endpoint_handler():
# do something
pass
app = Flask(__name__)
app.route('/some-endpoint', methods=['GET'])(some_endpoint_handler)
So in your scenario, you can pass the app.route call to your myClass object and set the route like this:
class myClass():
def __init__(self, router):
self.dbConnObj = DbToolsMySql('someconnection', 'slave')
self.dbConnObj.connect()
self.blueprint = Blueprint('myClass', __name__)
#self.blueprint.add_url_rule('/my_method', view_func=self.my_method)
router('/my_method', ['GET'])(self.my_method)
def my_method(self, event):
retun "hello"
myObj = myClass( app.route )
or, invert the dependency:
app = Flask(__name__)
#app.route(myClass.blueprint.some_endpoint_string)
def some_endpoint_handler():
myClass.blueprint.call_some_endpoint_handler()
pass
if __name__ == "main":
app.run()

Flask: how to override blueprint route?

I have base_blueprint like that:
from flask import (
jsonify,
Blueprint,
)
class RestBlueprint(object):
def __init__(self, db_model):
self.blueprint = Blueprint(db_model.__tablename__, db_model.__tablename__, url_prefix='/' + db_model.__tablename__ + '/')
self.db_model = db_model
#self.blueprint.route("/<entity_id>", methods=["GET"])
def get_by_id(entity_id):
response = get_item()
return jsonify(response)
I want to inherit this blueprint by other blueprints like that:
from rest_blueprint import RestBlueprint
class UserRestBlueprint(RestBlueprint):
def __init__(self, db_model):
super(UserRestBlueprint, self).__init__(db_model)
#self.blueprint.route("/<user_id>", methods=["GET"])
def get_by_id(user_id):
response = get_item()
del response["password"]
return jsonify(response)
This is a basic example and the reason I want to do it that way is to have base blueprint used for most of my models for CRUD and have custom blueprints for 4-5 models. The problem that I have is that I can't override base blueprint crud methods and I was curious if there is any way to do that in order to have custom CRUD methods for custom blueprints.
Any help is appreciated!

Call decorators directly Flask, python

I am trying to create a simple Rest API.
Even if it is simple, I don't want to mix everything in a single file.
Therefore I have defined separate classes
Here is some of my files
app = Flask(__name__)
if __name__ == '__main__':
api = PostApi(app)
api.setup()
api.set_routes()
app.run(debug=True)
Post API class
class PostApi(object):
BASE_API_ROUTE = '/post'
def __init__(self, app):
super(PostApi, self).__init__()
self.app = app
def setup(self):
self.api = Api(self.app)
self.app.config['SECRET_KEY'] = SECRET['digest_key']
def set_routes(self):
self.api.add_resource(PostCategories, self.BASE_API_ROUTE + "/categories")
self.api.add_resource(PostCatalog, self.BASE_API_ROUTE + "/catalog")
self.api.add_resource(PostTags, self.BASE_API_ROUTE + "/tags")
And for example one of my endpoint classes
class PostTags(Resource):
def __init__(self):
super(PostTags, self).__init__()
def get(self):
return {'hello': 'world'}
It works, but I need to add authentication for my routes.
As you can see I am not using route decorators like app.route instead I am using the library flask_restful.
I need to protect my routes with the Digest Auth in this case. However, I am not sure how to do this, because I am not using decorators
I am a newbie developer. Could you suggest how to keep my endpoints separated and apply some protection to my routes.
You can use before_request. This will be called before every request on every route.
something like this:
#app.before_request
def before_request():
//add your logic here
there's also before_first_request.
visit Flask Documentation for more info.

Extending Flask class as main App

I'm learning Flask and am a bit confused about how to structure my code. So I tried to extend Flask main class as follows:
from flask import Flask, ...
class App(Flask):
def __init__(self, import_name, *args, **kwargs):
super(App, self).__init__(import_name, *args, **kwargs)
Note that I am aware of that this may be a completely wrong approach.
So that when I want to start the app I do:
app = App(__name__)
if __name__ == '__main__':
app.run()
This way I can order my methods and routes in the class, but the problem is when using self-decorators:
#route('/')
def home(self, context=None):
context = context or dict()
return render_template('home.html', **context)
Which raises an error as unresolved reference 'route'. I guess this is not the way I should be structuring the app. How should I do it instead or how do I get the error fixed?
Doing this doesn't make sense. You would subclass Flask to change its internal behavior, not to define your routes as class methods.
Instead, you're looking for blueprints and the app factory pattern. Blueprints divide your views into groups without requiring an app, and the factory creates and sets up the app only when called.
my_app/users/__init__.py
from flask import Blueprint
bp = Blueprint('users', __name__, url_prefix='/users')
my_app/users/views.py
from flask import render_template
from my_app.users import bp
#bp.route('/')
def index():
return render_template('users/index.html')
my_app/__init__.py
def create_app():
app = Flask(__name__)
# set up the app here
# for example, register a blueprint
from my_app.users import bp
app.register_blueprint(bp)
return app
run.py
from my_app import create_app
app = create_app()
Run the dev server with:
FLASK_APP=run.py
FLASK_DEBUG=True
flask run
If you need access to the app in a view, use current_app, just like request gives access to the request in the view.
from flask import current_app
from itsdangerous import URLSafeSerializer
#bp.route('/token')
def token():
s = URLSafeSerializer(current_app.secret_key)
return s.dumps('secret')
If you really want to define routes as methods of a Flask subclass, you'll need to use self.add_url_rule in __init__ rather than decorating each route locally.
class MyFlask(Flask):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.add_url_rule('/', view_func=self.index)
def index(self):
return render_template('index.html')
The reason route (and self) won't work is because it's an instance method, but you don't have an instance when you're defining the class.

Categories