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.
Related
I'm relatively new to pytest so any guidance on the proper way to do this is appreciated. I'm looking for information how to write a unit test for a flask route decorator using pytest.
As an example, I have a flask route that I want to protect with a Bearer token or similar and am doing so through a decorator. I understand I need to pass function and an HTTP request through this decorator but I'm not quite clear on how to do that with pytest.
I thought maybe using a lambda: None to mock a function pass but it doesn't appear to be entering the decorator in that method. In additon I'm not really sure how to assert my return from the decorator.
Decorator
def auth_required(func):
def decorator(*args, **kwargs)
try:
access_token = request.headers.get('AUTHORIZATION', '').split('Bearer ')[1]
except IndexError:
return {"Access Denied": "Invalid authorization header"}, 400
if is_token_valid(access_token):
return func(*args, **kwargs)
else:
return {"Access Denied": "The provided bearer token is not valid"}, 403
return decorator
Usage in Flask Routes
#api.route('/$test-route', methods=['POST'], endpoint="test-route-example")
#auth_required
def process_json():
return "Hello World"
Attempt to Test
class TestTokenRequired:
payload = {'field1': 'value'}
def test_invalid_token(self, app):
encoded_jwt = jwt.encode(self.payload, '12345', algorithm='HS256')
with app.test_request_context(
headers={'AUTHORIZATION': 'Bearer {}'.format(encoded_jwt)}
):
auth_required(lambda: None)()
I have an async function that calls a streaming api. What is the best way to write unit test for this function? The api response has to be mocked.
I tried with aiounittest and used mock from unittest. But this calls the actual api instead of getting the mocked response. Also tried with pytest.mark.asyncio annotation, but this kept giving me the error - coroutine was never awaited. I have verified that pytest-asyncio has been installed.
I am using VS Code and Python 3.6.6
Here is the relevant code snippet:
async def method1():
response = requests.get(url=url, params=params, stream=True)
for data in response.iter_lines():
# processing logic here
yield data
Pasting some of the tests I tried.
def mocked_get(*args, **kwargs):
#implementation of mock
class TestClass (unittest.TestCase):
#patch("requests.get", side_effect=mocked_get)
async def test_method (self, mock_requests):
resp = []
async for data in method1:
resp.append (data)
#Also tried await method1
assert resp
Also tried with class TestClass (aiounittest.AsyncTestCase):
Use asynctest instead of aiounittest.
Replace unittest.TestCase with asynctest.TestCase.
Replace from unittest.mock import patch with from asynctest.mock import patch.
async for data in method1: should be async for data in method1():.
import asynctest
from asynctest.mock import patch
class TestClass(asynctest.TestCase):
#patch("requests.get", side_effect=mocked_get)
async def test_method(self, mock_requests):
resp = []
async for data in method1():
resp.append(data)
assert resp
I want to access a route-level dependency (cache) from a custom APIRoute class. The API is defined using a router and a custom APIRoute class.
APIRoute class
from typing import Callable
from fastapi import Request, Response, Depends
from fastapi.routing import APIRoute
class RecordRequestResponseRoute(APIRoute):
def get_route_handler(self) -> Callable:
original_route_handler = super().get_route_handler()
async def custom_route_handler(request: Request) -> Response:
# need to access cache here
response: Response = await original_route_handler(request)
return response
return custom_route_handler
Router API
from fastapi import Response, Depends, APIRouter, BackgroundTasks
import fastapi_plugins
import aioredis
router = APIRouter(route_class=RecordRequestResponseRoute)
#router.get("/users", tags=["users"])
async def match_frame(background_tasks: BackgroundTasks,
cache: aioredis.Redis = Depends(fastapi_plugins.depends_redis)):
return {"success": True, "data": []}
I need to access cache in RecordRequestResponseRoute class. I tried using sub dependencies but that did not help. What will be the correct way to do it?
I've been facing the same issue for some time and the best implementation I've came to is using fastapi-utils and class-based routing. Thus we can set up our dependencies as attributes of the class and create any method there. And call it like decorator or just inside route function.
Also you can efficiently choose which route will be changed by "request.url.path.split("/")[-1]". The string returns you the route. But it can also be done via decorator or you just run this func inside proper route.
Hope it will be useful!
Code example:
# some_route.py
from typing import List
from fastapi import APIRouter, Request, Depends, status, Response
from fastapi_utils.cbv import cbv
from fastapi_utils.inferring_router import InferringRouter
from schemas import UserInfoToken
from utils import get_userinfo
from logger_repo import log_action
router = InferringRouter(tags=["some_tag"])
#cbv(router)
class HardwareRouterClass:
userinfo: UserInfoToken = Depends(get_userinfo)
async def user_action_logger(self, request: Request) -> Response:
user_action = request.url.path.split("/")[-1]
if user_action == "route_1":
data = await request.json()
await log_action(
user_email=self.userinfo.email,
device=data["some_key"]
)
#router.get("/health")
async def get_health(self):
return "OK"
#router.post("/route_1")
async def post_route(
self, request: Request, data: ReadersBody
) -> ReadersResponse:
await self.user_action_logger(request)
return await your_func(data)
For returning a 400/500 response to clients in a flask webapp, I've seen the following conventions:
Abort
import flask
def index(arg):
return flask.abort("Invalid request", 400)
Tuple
def index(arg):
return ("Invalid request", 400)
Response
import flask
def index(arg):
return flask.Response("Invalid request", 400)
What are the difference and when would one be preferred?
Related question
Coming from Java/Spring, I am used to defining a custom exception with a status code associated with it and then anytime the application throws that exception, a response with that status code is automatically returned to the user (instead of having to explicitly catch it and return a response as shown above). Is this possible in flask? This is my little wrapped attempt
from flask import Response
class FooException(Exception):
""" Binds optional status code and encapsulates returing Response when error is caught """
def __init__(self, *args, **kwargs):
code = kwargs.pop('code', 400)
Exception.__init__(self)
self.code = code
def as_http_error(self):
return Response(str(self), self.code)
Then to use
try:
something()
catch FooException as ex:
return ex.as_http_error()
The best practice is to create your custom exception classes and then registering with Flask app through error handler decorator. You can raise a custom exception from business logic and then allow the Flask Error Handler to handle any of custom defined exceptions. (Similar way it's done in Spring as well.)
You can use the decorator like below and register your custom exception.
#app.errorhandler(FooException)
def handle_foo_exception(error):
response = jsonify(error.to_dict())
response.status_code = error.status_code
return response
You can read more about it here Implementing API Exceptions
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.