294 lines
9.8 KiB
Markdown
294 lines
9.8 KiB
Markdown
# FastAPI template
|
|
It is a simple and handy FastAPI template
|
|
for combined frontend and backend as in Flask.
|
|
Includes Jinja, WTForms, MySQL ORM and Docker.
|
|
|
|
|
|
## Features
|
|
- Basic FastAPI app
|
|
- `@app.route`s are defined in separate files
|
|
- Lots of helper functions
|
|
- Jinja2 templates, WTForms
|
|
- MySQL/MariaDB database, SQLAlchemy 2
|
|
- AutoPEP8 formatter, MyPy and Pylint
|
|
- Dockerfile, Docker-Compose
|
|
|
|
|
|
## Usage
|
|
1. Create a repository from this template
|
|
2. For debugging, change the database connection parameters
|
|
in `.env_debug` file corresponding to the configuration
|
|
of MariaDB/MySQL server on your PC
|
|
1. If you have no MariaDB/MySQL test server installed,
|
|
search for "[mariadb install (your linux distro)](https://searx.dc09.ru/search?q=mariadb+install+ubuntu)",
|
|
for example: [an article on DigitalOcean for Ubuntu 22.04](https://url.dc09.ru/mdbu22do)
|
|
3. Edit app [paths](#paths),
|
|
[custom error pages](#custom-error-pages)
|
|
and [forms](#wtforms)
|
|
as explained in the "Structure" section
|
|
4. Create your own Jinja templates in the `templates/` directory,
|
|
editing `base.html` and inheriting other templates from it is recommended
|
|
(see `index,table,admin.html` for examples)
|
|
5. Edit or remove CSS and JS files in `static/`
|
|
6. Edit `sql/models,schemas,crud.py`
|
|
corresponding to your database structure
|
|
as explained below ([Structure > Database](#database))
|
|
7. Check if `Makefile`, `Dockerfile`, `docker-compose.yml` are correct
|
|
8. Run the formatter and linters (`make format`, then `make check`)
|
|
9. Open `.env` and `.env_db`, generate secret keys and the database password
|
|
as explained in the comment above `SESSION_KEY=`
|
|
10. Edit the `version_code` file if needed,
|
|
build a docker image and [publish](#publishing-app) it
|
|
|
|
### Makefile
|
|
Make commands:
|
|
|Command|Description|
|
|
|:-----:|:----------|
|
|
|`make format`|Format the code using AutoPEP8|
|
|
|`make check`|Check the code with linters (MyPy, Pylint)|
|
|
|`make dev`|Run the app in development mode|
|
|
|`make prod`|Run a production server|
|
|
|`make docker` or `make docker-build`|Build a docker image from `Dockerfile`|
|
|
|`make docker-push`|Upload the built image to Docker Hub|
|
|
|`make docker-run`|Starts the containers with Docker Compose|
|
|
|`make clean`|Clean all cache|
|
|
|
|
|
|
## Structure
|
|
|
|
### Configuration
|
|
#### `.env`s
|
|
- `.env_debug` contains the DB configuration and tokens
|
|
for the development environment, must not be used in production
|
|
- `.env` is a config for the application itself,
|
|
loaded only in docker-compose by default
|
|
- `.env_db` is a config for MySQL/MariaDB server,
|
|
also loaded only in docker-compose for the mariadb container
|
|
- `version` contains only one variable, your application version code;
|
|
change it whatever you want or leave `1.0.0`
|
|
#### The main config loaded by `app/common.py`:
|
|
- `templates_dir`, `static_dir` contain the paths
|
|
to templates and static files directories correspondingly
|
|
- `settings` (pydantic.BaseSettings):
|
|
- `debug` means the application is running
|
|
in a development environment
|
|
- `session_key`, `csrf_key` are secret keys
|
|
for WTForms CSRF protection;
|
|
generated with secrets.token_urlsafe
|
|
if are not set in the environment variables
|
|
- `templates` is a Jinja2Templates FastAPI object,
|
|
shouldn't be used manually, see
|
|
Helper Functions -> respond.with_tmpl
|
|
below
|
|
#### The database config loaded by `sql/__init__.py`:
|
|
- `sql_settings` (pydantic.BaseSettings):
|
|
- `db_host`
|
|
- `db_port`
|
|
- `db_user`
|
|
- `db_password`
|
|
- `db_database`
|
|
- `db_url` is the MySQL connection URL
|
|
generated from the sql_settings configuration;
|
|
just edit the line declaring `db_url` in `db.py`
|
|
if you are going to use other DBMS, e.g. PostgreSQL
|
|
|
|
### Paths
|
|
`app/paths` directory contains all FastAPI paths
|
|
separated into multiple files.
|
|
Each file have a class inside with the `add_paths` method.
|
|
This method is called on application startup.
|
|
|
|
You can
|
|
- add your own paths to the existing files
|
|
- rename or delete a file in `app/paths/`
|
|
(it's *not* recommended to do this with `errors.py`,
|
|
see about [error pages](#custom-error-pages) below)
|
|
- create a new file in this directory
|
|
copying the contents of `pages.py`
|
|
|
|
> **Note**
|
|
>
|
|
> In the paths files, FastAPI's decorators
|
|
are called with `@self.app.`, not just `@app.`
|
|
|
|
In case of deleting/renaming/creating any paths files,
|
|
`app/main.py` also must be modified:
|
|
1. Find the comment `# Add your own paths...`
|
|
2. Add or remove import statements below
|
|
3. Add or remove elements in `paths` list below
|
|
|
|
### Custom Error Pages
|
|
`app/paths/errors.py` automatically adds error handlers
|
|
when the application launches.
|
|
By default, 404 and 500 HTTP codes are configured
|
|
for the custom pages (`templates/404.html` and `500.html`),
|
|
but you can add your own:
|
|
1. Open `errors.py`
|
|
2. Find the comment `# Add other...`
|
|
3. Add (or remove) elements to the list below
|
|
|
|
### WTForms
|
|
[Related docs page](https://github.com/muicss/starlette-wtf/blob/master/README.md)
|
|
|
|
Each file in the `app/forms` directory
|
|
contains a WTForms class (or multiple classes).
|
|
You can simply copy `users.py` content
|
|
to create a form and use it in your project.
|
|
|
|
Also, there is one helper function in `__init__.py`
|
|
named `get_form` for instantiating WTForms class.
|
|
It should be preferred to the direct constructor call.
|
|
|
|
### Sample website
|
|
Included templates and CSS/JS files, paths, forms and SQL database
|
|
are related to the sample website (for showing how the template works)
|
|
and can be deleted or edited, **except:**
|
|
- `__init__.py`s
|
|
- `main.py`
|
|
- `common.py`
|
|
- `respond.py`
|
|
- `paths/errors.py`
|
|
|
|
### Database
|
|
[Related docs page](https://fastapi.tiangolo.com/tutorial/sql-databases/)
|
|
|
|
- SQLAlchemy models are stored in `app/sql/models.py`
|
|
- Pydantic models (schemas)
|
|
used for more handy SQLAlchemy models processing
|
|
are stored in `app/sql/schemas.py`
|
|
- Functions executing SQL CRUD requests
|
|
are stored in `app/sql/crud.py`
|
|
|
|
> **Note**
|
|
>
|
|
> As the official FastAPI documentation recommends (the link is above),
|
|
there are two schemas — `User` and `UserCreate` — for one `User` SQLAlchemy model.
|
|
> The first one is needed for SELECT requests,
|
|
and the second one is for INSERT requests (creating users).
|
|
> The first one contains all information about user,
|
|
but the second one (`UserCreate`) omits `id` field,
|
|
because we don't want to specify an ID when creating a new user,
|
|
it will be generated automatically by the MariaDB server.
|
|
|
|
|
|
## Helper functions
|
|
|
|
### `respond.py`
|
|
Import: `from . import respond`
|
|
|
|
#### `with_redirect(url=/, code=200, ...)`
|
|
Return a redirect to the page specified in `url`.
|
|
By default, code is 302 so method is changed to GET.
|
|
To leave the same HTTP method, use 307 status code
|
|
or call `with_redirect_307` function.
|
|
`args` and `kwargs` are passed directly
|
|
to the Response constructor.
|
|
|
|
**Args:**
|
|
- url (str, optional): Target URL, root by default
|
|
- code (int, optional): HTTP response code
|
|
|
|
**Returns:** FastAPI's RedirectResponse object
|
|
|
|
#### `with_redirect_302(url=/, ...)`
|
|
#### `with_redirect_307(url=/, ...)`
|
|
As said before,
|
|
- HTTP 302 Found redirects to a page
|
|
without saving the same method
|
|
as in the current request
|
|
- HTTP 307 Temporary Redirect
|
|
doesn't change the method
|
|
(POST request = POST method after redirect)
|
|
|
|
#### `with_text(content, code=200, ...)`
|
|
Return a plain text to the user.
|
|
`args` and `kwargs` are passed directly
|
|
to the Response constructor.
|
|
|
|
**Args:**
|
|
- content (str): Plain text content
|
|
- code (int, optional): HTTP response code
|
|
|
|
**Returns:** FastAPI's PlainTextResponse object
|
|
|
|
#### `with_template(name, request, code=200, ..., **context)`
|
|
#### `with_tmpl(name, request, code=200, ..., **context)`
|
|
Render a Jinja2 template and return Response object.
|
|
`response_class` parameter is not needed.
|
|
A small explanation about the `request` function argument:
|
|
```python
|
|
from fastapi import Request
|
|
from . import respond
|
|
|
|
@app.get('/')
|
|
async def main_page(req: Request): # <--
|
|
return respond.with_tmpl(
|
|
'index.html',
|
|
request=req, # <--
|
|
...
|
|
)
|
|
```
|
|
FastAPI will automatically pass the Request object
|
|
to your function if you specify
|
|
the correct type hint (`: Request`)
|
|
|
|
**Args:**
|
|
- name (str): Template filename
|
|
- request (Request): FastAPI request object
|
|
- code (int, optional): HTTP response code
|
|
- headers (Optional[Mapping[str, str]], optional):
|
|
Additional headers, passed to the Response constructor
|
|
- background (Optional[BackgroundTask], optional):
|
|
Background task, passed to the Response constructor
|
|
|
|
**Returns:** FastAPI's TemplateResponse object
|
|
|
|
#### `with_file(path, mime=None, code=200, ...)`
|
|
Send the file specified in `path`
|
|
automatically guessing its mimetype if `mime` is None.
|
|
`args` and `kwargs` are passed directly
|
|
to the Response constructor.
|
|
|
|
**Args:**
|
|
- path (os.PathLike): File path
|
|
- mime (Optional[str], optional): File mimetype
|
|
- code (int, optional): HTTP response code
|
|
|
|
**Returns:** FileResponse: FastAPI's FileResponse object
|
|
|
|
### `forms/__init__.py`
|
|
Import: `from . import forms`
|
|
|
|
#### `async get_form(form, req)`
|
|
Almost the same as `form.from_formdata`,
|
|
and must be used *instead* of instantiating
|
|
form object directly as in Flask.
|
|
See `respond.with_tmpl` for explanation
|
|
about the `request` argument.
|
|
|
|
**Args:**
|
|
- form (Type[StarletteForm]): StarletteForm class
|
|
- req (Request): Request object
|
|
|
|
**Returns:** StarletteForm instance
|
|
|
|
### `sql/__init__.py`
|
|
Import: `from . import sql`
|
|
|
|
#### `async get_db()`
|
|
FastAPI dependency returning database Session object.
|
|
Code is copied from the official docs.
|
|
|
|
**Yields:** SQLAlchemy Session object
|
|
|
|
|
|
## Publishing app
|
|
First of all, check the `version_code` file and correct it if needed,
|
|
then build a Docker image using `make docker` command.
|
|
|
|
Follow [this documentation page](https://docs.docker.com/get-started/04_sharing_app/)
|
|
to create an account in the Docker Hub.
|
|
|
|
To publish the image, you can use `make docker-push` command,
|
|
or manually enter the image name and its tag (version):
|
|
`docker push yourname/yourapp:tag`
|