How to run FastAPI application from Poetry? - python

I have a fastapi project built by poetry. I want to run the application with a scripts section in pyproject.tom like below:
poetry run start
What is inside double quotes in the section?
[tool.poetry.scripts]
start = ""
I tried to run the following script.
import uvicorn
from fastapi import FastAPI
app = FastAPI()
#app.get("/")
async def root():
return {"message": "Hello World"}
def main():
print("Hello World")
uvicorn.run(app, host="0.0.0.0", port=8000, reload=True, workers=2)
if __name__ == "__main__":
main()
It stops the application and just shows warning like this.
WARNING: You must pass the application as an import string to enable 'reload' or 'workers'.

I found the solution to this problem. See below:
In pyproject.toml
[tool.poetry.scripts]
start = "my_package.main:start"
In your main.py inside my_package folder.
import uvicorn
from fastapi import FastAPI
app = FastAPI()
#app.get("/")
async def root():
return {"message": "Hello World"}
def start():
"""Launched with `poetry run start` at root level"""
uvicorn.run("my_package.main:app", host="0.0.0.0", port=8000, reload=True)

You will need to pass the module path (module:function) to the start script in project.toml:
[tool.poetry.scripts]
start = "app:main"
Now run the command below will call the main function in the app module:
$ poetry run start

Just as the error message says, do
uvicorn.run("app")
Note also using reload and workers is useless and will just use the reloader. These flags are mutually exclusive

WARNING: You must pass the application as an import string to enable
'reload' or 'workers'.
try using the same way to run a basic script i.e file:variable
ERROR: Error loading ASGI app. Import string "app" must be in
format ":".
uvicorn.run("backend.main:app", host="0.0.0.0", port=8000, reload=True, workers=2)

Related

pytest requests to fastapi in a separate process

I am trying to have working tests for an API written with FastAPI.
I start the service in a separate process, run the tests with requests to the service, and check if the results are as expected.
I have extracted the key parts into a minimal working example, in the PD.
Running the MWE with the main file works fine. The tests fail, though.
Why do the tests fail?
How should we test APIs?
PD: the code is in a GIST, but now also here:
README.md
# Minimal example to ask in StackOverflow
Pytest does not allow to start a process with a service and tests requests to it,
at least not in the most straightforward way IMHO.
I may be missing something, or I may be doing something wrong.
Hence, I share this short code in a gist, to ask.
## How to run it
The dependencies are: `fastapi pytest requests uvicorn`.
You may install them with your package / environment manager of choice,
or use `pipenv install` with the provided `Pipfile`.
To run the code in the environment (e.g. `pipenv shell`), run: `python3 mwe.py`.
You should see everything is `OK`.
To run the test, run in the environment: `pytest`.
This does not work for me, the request times out.
mwe.py
import fastapi, multiprocessing, requests, time, uvicorn
app = fastapi.FastAPI()
#app.get('/ok')
def ok():
return 'OK'
class service:
def __enter__(self):
def run_service():
uvicorn.run('mwe:app', host='0.0.0.0', port=8000, reload=True)
self.service = multiprocessing.Process(target=run_service)
self.service.start()
time.sleep(10)
def __exit__(self, *args):
self.service.terminate()
def main():
with service():
return requests.get('http://127.0.0.1:8000/ok').ok
if __name__ == '__main__':
print('🆖🆗'[main()])
Pipfile
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"
[packages]
pytest = "*"
fastapi = "*"
requests = "*"
uvicorn = "*"
[requires]
python_version = "3.10"
python_full_version = "3.10.6"
test_mwe.py
from mwe import main
def test_main():
assert main()
Example for testing from official docs
from fastapi import FastAPI
from fastapi.testclient import TestClient
app = FastAPI()
#app.get("/")
async def read_main():
return {"msg": "Hello World"}
client = TestClient(app)
def test_read_main():
response = client.get("/")
assert response.status_code == 200
assert response.json() == {"msg": "Hello World"}
So, for you example from attached git it can be rewrited to something like this:
def test_get_ok():
response = client.get("/ok")
assert response.status_code == 200
assert response.text() == 'OK'
In a nutshell, it is difficult to describe how to test API correctly, but you can take as an example an excellent template for designing RESTfull API on FastAPI in which there are examples of autotests written just with the Pytest

404 for all routes with Flask on uWSGI/Gunicorn

