Run fastapi app in a dockerized container on Azure functions - azure

I am trying to run a fastapi application using a docker container on Azure functions. It is giving a 404 when I try to hit the endpoint, I have made the required changes to host.json and function.json
Given below is what my function init file looks like and the folder structure
import logging
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
import fastapi
import azure.functions as func
import nest_asyncio
from fastapi.middleware.cors import CORSMiddleware
nest_asyncio.apply()
app=fastapi.FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
#app.get("hello/{name}")
async def get_name(
name: str,):
return {
"name": name,}
def main(req: func.HttpRequest, context: func.Context) -> func.HttpResponse:
return func.AsgiMiddleware(app).handle(req, context)
Folder structure

Related

How to add context to HTMLResponse?

How to add context to HTMLResponse as you can do with TemplateResponse to insert content into the HTML site? Example with TemplateResponse:
return templates.TemplateResponse('index.html', context={'request': request, "Variable1": V1, "Variable2": V2})
And how to add the context here?:
#app.get("/")
def root():
return HTMLResponse(pkg_resources.resource_string(__name__, "index.html"))
You can use jinja to achieve solution to your problem.
from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
app = FastAPI()
app.mount("/static", StaticFiles(directory="static"), name="static")
templates = Jinja2Templates(directory="templates")
#app.get("/items/{id}", response_class=HTMLResponse)
async def read_item(request: Request, id: str):
context = {'request': request, "Variable1": V1, "Variable2": V2}
return templates.TemplateResponse("item.html", context)
Reference: https://fastapi.tiangolo.com/advanced/templates/

How to import app instance created from create_app()

How do I import app instance created in app/app.py's create_app() function to be used in app/api.py since the app instance returns only to my manage.py?
My project structure:
project/
app/
api.py
app.py
models.py
schema.py
...
manage.py
requirements.txt
README.md
I'm starting my dev app with command python manage.py
# manage.py
from app.app import create_app
app = create_app()
if __name__ == '__main__':
app.run(debug=True, host='localhost', port=5000)
# app/app.py
def create_app():
app = Flask(__name__)
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['SQLALCHEMY_DATABASE_URI'] = config['mysql']['db_uri']
app.config['SQLALCHEMY_ENGINE_OPTIONS'] = {'pool_recycle': 3000, 'pool_pre_ping': True, }
register_extensions(app)
#app.before_first_request
def setup_logging():
gunicorn_error_logger = logging.getLogger('gunicorn.error')
app.logger.handlers.extend(gunicorn_error_logger.handlers)
app.logger.setLevel(gunicorn_error_logger.level)
return app
def register_extensions(app):
db.init_app(app)
api.init_app(app)
# app/api.py
from flask import Flask, jsonify
from flask_restful import Api, Resource
api = Api()
class TestGet(Resource):
def get(self):
app.logger.info('ok') ## Here I wish to use the app instance for logging
return 'ok'
api.add_resource(TestGet, '/ok')
How do I import the app
instance into app/api.py so that I may use app.logger.info(...)?
If I call create_app() it will create a new instance and not the one that is already running?
There is no need to import app.
You can use the current_app helper.
from flask import current_app
You can think of it as a proxy to the app instance.
Links to the official documentation:
https://flask.palletsprojects.com/en/1.1.x/appcontext/#the-application-context
https://flask.palletsprojects.com/en/1.1.x/api/#flask.current_app
Miguel Grinberg also offers very good information on this topic:
https://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-xv-a-better-application-structure
(search for current_app on the page)

Is there a problem while creating setUp() function in my Unit test cases?

