I have the following Flask routes and a custom helper:
from spots import app, db
from flask import Response
import simplejson as json
def json_response(action_func):
def create_json_response(*args, **kwargs):
ret = action_func(*args, **kwargs)
code = 200
if len(ret) == 2:
code = ret[0]
resp = ret[1]
else:
resp = ret[0]
return Response(
response=json.dumps(resp, indent=4),
status=code,
content_type='application/json'
)
return create_json_response
#app.route('/test')
#json_response
def test():
return 400, dict(result="Test success")
#app.route('/')
#json_response
def home():
return 200, dict(result="Home success")
I would expect a GET request to /test to return something like {"result":"Test success"} but that is not the case. Instead, any request seems to match the last route, i.e. home. Why?
I wonder if this is caused by some lack of insulation between the different calls to json_response?
Thanks in advance.
As Видул Петров said the solution is to use functools.wraps:
import functools
def json_response(action_func):
#functools.wraps(action_func)
def create_json_response(*args, **kwargs):
...
return create_json_response
The reason is that Flask’s routing system maps URLs to "endpoints", and then endpoints to view functions. The endpoint defaults to the __name__ attribute of the view function. In this case the decorated function was passed to app.route so the endpoint was create_json_response for both rules and the last view defined for that endpoint was used in both cases.
functools.wraps takes the __name__ (and other attributes) from the original function and fixes this. It is always a good idea to use it in decorated wrappers.
Related
I am using https://pypi.org/project/fastapi-microsoft-identity/ package for AD Azure authorization using requires_auth decorator. From my tests would like to avoid the authorization process but I cannot disable this decorator always returns 401.
this is the following code I am using from my test
def mock_decorator(*args, **kwargs) :
def decorator(f)
wraps(f)
def requires_auth(*args, **kwargs):
return f(*args, **kwargs)
return requires_auth
patch('fastapi_microsoft_identity.requires_auth', mock_decorator).start()
#pytest.fixture
def client():
with TestClient(api) as client:
yield client
def test_api_endpoint(client):
response = client.get("/api/weather/london")
assert response.status_code == 200
Since I am patching auth decorator should return 200 status code, instead, I am getting 401 UnAuthorized
The following code from api
#router.get('/api/weather/{city}')
#requires_auth
async def weather(request: Request, loc: Location = Depends(), units: Optional[str] = 'metric'):
try:
validate_scope(expected_scope, request)
return await openweather_service.get_report_async(loc.city, loc.state, loc.country, units)
except AuthError as ae:
return fastapi.Response(content=ae.error_msg, status_code=ae.status_code)
except ValidationError as ve:
return fastapi.Response(content=ve.error_msg, status_code=ve.status_code)
except Exception as x:
return fastapi.Response(content=str(x), status_code=500)
please help what I am doing wrong.
You can't really bypass a decorator but can circumnavigate it if in its implementation it uses #wrap() in the code.
Now # require_auth does use a #wrap in its implementation so we can circumnavigate the decorator by calling the original function in this case ‘weather’ function as:-
weather.__wrapped__()
instead of the usual way of calling the function.
The __wrapped__ method basically has reference to the original function instead of the decorator.
Here I created a small API which basically returns two strings now here I have called the weather function without the .__wrapped__
# main.py
from fastapi_microsoft_identity import requires_auth, validate_scope, AuthError
import fastapi
from fastapi import FastAPI
app = FastAPI()
#requires_auth
async def weather() :
return False
#app.get("/")
async def root():
if await weather.() :
return {"Decorator is working and not allowing the function f() to work "}
else :
return {"Decorator is disabled"}
Here the weather () doesn’t get executed that is why the string “Decorator is working and not allowing the function f() to work” is returned but in your case it is giving unauthorize error
But now when I call it using wrapped
# main.py
from fastapi_microsoft_identity import requires_auth, validate_scope, AuthError
import fastapi
from fastapi import FastAPI
app = FastAPI()
#requires_auth
async def weather() :
return False
#app.get("/")
async def root():
if await weather.__wrapped__() :
return {"Decorator is working and not allowing the function f() to work "}
else :
return {"Decorator is disabled"}
Now that I have used the,__wrapped__ while calling the weather function it is being executed directly circumnavigating the decorator
So if you don’t want the require#authto work call the weather function like this weather.__wrapped__(<arguments>)
Refer this python docs on wrapper method and this gihub repo on require_auth.
I want to create a middleware function in Flask that logs details from the request and the response. The middleware should run after the Response is created, but before it is sent back. I want to log:
The request's HTTP method (GET, POST, or PUT)
The request endpoint
The response HTTP status code, including 500 responses. So, if an exception is raised in the view function, I want to record the resulting 500 Response before the Flask internals send it off.
Some options I've found (that don't quite work for me):
The before_request and after_request decorators. If I could access the request data in after_request, my problems still won't be solved, because according to the documentation
If a function raises an exception, any remaining after_request functions will not be called.
Deferred Request Callbacks - there is an after_this_request decorator described on this page, which decorates an arbitrary function (defined inside the current view function) and registers it to run after the current request. Since the arbitrary function can have info from both the request and response in it, it partially solves my problem. The catch is that I would have to add such a decorated function to every view function; a situation I would very much like to avoid.
#app.route('/')
def index():
#after_this_request
def add_header(response):
response.headers['X-Foo'] = 'Parachute'
return response
return 'Hello World!'
Any suggestions?
My first answer is very hacky. There's actually a much better way to achieve the same result by making use of the g object in Flask. It is useful for storing information globally during a single request. From the documentation:
The g name stands for “global”, but that is referring to the data being global within a context. The data on g is lost after the context ends, and it is not an appropriate place to store data between requests. Use the session or a database to store data across requests.
This is how you would use it:
#app.before_request
def gather_request_data():
g.method = request.method
g.url = request.url
#app.after_request
def log_details(response: Response):
g.status = response.status
logger.info(f'method: {g.method}\n url: {g.url}\n status: {g.status}')
return response
Gather whatever request information you want in the function decorated with #app.before_request and store it in the g object.
Access whatever you want from the response in the function decorated with #app.after_request. You can still refer to the information you stored in the g object from step 1. Note that you'll have to return the response at the end of this function.
you can use flask-http-middleware for it link
from flask import Flask
from flask_http_middleware import MiddlewareManager, BaseHTTPMiddleware
app = Flask(__name__)
class MetricsMiddleware(BaseHTTPMiddleware):
def __init__(self):
super().__init__()
def dispatch(self, request, call_next):
url = request.url
response = call_next(request)
response.headers.add("x-url", url)
return response
app.wsgi_app = MiddlewareManager(app)
app.wsgi_app.add_middleware(MetricsMiddleware)
#app.get("/health")
def health():
return {"message":"I'm healthy"}
if __name__ == "__main__":
app.run()
Every time you make request, it will pass the middleware
Okay, so the answer was staring me in the face the whole time, on the page on Deferred Request Callbacks.
The trick is to register a function to run after the current request using after_this_request from inside the before_request callback. This is the code snippet of what worked for me:
#app.before_request
def log_details():
method = request.method
url = request.url
#after_this_request
def log_details_callback(response: Response):
logger.info(f'method: {method}\n url: {url}\n status: {response.status}')
These are the steps:
Get the required details from the response in the before_request callback and store them in some variables.
Then access what you want of the response in the function you decorate with after_this_request, along with the variables you stored the request details in earlier.
I have created a web app which will be publicly facing. There are some admin tools that the IT department will use to administer certain things in databases for example.
I have all my routes and models for the database, I just want to get some insight as to whether my function would be a suitable method of whitelisting IP addresses to routes and if I have missed something.
def allowed_ip(request):
if not request:
now = time.strftime("%b-%d-%Y_%H:%M:%S", time.gmtime(time.time()))
app.logger.info('No request was sent -=- {}'.format(now))
return False
if request and request.headers['X-Real-IP']:
if request.headers['X-Real-IP'] not in config.Config.ALLOWED_IPS:
now = time.strftime("%b-%d-%Y_%H:%M:%S", time.gmtime(time.time()))
app.logger.info('Request received from non-whitelist client {} -=- {}'.format(request.headers['X-Real-IP'],
now))
return False
else:
now = time.strftime("%b-%d-%Y_%H:%M:%S", time.gmtime(time.time()))
app.logger.info('Request received from whitelisted client {} -=- {}'.format(request.headers['X-Real-IP'],
now))
return True
else:
now = time.strftime("%b-%d-%Y_%H:%M:%S", time.gmtime(time.time()))
app.logger.info('Request received from but no IP sent -=- {}'.format(now))
return False
The function checks if it received a request, (I know that seems pointless but I was receiving some weird errors without this line), if it has received a request, it checks the X-Real-IP header to see if it in our whitelist.
Is there anything I'm missing that could be manipulated here?
I appreciate this may be a broad or off-topic question but I'm also open to other methods of doing this. Perhaps it would be better for me to manage whitelisting at Nginx's level?
My answer adapted into my code:
from functools import wraps
def whitelisted(f):
#wraps(f)
def decorated_function(*args, **kwargs):
if request.headers['X-Real-IP'] not in app.config.get('ALLOWED_IPS'):
return redirect(url_for('login', next=request.url))
return f(*args, **kwargs)
return decorated_function
Now this is possible:
#app.route('/map')
#whitelisted
#login_required
def show_all():
I will do something like this:
# helpers.py
from flask import request, current_app
def check_ip():
def decorator(f):
def wrapped_function(*args, **kwargs):
ip = request.environ.get('HTTP_X_REAL_IP', request.remote_addr)
if ip:
if ip in current_app.config.get('ALLOWED_IPS'):
return f(*args, **kwargs)
return 'Nice try! <3' # Do a real thing like real http code for forbiden, etc ... use response
return update_wrapper(wrapped_function, f)
return decorator
# routes.py
index = Blueprint('index ', __name__)
#index.route('/')
#check_ip()
def hello_world():
return render_template('home.html')
But just using the IP is not secure, if you want something better you should use flask_login or something like this in my opinion.
I have a application with a large number of automatically defined rules. I need every rule to check for a Bearer token before proceeding.
I have found app.url_map.iter_rules() which does let me see all the defined rules. I want a way to redefine every rule to use its current function, but wrap it with the authenticate_admin decorator defined below.
from flask import Flask
from functools import wraps
app = Flask(__name__)
# Check that the request has the correct `bearer_token`.
def authenticate_admin(func):
#wraps(func)
def wrapped(*args, **kwargs):
bearer_token = vault.get('secret/oauth')['bearer_token']
expected = ("Bearer " + bearer_token)
if expected != request.headers.get('Authorization'):
return jsonify({'error': "Authorization token incorrect"}), 401
return func(*args, **kwargs)
return wrapped
# .... Define a bunch of routes (Elided) ....
for rule in app.url_map.iter_rules():
# NEXT LINE IS PSEUDOCODE; IT IS WHAT I WANT TO ACHIEVE
rule.fx = authenticate_admin(rule.fx)
What I want is that after the above for loop executes, all rules will now require you to supply a Bearer token.
In the end, I just ended up adding following davidism's advice and made authenticate a function that is run by all endpoints. I added a whitelist of endpoints that do NOT require authentication.
from flask import request
import vault
app = Flask(__name__)
# .... Define a bunch of routes (Elided) ....
WHITELIST_POST = ['/post1', '/post2', '/post3']
WHITELIST_GET = ['/', '/get1', '/get2']
def authenticate():
if request.method == "GET" and request.url_rule.rule in WHITELIST_GET:
return
if request.method == "POST" and request.url_rule.rule in WHITELIST_POST:
return
bearer_token = vault.get('secret/oauth')['bearer_token']
expected = ("Bearer " + bearer_token)
if expected != request.headers.get('Authorization'):
abort(401)
app.before_request(authenticate)
On a side note, my boss liked this even more. It means that any endpoint we create on the server by default requires a bearer token. In the past we have forgotten to require a bearer token for some endpoints. Now it is opt out instead of opt in.
I would like to route to a different Flask views based on the Accept HTTP header, for example:
#api.route('/test', accept='text/html')
def test_html():
return "<html><body>Test</body></html>"
#api.route('/test', accept='text/json')
def test_json():
return jsonify(test="Test")
I haven't found relevant option in Werkzeug Rule constructor, which is used by Flask. Is it a missing feature or is it possible to achieve the same effect differently, for example by intercepting and modifying URL path before routing?
I don't want to merge the views into one because it would complicate code significantly, there are many of them and they reside in different blueprints.
I am aware that similar question has been asked, but nobody answered it using Flask. It's possible to do it in different web frameworks, for example in Pyramid using predicates - sample code can be found in this answer.
I know this is an old question but I ended up here looking for something similar so I hope it helps someone else.
flask_accept has the functionality to handle different Accept types through different routes.
from flask import Flask, jsonify
from flask_accept import accept
app = Flask(__name__)
#app.route('/')
#accept('text/html')
def hello_world():
return 'Hello World!'
#hello_world.support('application/json')
def hello_world_json():
return jsonify(result="Hello World!")
if __name__ == '__main__':
app.run()
if you just want to reject requests depending on whether they are a specific data type you could also use Flask-Negotiate
from flask import Flask
from flask_negotiate import consumes, produces
app = Flask(__name__)
#app.route('/consumes_json_only')
#consumes('application/json')
def consumes_json_only():
return 'consumes json only'
When one tries to access the endpoint without a valid Accept header:
$ curl localhost:5000 -I
HTTP 415 (Unsupported Media Type)
I wrote a decorator which does that (copying here for posterity). It's just a rough idea that could be improved further (e.g. returning 406 Not Acceptable response instead of using the default handler when there are no handlers that match given MIME type). More explanations are in the comments.
import functools
from flask import Flask, request, jsonify
app = Flask(__name__)
def accept(func_or_mimetype=None):
"""Decorator which allows to use multiple MIME type handlers for a single
endpoint.
"""
# Default MIME type.
mimetype = 'text/html'
class Accept(object):
def __init__(self, func):
self.default_mimetype = mimetype
self.accept_handlers = {mimetype: func}
functools.update_wrapper(self, func)
def __call__(self, *args, **kwargs):
default = self.default_mimetype
mimetypes = request.accept_mimetypes
best = mimetypes.best_match(self.accept_handlers.keys(), default)
# In case of Accept: */*, choose default handler.
if best != default and mimetypes[best] == mimetypes[default]:
best = default
return self.accept_handlers[best](*args, **kwargs)
def accept(self, mimetype):
"""Register a MIME type handler."""
def decorator(func):
self.accept_handlers[mimetype] = func
return func
return decorator
# If decorator is called without argument list, return Accept instance.
if callable(func_or_mimetype):
return Accept(func_or_mimetype)
# Otherwise set new MIME type (if provided) and let Accept act as a
# decorator.
if func_or_mimetype is not None:
mimetype = func_or_mimetype
return Accept
#app.route('/')
#accept # Or: #accept('text/html')
def index():
return '<strong>foobar</strong>'
#index.accept('application/json')
def index_json():
return jsonify(foobar=True)
#index.accept('text/plain')
def index_text():
return 'foobar\n', 200, {'Content-Type': 'text/plain'}
You can return different response types based on the Accept header using request. Example.
if request.accept_mimetypes['application/json']:
return jsonify(<object>), '200 OK'