I found and forked the following Flask/SQLAlchemy/Marshmallow example project:
https://github.com/summersab/RestaurantAPI
It works like a charm, and I used it to build a custom API. It runs flawlessly with:
python run.py
Then, I tried to run it on a proper webserver:
uwsgi --http localhost:5000 --wsgi-file run.py --callable app
However, I get 404 for all routes except /, /api, and /api/v1.0. I tried it on Gunicorn with the same results leading me to believe that the problem is with the code, not the webserver config.
All of the following posts have the same problem, but from what I could tell, none of them had solutions that worked for me (I may have missed something):
Flask on nginx + uWSGI returns a 404 error unless the linux directory exists
Unexplainable Flask 404 errors
Nginx+bottle+uwsgi Server returning 404 on every request
Flask application with Blueprints+uWSGI+nginx returning 404's (no routing?)
Flask Blueprint 404
Could someone look at the code in my repo and help me figure out what's wrong?
EDIT:
Per the response from #v25, I changed my run.py to the following:
from flask import Flask, redirect, render_template
from app import api_bp
from model import db, redis_cache
from config import DevelopmentConfig, TestingConfig, BaseConfig, PresentConfig
app = Flask(__name__)
t = 0
def create_app(config_filename):
app.config.from_object(config_filename)
global t
if t == 0:
app.register_blueprint(api_bp, url_prefix='/api/v1.0')
t = 1
if config_filename != TestingConfig:
db.init_app(app)
redis_cache.init_app(app)
return app
#app.route('/')
#app.route('/api/')
#app.route('/api/v1.0/')
def availableApps():
return render_template('availableApp.html')
PresentConfig = BaseConfig
app = create_app(PresentConfig)
if __name__ == "__main__":
app.run(use_debugger=False, use_reloader=False, passthrough_errors=True)
I then ran this with uwsgi, and it works as expected:
uwsgi --http localhost:5000 --wsgi-file run.py --callable app
Thanks for your help!
This could quickly be solved by creating a new file wsgi.py, with the following contents:
import run
PresentConfig = run.BaseConfig
app = run.create_app(PresentConfig)
Then execute with:
uwsgi --http localhost:5000 --wsgi-file wsgi.py --callable app
or...
gunicorn --bind 'localhost:5000' wsgi:app
Why does this work...
If you have a look inside the run.py file, and note what's happening when you launch that directly with python:
if __name__ == "__main__":
PresentConfig = BaseConfig
app = create_app(PresentConfig)
app.run(use_debugger=False, use_reloader=False, passthrough_errors=True)
you can see that app is being created, based on the return value of the create_app function which is passed a config. Note also that the create_app function registers the "other URLs" as part of the api_bp blueprint.
However the code inside this if clause is never executed when the app is executed with uwsgi/gunicorn; instead the app object which is imported is one without the other URLs registered.
By creating the wsgi.py file above, you're doing all of this in a manner which can then be improted by the wsgi/gunicorn executable.
With that in mind, another way to fix this would be to change the last four lines of run.py to look more like:
PresentConfig = BaseConfig
app = create_app(PresentConfig)
if __name__ == "__main__":
app.run(use_debugger=False, use_reloader=False, passthrough_errors=True)
You could then execute this with your original wsgi command.
It's worth noting this may break other code which does from run import app and expects app not to be the return value of create_app (unlikely in this case).
Let me know if you need clarification on anything.

Flask + Wsgi returning python shell script output

I have deployed two containers flask + wsgi and nginx I have a simple code which works returning hello world.
When I try to return the output of a python shell script to a webpage I get internal server error, the script it works via cli it even prints the output of docker ps.
Working code returns a simple hello world :
# app.py
from flask import Flask
app = Flask(__name__)
#app.route('/')
def hello_world():
return 'Hello world!'
if __name__ == '__main__':
app.run(host='0.0.0.0')
Not working code i get internal server error please help im not really sure why ... or how to debug it
#!/usr/bin/env python
import subprocess
def dockers():
call = subprocess.call('docker ps', shell=True)
return call
#!/user/bin/env python
from flask import Flask
from cont import dockers
app = Flask(__name__)
print(dockers())
#app.route('/')
def hello_world():
return dockers()
if __name__ == '__main__':
app.run(host='0.0.0.0')
Dont ever try to pass an object to a web page youll have a bad time. i wrote the result into a file split the lines to a list and returned it to the webpage.

python unitests for sanic app

I'm building CRUD REST APIs using peewee ORM and sanic(sanic-crud) as app server. Things are working fine. And I wrote couple of unittest cases for the same.
But, I'm facing problem running unittests. The problem is that unittests starts sanic app server and stalled there. Its not running unittest cases at all. But when I press Ctrl+C manually then the sanic server gets terminated and unittests execution starts. So, it means there should be a way to start sanic server and continue unittests run and terminate server at the end.
Can someone please me the correct way writting unittest cases for sanic app?
I followed official docs too but no luck.
http://sanic.readthedocs.io/en/latest/sanic/testing.html
I tried following
from restapi import app # the execution stalled here i guess
import unittest
import asyncio
import aiohttp
class AutoRestTests(unittest.TestCase):
''' Unit testcases for REST APIs '''
def setUp(self):
self.loop = asyncio.new_event_loop()
asyncio.set_event_loop(None)
def test_get_metrics_all(self):
#asyncio.coroutine
def get_all():
res = app.test_client.get('/metrics')
assert res.status == 201
self.loop.run_until_complete(get_all())
from restapi.py
app = Sanic(__name__)
generate_crud(app, [Metrics, ...])
app.run(host='0.0.0.0', port=1337, workers=4, debug=True)
Finally managed to run unittests by moving app.run statement to main block
# tiny app server starts here
app = Sanic(__name__)
generate_crud(app, [Metrics, ...])
if __name__ == '__main__':
app.run(host='0.0.0.0', port=1337, debug=True)
# workers=4, log_config=LOGGING)
and
from restapi import app
import json
import unittest
class AutoRestTests(unittest.TestCase):
''' Unit testcases for REST APIs '''
def test_get_metrics_all(self):
request, response = app.test_client.get('/metrics')
self.assertEqual(response.status, 200)
data = json.loads(response.text)
self.assertEqual(data['metric_name'], 'vCPU')
if __name__ == '__main__':
unittest.main()

