Intro Introducing FastAPI + TypeScript + OpenAPI as a technology stack for quickly creating web applications with machine learning and image processing as the back end implemented in Python.
--I want to set up a web server (API server) quickly with Python
――Use like you used Flask until now
--"In Python"
――Because it is a machine learning / image processing service
―― "Crispy"
――I want to enjoy validation
--I want type guarantee for both server and client
――Machine learning and image processing apps tend to have many parameters ・ There is no consistent convention, so it is easy to make mistakes
- width
or w
--The range of values is [0, w]
or [0, 1]
?
-→ I want to cover with type annotation
FastAPI? https://fastapi.tiangolo.com/
--Starlette base
Demo
Of the above motivations
――Set up an API quickly --Type-guaranteed clients can be used
Demo of that
Click here for sample Repository: https://github.com/tuttieee/fastapi-typescript-openapi-example
Basically, proceed according to the Fast API tutorial, but it has been slightly modified for ↑.
--Including frontend (TypeScript) in the sample to cover up to creating WebApp --Develop and deploy with Docker
As per https://fastapi.tiangolo.com/deployment/.
--Digging one level and putting it in / backend
to make it a monorepo with frontend.
――In order to add migration etc. later, dig another layer and make it / backend / app / app
. / backend / app
is mounted in docker container
--Manage with docker-compose
After docker-compose build
, docker-compose up
, try curl localhost: 8000
$ curl localhost:8000
{"Hello":"World"}
Sample: https://github.com/tuttieee/fastapi-typescript-openapi-example/commit/703af24fff8d39e261e1f1ce5ba859f28e806fb2
docker-compose.devel.yml
--Mount the host directory
--Enable auto-reload
--Specify /start-reload.sh
for docker run command to enable auto-reload (your [official Docker image](https://hub.docker.com/r/tiangolo/uvicorn-" gunicorn-fastapi /) option)Sample: https://github.com/tuttieee/fastapi-typescript-openapi-example/commit/33f6e91bc48c9c6396a9ceaf1e720b88c78a6822
Basically proceed according to ↓. The part that uses MySQL is added independently.
https://fastapi.tiangolo.com/tutorial/sql-databases/
For the time being, use Dockerfile directly RUN pip install sqlalchemy
Sample: https://github.com/tuttieee/fastapi-typescript-openapi-example/commit/75b7d2d7ccb8bc5a142b86bd3800b5e190732628
--Create ʻapp / app / __ init__.py, ʻapp / app / database.py
, ʻapp / app / models.py --Try to see if it works --Enter the shell and
python -i`
-Try hitting ↓
from app import models
from app.database import SessionLocal, engine
models.Base.metadata.create_all(bind=engine)
user = models.User(email='[email protected]', hashed_password='')
db = SessionLocal()
db.add(user)
db.commit()
db.close()
db = SessionLocal()
db.query(models.User).all()
Sample: https://github.com/tuttieee/fastapi-typescript-openapi-example/commit/8f0d85445c33bfddd05466060b11501c2f3748b3
MySQL
RUN apt-get update && apt-get install -y \
default-mysql-client \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
RUN pip install sqlalchemy PyMySQL
sample:
-Use Official image. Basically, write the settings in docker-compose.yml
as per the README
--Add Credentials to .env
and pass it as an environment variable with docker-compose.yml
Sample: https://github.com/tuttieee/fastapi-typescript-openapi-example/commit/22d760d963394f340622ea402fdcf72f02cd240f
Alembic
--Added pip install alembic
to Dockerfile
(will it be converted to requirements.txt?)-> Docker-compose build
--Update database.py
--Update database URL for MySQL
--Update create_engine
for MySQL (remove unnecessary arguments)
--Added naming convention
Tips: You can use the starlette class as it is
Then enter the shell and do the following:
alembic init migrations
Ref: https://alembic.sqlalchemy.org/en/latest/tutorial.htmlSample: https://github.com/tuttieee/fastapi-typescript-openapi-example/commit/979f4b3982b3ce3e7631661af3171000d6ea0381
Ref: https://alembic.sqlalchemy.org/en/latest/autogenerate.html
--Editing ʻalembic.ini --Delete
sqlalchemy.url (because it will be taken from
database.py) --Edit
migrations / env.py`
-Added ↓
from app.models import Base
from app.database import SQLALCHEMY_DATABASE_URL
config.set_main_option("sqlalchemy.url", SQLALCHEMY_DATABASE_URL)
target_metadata = Base.metadata
--Define models.py
for MySQL (specify the length in String
)
alembic revision --autogenerate -m "Add users and items tables"
Sample: https://github.com/tuttieee/fastapi-typescript-openapi-example/commit/4ceae799f6f6b83ffc71cfec5ce13a669e137757
alembic upgrade head
--Run the following with python -i
from app import models
from app.database import SessionLocal
user = models.User(email='[email protected]', hashed_password='')
db = SessionLocal()
db.add(user)
db.commit()
db.close()
db = SessionLocal()
db.query(models.User).all()
See https://fastapi.tiangolo.com/tutorial/sql-databases/#create-initial-pydantic-models-schemas ʻApp / schemas.py` created
See https://fastapi.tiangolo.com/tutorial/sql-databases/#crud-utils ʻApp / crud.py` created
See https://fastapi.tiangolo.com/tutorial/sql-databases/#main-fastapi-app ʻApp / main.py` update
Sample: https://github.com/tuttieee/fastapi-typescript-openapi-example/commit/1e7d140c1d7929c15f7815648deb14f831807ddd
cURL example:
curl localhost:8000/users/
curl -X POST -d '{"email": "[email protected]", "password": "test"}' localhost:8000/users/
From OpenAPI http://localhost:8000/docs
MISC
prestart.sh
backend / app / app / main.py
--Sample: https://github.com/tuttieee/fastapi-typescript-openapi-example/commit/36474ed029fbf6a03dc3d815ecfe7462ac3cf1fefrom fastapi.routing import APIRoute
...
def use_route_names_as_operation_ids(app: FastAPI) -> None:
"""
Simplify operation IDs so that generated API clients have simpler function
names.
Should be called only after all routes have been added.
"""
for route in app.routes:
if isinstance(route, APIRoute):
route.operation_id = route.name
use_route_names_as_operation_ids(app)
Frontend Create React App
$ yarn create react-app frontend --template typescript
$ cd frontend
$ yarn start
Sample: https://github.com/tuttieee/fastapi-typescript-openapi-example/commit/21b1f0f926132fdbf9c1dbef967ef4286fa27279
--CORS settings
--Added the following to backend / app / app / main.py
# TODO: This is for development. Remove it for production.
origins = [
"<http://localhost:3000",>
]
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
yarn add axios
--Try fetching roughly with frontend / src / App.tsx
import React, { useState, useEffect } from 'react';
import './App.css';
import { DefaultApi, Configuration, User } from './api-client';
const config = new Configuration({ basePath: 'http://localhost:8000' }); // TODO: This is for dev
export const apiClient = new DefaultApi(config);
const App: React.FC = () => {
const [users, setUsers] = useState<User[]>([]);
useEffect(() => {
apiClient.readUsers().then((response) => {
setUsers(response.data);
})
})
return (
<div className="App">
<ul>
{users.map(user =>
<li key={user.id}>{user.email}</li>
)}
</ul>
</div>
);
}
export default App;
Sample: https://github.com/tuttieee/fastapi-typescript-openapi-example/commit/79fc0bb26c6ffa7e6e43473f0ef497dd1c2f53ff
--Must be typed manually --Write yourself ʻinterface User`
Intro OpenAPI: Former Swagger FastAPI will generate a JSON definition for OpenAPI: <http: //localhost: 8000/openapi.json> OpenAPI Generator that eats this JSON and generates clients in various languages
See https://openapi-generator.tech/docs/installation.html#docker
docker-compose.openapi-generator.yml
version: "3.7"
services:
openapi-generator:
image: openapitools/openapi-generator-cli
volumes:
- ./frontend:/frontend
working_dir: /frontend
command:
- generate
- -g
- typescript-axios
- -i
- <http://backend/openapi.json>
- -o
- /frontend/src/api-client
- --additional-properties=supportsES6=true,modelPropertyNaming=original
# modelPropertyNaming=original is necessary though camelCase is preferred
# See <https://github.com/OpenAPITools/openapi-generator/issues/2976>
--Add command to Makefile
oapi/gen:
docker-compose -f docker-compose.yml -f docker-compose.openapi-generator.yml up openapi-generator \
&& docker-compose -f docker-compose.yml -f docker-compose.openapi-generator.yml rm -f openapi-generator
frontend/src/App.tsx
import React, { useState, useEffect } from 'react';
import './App.css';
import { DefaultApi, Configuration, User } from './api-client';
const config = new Configuration({ basePath: '<http://localhost:8000'> }); // TODO: This is for dev
export const apiClient = new DefaultApi(config);
const App: React.FC = () => {
const [users, setUsers] = useState<User[]>([]);
useEffect(() => {
apiClient.readUsers().then((response) => {
setUsers(response.data);
})
})
return (
<div className="App">
<ul>
{users.map(user =>
<li>{user.email}</li>
)}
</ul>
</div>
);
}
export default App;
Sample: https://github.com/tuttieee/fastapi-typescript-openapi-example/commit/1d38242a63c9a5477fa1cde506f36f68a19060a7
In the actual project, Unittest, CI, Linter, etc. are adopted as appropriate.
Recommended Posts