Minimal FastAPI Static File Server Script - fastapi

I want to write a minimal FastAPI static file server launched from a script that allows you to specify the directory to share on the command line. Following the example in the FastAPI documentation, I wrote this.
import uvicorn
from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
server = FastAPI()
if __name__ == "__main__":
import sys
directory = sys.argv[1]
server.mount("/static", StaticFiles(directory=directory), name="static")
uvicorn.run(app="my_package:server")
If I run this with the argument /my/directory where this directory contains file.txt I expect that I'd be able to download file.txt at the URL http://localhost:8000/static/file.txt, but this returns an HTTP 404.
How do I write this minimal static file server script?

The assumption I made about sys.argv not being available when uvicorn loads your module is wrong, so it should work as you expect by moving your static setup outside of the __main__ guard:
import uvicorn
import sys
from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
server = FastAPI()
directory = sys.argv[1]
server.mount("/static", StaticFiles(directory=directory), name="static")
if __name__ == "__main__":
uvicorn.run(app="my_package:server")

When you call uvicorn.run(app="my_package:server"), it actually starts a separate process where my_package is imported. Therefore, everything inside if __name__ == "__main__": will not be run in the uvicorn process, so your directory will never be mounted.
One possible solution would be getting the directory from an environment variable, which is set from a small bash script:
from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
server = FastAPI()
directory = os.getenv("DIRECTORY")
server.mount("/static", StaticFiles(directory=directory), name="static")
start.sh:
#!/usr/bin/env bash
DIRECTORY=$1 uvicorn mypackage:server

Related

pipenv: pdfkit not found when running FastAPI app

I've been developing an API using FastAPI and pipenv to manage my virtual environment. To start my testing setup, I use the following commands on the console:
pipenv shell
uvicorn backend:app --reload
The API starts after a couple of seconds and everything is okay. However, recently I decided to install pdfkit to generate some PDF documents that need to be sent back to a webapp. However, after I did (using this this page as reference):
pipenv install pdfkit
Now, whenever I try to run the same uvicorn command to start my API, the API returns the error module pdfkit not found. I've checked that the package is properly installed and it does appear on my Pipfile and whenever I type pip freeze once my enviroment has been activated.
Here's a snippet of the API call that returning the error:
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse, StreamingResponse
from scipy.optimize import leastsq
from openpyxl import load_workbook
from openpyxl.utils.dataframe import dataframe_to_rows
from openpyxl.styles import Font
from openpyxl import Workbook
import pandas as pd
import numpy as np
import json
import os
# ==========================
# SETUP
# ==========================
app = FastAPI()
# ==========================
# CORS
# ==========================
origins = [
"http://127.0.0.1:5500/",
"http://127.0.0.1:8000/",
"http://localhost"
]
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # List of origins that are allowed to make cross-origin requests
allow_credentials=True, # Indicate that cookies should be supported for cross-origin requests
allow_methods=["*"], # List of HTTP methods that should be allowed for cross-origin requests
allow_headers=["*"], # List of HTTP headers that should be supported for cross-origin requests
)
# ==========================
# GET: PDF RENDERING
# ==========================
#app.get("/pdf")
def render_pdf(pdf_content: str):
from jinja2 import Template, Environment, FileSystemLoader
import pdfkit as pdf
# Jinja2 template "generator" or "main" object (AKA Environment)
env = Environment(loader = FileSystemLoader('PDF Rendering'), trim_blocks=True, lstrip_blocks=True)
# Template parameters
template_params = {
"BASE_PATH": os.getcwd(),
"REPORT_CONTENT": pdf_content,
}
# Render template
template = env.get_template("pdf_template.j2")
template_text = template.render(template_params)
with open("PDF Rendering/render.html", "w") as f:
f.write(template_text)
# Turn rendered file to PDF
pdf.from_file("PDF Rendering/render.html", "PDF Rendering/render.pdf")
return {
"message": "Success"
}
(The Jinja section works mind you, but for some reason, the pdfkit one doesnt)

fastapi cannot upload file larger than 1MB

I create a basic upload example as below. It works fine if the file size is less than 1MB, but got a 400 bad request, "detail": "There was an error parsing the body" once the file size is larger than 1MB. I have installed the python-multipart package.
from typing import List
import uvicorn
from fastapi import FastAPI, UploadFile, File
app = FastAPI(debug=True)
#app.post("/uploadfiles/")
async def create_upload_files(file: UploadFile = File(...)):
return {"filenames": file.filename}
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8000)
the problem is the version of anyio python package. Consider using versions >= 3.4.0.

How to start Bokeh server programmatically in dev / debug mode