I am writing unit test cases for my app.py file. I have created a setUp() function in my test file but the moment I execute any test case, it throws an error like
============================= test session starts ==============================
platform linux -- Python 3.6.9, pytest-6.1.1, py-1.9.0, pluggy-0.13.1 -- /home/curiousguy/PycharmProjects/leadgen/venv/bin/python
cachedir: .pytest_cache
rootdir: /home/curiousguy/PycharmProjects/leadgen/tests
plugins: cov-2.10.1
collecting ... ENV :None
test_app.py:None (test_app.py)
test_app.py:5: in <module>
from app import app
../app.py:7: in <module>
from appli import appli
../appli.py:6: in <module>
appli = create_app(db, config_name)
../app_setup.py:21: in create_app
leadgen_app.config.from_object(app_config[config_name])
E KeyError: None
collected 0 items / 1 error
==================================== ERRORS ====================================
_________________________ ERROR collecting test_app.py _________________________
test_app.py:5: in <module>
from app import app
../app.py:7: in <module>
from appli import appli
../appli.py:6: in <module>
appli = create_app(db, config_name)
../app_setup.py:21: in create_app
leadgen_app.config.from_object(app_config[config_name])
E KeyError: None
The test file is:
import unittest
from unittest.mock import patch
import pytest
# from appli import appli
from app import app
#import app
class MyApp(unittest.TestCase):
def setUp(self):
app.testing = True
self.client = app.test_client()
def tearDown(self):
pass
def test_settings_passed(self):
response = self.client.get('/settings', follow_redirects=True)
self.assertEqual(response.status_code, 200)
with pytest.raises(AssertionError) as wt:
self.assertEqual(response.status_code, 403)
Since the errors are pointing to different files I am adding those files as well.
app.py
import functools
import pickle
from flask import (redirect, render_template, request, Response, session, url_for)
from flask_login import LoginManager, login_user, current_user
from flask_admin import Admin
from ldap3 import Server, Connection, ALL, SIMPLE
from appli import appli
import datetime
from modules.controller import application
from modules.users_view import MyAdminIndexView, UsersView
from database.db_model import (Companies, Users, Leads, db)
###########################################################
# Init section #
###########################################################
app = appli
login_manager = LoginManager()
login_manager.login_view = "login"
login_manager.init_app(app)
server = Server(app.config['LDAP_SERVER'],
port=app.config['LDAP_PORT'], get_info=ALL)
server_tor = Server(app.config['LDAP_SERVER_TOR'],
port=app.config['LDAP_PORT'], get_info=ALL)
admin = Admin(app, name='LEADGEN Admin', index_view=MyAdminIndexView(), base_template='master.html')
admin.add_view(UsersView(Users, db.session))
application_inst = application("Lead Generator")
#rest of code
appli.py
import os
from app_setup import create_app
from database.db_model import db
config_name = os.getenv('FLASK_ENV')
appli = create_app(db, config_name)
if __name__ == '__main__':
appli.run()
app_Setup.py
def create_app(db,config_name):
leadgen_app = Flask(__name__, instance_relative_config=True)
# config_name = os.getenv('FLASK_ENV', 'default')
print('ENV :' + str(config_name))
# leadgen_app.config.from_object(eval(settings[config_name]))
leadgen_app.config.from_object(app_config[config_name])
leadgen_app.config.from_pyfile('config.cfg', silent=True)
# Configure logging
leadgen_app.logger.setLevel(leadgen_app.config['LOGGING_LEVEL'])
handler = logging.FileHandler(leadgen_app.config['LOGGING_LOCATION'])
handler.setLevel(leadgen_app.config['LOGGING_LEVEL'])
formatter = logging.Formatter(leadgen_app.config['LOGGING_FORMAT'])
handler.setFormatter(formatter)
leadgen_app.logger.addHandler(handler)
leadgen_app.logger.propagate = 0
# Configure sqlalchemy
leadgen_app.app_context().push()
db.init_app(leadgen_app)
# with leadgen_app.app_context():
# db.create_all()
from leads.leads_bp import leads_bp
from process.process_bp import process_bp
from CAAPI.caapi_bp import caAPI_bp
leadgen_app.register_blueprint(leads_bp)
leadgen_app.register_blueprint(process_bp)
leadgen_app.register_blueprint(caAPI_bp)
return leadgen_app
Where am I making a mistake to run my test case successfully?
The KeyError is caused by app_config[config_name].
config_name comes from config_name = os.getenv('FLASK_ENV').
getenv defaults to None when no value is set, see https://docs.python.org/3/library/os.html#os.getenv
This means you have to set the environment variable in order to make tests pass.
You could also debug your application with pdb - I gave a lightning talk how to debug a Flask application...
https://www.youtube.com/watch?v=Fxkco-gS4S8&ab_channel=PythonIreland
So I figured out an answer to this. here is what I did.
I made changes to my setUp() function in my testcase file
def setUp(self):
self.app = create_app(db)
self.client = self.app.test_client(self)
with self.app.app_context():
# create all tables
db.create_all()
and then I imported from app_setup import create_app in my testcase file. And finally I made changes in app_setup.py in function
def create_app(db, config_name='default')
And my testcases are now running.

Uvicorn not running sanic "before_server_start"

