FastAPI - How to get app instance inside a router? - python

I want to get the app instance in my router file, what should I do ?
My main.py is as follows:
# ...
app = FastAPI()
app.machine_learning_model = joblib.load(some_path)
app.include_router(some_router)
# ...
Now I want to use app.machine_learning_model in some_router's file , what should I do ?

Since FastAPI is actually Starlette underneath, you could store the model on the app instance using the generic app.state attribute, as described in Starlette's documentation (see State class implementation too). Example:
app.state.ml_model = joblib.load(some_path)
As for accessing the app instance (and subsequently, the model) from outside the main file, you can use the Request object. As per Starlette's documentation, where a request is available (i.e., endpoints and middleware), the app is available on request.app. Example:
from fastapi import Request
#router.get('/')
def some_router_function(request: Request):
model = request.app.state.ml_model

Related

Is there a way to move `import` statement to the top of Flask view file when service class references `current_app`?

I am trying to use a global configuration when defining an ElasticSearch DSL model, which is more or less a regular Python class aka service class.
"""Define models"""
from elasticsearch_dsl import Document, Text
from flask import current_app
class Greeting(Document):
"""Define Greeting model"""
message = Text()
class Index:
name = current_app.config['GREETINGS_INDEX']
def save(self, ** kwargs):
return super().save(** kwargs)
Unfortunately, if my import statement is at the top of the view file, I get this error message:
RuntimeError: Working outside of application context.
This typically means that you attempted to use functionality that needed
to interface with the current application object in some way. To solve
this, set up an application context with app.app_context(). See the
documentation for more information.
The only way to get things to work is if I import the model/service class inside the request like this:
from elasticsearch_dsl import Search
from flask import Blueprint, current_app
# from .models import Greeting ### this will throw the application context error
greetings = Blueprint(
'greetings',
__name__,
url_prefix='/greetings/'
)
...
#greetings.route("/elasticsearch/new/")
def new_greeting_using_elasticsearch():
from .models import Greeting ### this avoids the application context error
Greeting.init()
greeting = Greeting(message="hello, elastic")
greeting.save()
return(
"a greeting was saved; "
"it is viewable from https://localhost:5000/greetings/elasticsearch/"
)
This seems like a code smell. Is there another way to accomplish using reusing configurations that can keep the import statement at the top of the file?
These questions/answers seem to suggest that this is the only way:
How to access config value outside view function in flask
Flask - RuntimeError: Working outside of application context
Am I missing something? Should I rearchitect my application to avoid this? Or is this just a Flask-way/thing?
Thank you for your help 🙏
Other questions/answers/articles that did not help me:
"RuntimeError: Working outside of application context " with Python Flask app ( Sending gmail using scheduler )
https://flask.palletsprojects.com/en/0.12.x/appcontext/#creating-an-application-context
Access config values in Flask from other files
RuntimeError: working outside of application context
Python #property in Flask configs?
Reading properties from config file with Flask and Python

Create object on startup and share across requests

I would like to use my Django application as a relay for a session-based online service and share this session among all users. For this, I've configured a python-requests Session object.
I would like to initialise this Session when Django starts up and keep it alive forever. My idea is to have all requests to my Django application share the session object by allowing the view for the particular request access the session object.
In Flask setting this up (for experimental purposes) is fairly easy:
from flask import Flask, render_template, request, session
from requests import Session
app = Flask(__name__)
session = Session()
session.post() # Setup Session by logging in
#app.route("/")
def use_session():
reply = session.get() # Get resource from web service
return jsonify(reply)
Here session would be created when starting and can be accessed by use_session().
I struggle to set up the same in Django though. Where would be the preferred place to create the session?
The equivalent of your Flask code in Django would be to put the same logic in a views.py file:
# yourapp/views.py
from django.http import HttpResponse
from requests import Session
session = Session()
session.post('https://httpbin.org/post') # Setup Session by logging in
def use_session(request):
reply = session.get('https://example.com') # Get resource from web service
return HttpResponse(reply.content, status=reply.status_code)
# yourproject/urls.py
from django.urls import path
from yourapp.views import use_session
urlpatterns = [
path('', use_session)
]
The object will get created as you start the server.
One problem with this approach is that in a real-world deployment you normally run multiple copies of your app (doesn't matter if it's Flask or Django or any other framework) to avoid thread blocking, utilize multiple CPU cores on the same server or have multiple servers, each copy will end up with its own session, which might work fine, but might be problematic.
In a situation like this, you can share a Python object1 across multiple processes by serializing it using the pickle module and storing the byte data in a database. A good place for the data would be something like Redis or Memcached but Django's ORM can be used as well:
# yourapp/models.py
from django.db import models
class Session(models.Model):
pickled = models.BinaryField()
# yourapp/views.py
import pickle
from django.http import HttpResponse
from requests import Session
from . import models
def use_session(request):
# Load the pickled session from the database
session_db_obj = models.Session.objects.first()
if session_db_obj:
# Un-pickle the session
session = pickle.loads(session_db_obj.pickled)
else:
# Create a new session
session = Session()
session.post('https://httpbin.org/post') # Setup Session by logging in
# Create the database object, pickle the session and save it
session_db_obj = models.Session()
session_db_obj.pickled = pickle.dumps(session)
session_db_obj.save()
reply = session.get('https://example.com') # Get resource from web service
return HttpResponse(reply.content, status=reply.status_code)
1: Not any object can be pickled and unpickled reliably, be careful!
Probably the best place to set this up is in settings.py since it's called before the application is initialized and you can easily import your code from there.
This being said, you may want to look into connection pooling or have a wrapper on top of your session which can recreate it in case of failure. Networks are not as reliable as they seem and if you plan to keep the session running for a long time it increases the chances that the session will be stopped at some point.