Use Flask's Click CLI with the app factory pattern

I define my Flask application using the app factory pattern. When using Flask-Script, I can pass the factory function to the Manager. I'd like to use Flask's built-in Click CLI instead. How do I use the factory with Click?
My current code uses Flask-Script. How do I do this with Click?
from flask import Flask
from flask_script import Manager, Shell
def create_app():
app = Flask(__name__)
...
return app
manager = Manager(create_app)
def make_shell_context():
return dict(app=app, db=db, User=User, Role=Role)
manager.add_command('shell', Shell(make_context=make_shell_context))
if __name__ == '__main__':
manager.run()
The flask command is a Click interface created with flask.cli.FlaskGroup. Create your own group and pass it the factory function. Use app.shell_context_processor to add objects to the shell.
from flask import Flask
from flask.cli import FlaskGroup
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
def create_app(script_info=None):
app = Flask(__name__)
db.init_app(app)
...
#app.shell_context_processor
def shell_context():
return {'app': app, 'db': db}
return app
cli = FlaskGroup(create_app=create_app)
#cli.command
def custom_command():
pass
if __name__ == '__main__':
cli()
Run your file instead of the flask command. You'll get the Click interface using your factory.
FLASK_DEBUG=1 python app.py run
Ideally, create an entry point and install your package in your env. Then you can call the script as a command. Create a setup.py file with at least the following.
project/
app/
__init__.py
setup.py
from setuptools import setup, find_packages
setup(
name='my_app',
version='1.0.0',
packages=find_packages(),
entry_points={
'console_scripts': [
'app=app:cli',
],
},
)
pip install -e /path/to/project
FLASK_DEBUG=1 app run
Using your own CLI is less robust than the built-in flask command. Because your cli object is defined with your other code, a module-level error will cause the reloader to fail because it can no longer import the object. The flask command is separate from your project, so it's not affected by errors in your module.
Newly updated for Flask >= 2.1. See my other answer for Flask < 2.1.
In order to pass arguments to our app, we store them in script_info. And in order to do that, we create a custom Click interface using flask.cli.FlaskGroup.
However, passing script_info directly to app factories is deprecated in Flask 2, so we use
Click's get_current_context function to get the current context and then access script_info from that context.
manage.py
#!/usr/bin/env python
import click
import config
from click import get_current_context
from flask import Flask
from flask.cli import FlaskGroup, pass_script_info
def create_app(*args, **kwargs):
app = Flask(__name__)
ctx = get_current_context(silent=True)
if ctx:
script_info = ctx.obj
config_mode = script_info.config_mode
elif kwargs.get("config_mode"):
# Production server, e.g., gunincorn
# We don't have access to the current context, so must
# read kwargs instead.
config_mode = kwargs["config_mode"]
...
return app
#click.group(cls=FlaskGroup, create_app=create_app)
#click.option('-m', '--config-mode', default="Development")
#pass_script_info
def manager(script_info, config_mode):
script_info.config_mode = config_mode
...
if __name__ == "__main__":
manager()
Now you can run the dev server and set your desired config_mode by using either -m or --config-mode. Note, until Flask 2.1 drops you have to install Flask#aa13521d42bfdb
pip install git+https://github.com/pallets/flask.git#aa13521d42bfdb
python manage.py -m Production run
Production servers like gunincorn don't have access to the current context, so we pass what we need via kwargs.
gunicorn app:create_app\(config_mode=\'Production\'\) -w 3 -k gevent
In order to pass arguments to your app factory, you need to make use of script_info like so...
manage.py
#!/usr/bin/env python
import click
import config
from flask import Flask
from flask.cli import FlaskGroup, pass_script_info
def create_app(script_info):
app = Flask(__name__)
if script_info.config_mode:
obj = getattr(config, script_info.config_mode)
flask_config.from_object(obj)
...
return app
#click.group(cls=FlaskGroup, create_app=create_app)
#click.option('-m', '--config-mode', default="Development")
#pass_script_info
def manager(script_info, config_mode):
script_info.config_mode = config_mode
if __name__ == "__main__":
manager()
config.py
class Config(object):
TESTING = False
class Production(Config):
DATABASE_URI = 'mysql://user#localhost/foo'
class Development(Config):
DATABASE_URI = 'sqlite:///app.db'
class Testing(Config):
TESTING = True
DATABASE_URI = 'sqlite:///:memory:'
now in the command line you can do manage -m Production run (after either adding the entry_points to setup.py as #davidism mentioned, or running pip install manage.py).

Categories

Resources