I have a sanic app like so:
from functools import wraps
import os
from sanic import Sanic
from sanic.response import json
from es_api.client import ElasticEngine
from utils import cleanup
app = Sanic(__name__)
async def setup_es_client(app, loop):
app.es_client = ElasticEngine.from_bonsai("xkcd_production", test_instance=False)
#app.route("/", methods=["GET"])
async def home(request):
return json({"hello": "worldddd"})
#app.route("/all", methods=["GET"])
async def display_all_docs(request):
results = app.es_client.search_all().results()
return json(
{"results": results}
)
if __name__ == "__main__":
app.register_listener(setup_es_client,
'before_server_start')
app.run(host="0.0.0.0", port=8000, debug=True, workers=20)
When I run uvicorn myapp, it serves the homepage fine: I see the expected json.
But when I hit /all, it says
"app has not attribute es_client"
, which presumably indicates that the before_server_start function hasn't been run.
How do I fix this? I've looked into the sanic doc but I couldn't find any references to this issue
(It works fine when I run the app as is -- i.e, python3 myapp.py)
Fixed.
Move app.register_listener(setup_es_client,
'before_server_start') above if __name__=="__main__"
Better yet, just use sanic's builtin decorator for nicer ergonomics: https://sanic.readthedocs.io/en/latest/sanic/middleware.html

Pass filepath as parameter to a URL in FLASK(Python)

I want to build an api which accepts a parameter from the user which is a filepath and then process the file given in that path. The file to be processed is already in the server where the api will be running.
As of now, I have written an api where I have hardcoded the filepath in my code which runs the api. Now, I want to configure my api in such a way that accepts a filepath from the user. My api should accept the path as a parameter and process the file that has been given in the path.
The api code is as follows:
The convert function returns the category of the file.
import ectd
from ectd import convert
from flask import Flask, request
from flask_restful import Resource, Api
#from flask.views import MethodView
app = Flask(__name__)
api = Api(app)
#convert(r'D:\files\67cecf40-71cf-4fc4-82e1-696ca41a9fba.pdf')
class ectdtext(Resource):
def get(self, result):
return {'data': ectd.convert(result)}
#api.add_resource(ectdtext, '/ectd/<result>')
categories=convert(r'D:\files\6628cb99-a400-4821-8b13-aa4744bd1286.pdf')
#app.route('/')
def returnResult():
return categories
if __name__ == '__main__':
app.run(host="0.0.0.0", port=5000)
So, I want to make changes to this code to accept a parameter from the user which will be a filepath and the convert function will process that filepath. I want to know how to make my api accept a filepath parameter from the user.
Trial with requests.args.get:
import ectd
from ectd import convert
from flask import Flask, request
from flask_restful import Resource, Api
#from flask.views import MethodView
app = Flask(__name__)
api = Api(app)
#convert(r'D:\files\67cecf40-71cf-4fc4-82e1-696ca41a9fba.pdf')
class ectdtext(Resource):
def get(self, result):
return {'data': ectd.convert(result)}
#api.add_resource(ectdtext, '/ectd/<result>')
#app.route('/')
def returnResult():
categories=convert(r'D:\files\'.format(request.args.get('categories')))
return categories
if __name__ == '__main__':
app.run(host="0.0.0.0", port=5000)
results in error :
"RuntimeError: Working outside of request context.
This typically means that you attempted to use functionality that needed
an active HTTP request. Consult the documentation on testing for
information about how to avoid this problem."
PRESENT SCENARIO:
I am able to post a filepath to the url. My question is now how do I use this posted url with filepath in my code to trigger my function that takes in the filepath and processes the file. Code to post the filepath:
import ectd
from ectd import convert
from flask import Flask, request
from flask_restful import Resource, Api
#from flask.views import MethodView
app = Flask(__name__)
api = Api(app)
class ectdtext(Resource):
def get(self, result):
return {'data': ectd.convert(result)}
#api.add_resource(ectdtext, '/ectd/<result>')
categories=convert('/home/brian/ajay/files/5ca21af9-5b67-45f8-969c-ae571431c665.pdf')
#app.route('/')
def returnResult():
return categories
#app.route('/', defaults={'path': ''})
#app.route('/<path:path>')
def get_dir(path):
return path
##app.route('/get_dir/<path>')
#def get_dir(path):
# return path
if __name__ == '__main__':
app.run(host="0.0.0.0", port=5000)

Resources