FastAPI: Retrieve the endpoint for jinja2 `url_for` to give a jump link

Function description
In flask this code:
#app.route('/')
def index():
return ...
or
foo_router = Blueprint('foo', __name__)
#foo_router.route('/')
def index():
return ...
app.register_blueprint(foo_router, url_prefix='/api')
These ways you are able to use url_for('index') to retrieve url like https://hostname:8080/
or use url_for('foo.index') to retrive url like https://hostname:8080/api/foo
Desired function
Use url_for for jump link in fastapi like flask
Additional context
I have found a trick to do the implement
create url_naming.py and add the code below
from starlette.routing import Mount, Route
from .routers.foo.endpoints.area import index as foo_area_index
foo_router_for = Mount('/', routes=[
Route('/area', foo_area_index, name='foo_area.index'),
])
and in your main.py add
from .url_naming import foo_router_for
app.add_route('/foo', foo_router_for, name='foo_area.index')
In the codeblocks, I define name twice because which name I defined in a Route model cannot be detected by FastAPI, or it is overridden by FastAPI, and this impl make no sense because it is required to write down the real router name rather than the endpoint function name.
Despite that, what if I'd like to name my router in one file, which do not make effect on main.py?
I solve the problem with the following solution.
Just to name your handle function (endpoint) and the name can be normally used by url_for.
router = APIRouter(prefix='/api')
#router.get('/foo')
async def api_foo():
return ...
This way you can retrieve the url http://hostname:8080/api/foo/ using url_for('api_foo').
I guess this is because all the handle functions in FastAPI are named in the same space, and if they are named in FastAPI repeatedly, no error will be reported.

How to make customized middleware in CKAN

I've met a scenario which I have to override a common middleware in CKAN. And in CKAN default plugins/interface.py:
class IMiddleware(Interface):
u'''Hook into CKAN middleware stack
Note that methods on this interface will be called two times,
one for the Pylons stack and one for the Flask stack (eventually
there will be only the Flask stack).
'''
def make_middleware(self, app, config):
u'''Return an app configured with this middleware
When called on the Flask stack, this method will get the actual Flask
app so plugins wanting to install Flask extensions can do it like
this::
import ckan.plugins as p
from flask_mail import Mail
class MyPlugin(p.SingletonPlugin):
p.implements(p.I18nMiddleware)
def make_middleware(app, config):
mail = Mail(app)
return app
'''
return app
It shows that I have to define "MyMiddleWare" class under a plugin that I want to implement in an extension. However, as it shows in the example, the actual middleware Mail is imported from a different class. I want to override TrackingMiddleware especially the __call__(self, environ, start_response) method, which environ and start_response are passed in when make_pylons_stack are invoked during the configuration phase. If I want to override TrackingMiddleware Should I create another config/middleware/myTrackingMiddleware.py under ckanext-myext/ and then in plugin.py I implement the following?:
from myTrackingMiddleware import MyTrackingMiddleware
class MyPlugin(plugins.SingletonPlugin):
plugins.implements(plugins.IMiddleware, inherit=True)
def make_middleware(self, app, config):
tracking = MytrackingMiddleware(app, config)
return app
Update:
I tried to make the myTrackingMiddleware in an hierarchy and imported it in plugin.py, but I didn't received any request to '/_tracking' in myTrackingMiddleware.
I have implemented a set of process, and it works for myself. Basically, I kept what I have done as what I have mentioned in my own question. Then, if your middleware has some conflict with CKAN default Middleware, you probably have to completely disable the default one. I discussed with some major contributors of CKAN here: https://github.com/ckan/ckan/issues/4451. After I disabled CKAN ckan.tracking_enabled in dev.ini, I have the flexibility to get values from environ and handle tracking with my customized logic.

Setting global attributes in Flask framework

I am working on a small web project using Flask/Python. This is a simple client side application without database.
I want to set the REST service address as a global attribute, but haven't figured out how to do that.
I know that attributes can be seted in flask.config like this:
app = Flask(__name__)
app.config['attribute_name'] = the_service_address
but the Blueprint module cannot access the 'app' object.
Thanks a lot for your time.
Within a request context (i.e. in a view/handler) you can access the config on the current_app
from flask import current_app
current_app.config['attribute_name']
You can do this like adding an attribute to any python object:
def create_web_app():
app = Flask('foods')
setattr(app, 'cheese', CheeseService())

Categories

Resources