I'm still developing my project and I want to make a lot of changes to UI through HTML/CSS, I'm starting the Bokeh server programmatically and it looks like this:
from tornado.ioloop import IOLoop
from tornado.web import StaticFileHandler
from bokeh.server.server import Server
from bokeh.application import Application
from bokeh.application.handlers.function import FunctionHandler
from os.path import dirname, join
import sys
if not dirname(__file__) in sys.path: sys.path.append(dirname(__file__))
from models.ProjectDataLoading import modify_page1
from models.Empty import modify_page2
page1_app = Application(FunctionHandler(modify_page1))
page2_app = Application(FunctionHandler(modify_page2))
StaticFileHandler.reset()
io_loop = IOLoop.current()
server = Server(applications = {'/ProjectDataLoading': page1_app,
'/PrimaveraObjects': page2_app,
'/SpecializedReports': page2_app,
'/StatisticalReports': page2_app,
'/Predictions': page2_app},
extra_patterns=[(r'/static/css/(.*)', StaticFileHandler, {'path':join(dirname(__file__),"static","css")})],
io_loop = io_loop, port = 5006, static_hash_cache=False, debug=True, autoreload=True, compiled_template_cache=False, serve_traceback=True)
server.start()
server.show('/ProjectDataLoading')
io_loop.start()
I'm still working on page1 -> "ProjectDataLoading", and as you can see I'm trying to make the server to stop cashing the static files with the following codes:
StaticFileHandler.reset()
static_hash_cache=False
debug=True
of course, it's wrong, because it's related to tornado application, not Bokeh server, so, how can I do it Bokeh server?
Almost all the code to support dev mode, i.e. watching a list file files and auto-reloading, is in the application part of the Bokeh server, not in the library. So you would need to essentially reproduce the code here:
https://github.com/bokeh/bokeh/blob/8f9cf0a85ba51098da2d027150e900bfc073eeba/bokeh/command/subcommands/serve.py#L785-L816

I cannot initialize Flask SocketIO with app.from_object parameter

I have an app.py file where I initialize my app. I have another file (run.py) where I run Flask server from. Everything works with a standard flask app. However I am trying to integrate flask-socketio and it keeps failing with different errors depending on how I try to initialize the app.
I have tried the following ways to initialize flask-socketio:
socketio = SocketIO(app.config.from_object(app_config[env_name]))
socketio = SocketIO(app, **app.config[env_name])
socketio = SocketIO(**app.config[env_name])
Here is the relevant code from my app.py file.
def create_app(env_name):
"""
Create app
"""
# app initiliazation
app = Flask(__name__)
app.config.from_object(app_config[env_name])
async_mode = None
# initializing bcrypt and db
bcrypt.init_app(app)
db.init_app(app)
socketio = SocketIO(app.config.from_object(app_config[env_name]))
return socketio
My run.py file looks like this:
rom src.app import create_app
load_dotenv(find_dotenv())
env_name = os.getenv('FLASK_ENV')
app = create_app(env_name)
if __name__ == '__main__':
port = os.getenv('PORT')
# run app
app.run(app, host='0.0.0.0', port=port)
You will notice I am importing from a config.py file. That is where my environment variables are being for (dev, test, prod). Each environment is it's own class. For example:
class Development(object):
"""
Development environment configuration
"""
DEBUG = True
TESTING = False
SQLALCHEMY_TRACK_MODIFICATIONS=False
JWT_SECRET_KEY = os.getenv('JWT_SECRET_KEY')
SQLALCHEMY_DATABASE_URI = os.getenv('DATABASE_URL')
ALLOWED_EXTENSIONS = set(['png', 'jpg', 'jpeg', 'gif'])
UPLOADED_FILES_DEST = os.getenv('UPLOADED_FILES_DEST')
As you can see, most of those values are set in an environment variable since it is bad practice to put such sensitive information into a repo.
I would like to be able to initialize flask-socketio so I can setup rooms where users can share location based information.
Thanks in advance.
The Flask-SocketIO extensions takes the application instance as an argument. You should configure the application and then initialize it. For example:
app.config.from_object(app_config[env_name])
socketio = SocketIO(app)
I found the issue. I cannot instantiate SocketIO in my app.py file. I have to export app from my app.py file and instantiate SocketIO in my run.py file. My final code looks like this:
app.py
def create_app(env_name):
"""
Create app
"""
# app initiliazation
app = Flask(__name__)
app.config.from_object(app_config[env_name])
# initializing bcrypt and db
bcrypt.init_app(app)
db.init_app(app)
return app
Finally, my run.py file looks like this.
import os
import logging
from dotenv import load_dotenv, find_dotenv
from flask_socketio import SocketIO, join_room, emit
from src.app import create_app
load_dotenv(find_dotenv())
env_name = os.getenv('FLASK_ENV')
app = create_app(env_name)
socketio = SocketIO(app)
if __name__ == '__main__':
port = os.getenv('PORT')
# run app
socketio.run(app, host='0.0.0.0', port=port)

SQLAlchemy extension isn't registered when running app with Gunicorn

I have an application that works in development, but when I try to run it with Gunicorn it gives an error that the "sqlalchemy extension was not registered". From what I've read it seems that I need to call app.app_context() somewhere, but I'm not sure where. How do I fix this error?
# run in development, works
python server.py
# try to run with gunicorn, fails
gunicorn --bind localhost:8000 server:app
AssertionError: The sqlalchemy extension was not registered to the current application. Please make sure to call init_app() first.
server.py:
from flask.ext.security import Security
from database import db
from application import app
from models import Studio, user_datastore
security = Security(app, user_datastore)
if __name__ == '__main__':
# with app.app_context(): ??
db.init_app(app)
app.run()
application.py:
from flask import Flask
app = Flask(__name__)
app.config.from_object('config.ProductionConfig')
database.py:
from flask.ext.sqlalchemy import SQLAlchemy
db = SQLAlchemy()
Only when you start your app with python sever.py is the if __name__ == '__main__': block hit, where you're registering your database with your app.
You'll need to move that line, db.init_app(app), outside that block.

Resources