Skip to content

Commit 2cfd225

Browse files
authored
Merge pull request #5 from rafsaf/fastapi-users-support
Fastapi users support
2 parents 949dd71 + bd25047 commit 2cfd225

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

88 files changed

+3104
-666
lines changed

.github/workflows/tests.yml

Lines changed: 47 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ jobs:
99
runs-on: ubuntu-20.04
1010
strategy:
1111
matrix:
12-
python-version: [3.7, 3.8, 3.9]
12+
python-version: ["3.9", "3.10"]
1313

1414
services:
1515
postgres:
@@ -32,33 +32,64 @@ jobs:
3232
uses: actions/setup-python@v2
3333
with:
3434
python-version: ${{ matrix.python-version }}
35-
- name: Load cached venv
36-
id: cached-poetry-dependencies
35+
- name: Load cached venv1
36+
id: cached-poetry-dependencies1
3737
uses: actions/cache@v2
3838
with:
39-
path: .venv
40-
key: venv-${{ runner.os }}-${{ matrix.python-version }}-${{ hashFiles('**/poetry.lock') }}
39+
path: .venv1
40+
key: venv-${{ runner.os }}-${{ matrix.python-version }}-${{ hashFiles('{{cookiecutter.project_name}}/template_minimal/poetry.lock') }}
4141
- name: Install dependencies and actiavte virtualenv
42-
if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true'
42+
if: steps.cached-poetry-dependencies1.outputs.cache-hit != 'true'
4343
run: |
44-
python -m venv .venv
45-
source .venv/bin/activate
46-
pip install -r {{cookiecutter.project_name}}/requirements-dev.txt
44+
python -m venv .venv1
45+
source .venv1/bin/activate
46+
pip install -r {{cookiecutter.project_name}}/template_minimal/requirements-dev.txt
4747
pip install cookiecutter
48-
- name: Lint with flake8
48+
- name: Load cached venv2
49+
id: cached-poetry-dependencies2
50+
uses: actions/cache@v2
51+
with:
52+
path: .venv2
53+
key: venv-${{ runner.os }}-${{ matrix.python-version }}-${{ hashFiles('{{cookiecutter.project_name}}/template_fastapi_users/poetry.lock') }}
54+
- name: Install dependencies and actiavte virtualenv
55+
if: steps.cached-poetry-dependencies2.outputs.cache-hit != 'true'
56+
run: |
57+
python -m venv .venv2
58+
source .venv2/bin/activate
59+
pip install -r {{cookiecutter.project_name}}/template_fastapi_users/requirements-dev.txt
60+
pip install cookiecutter
61+
- name: Lint with flake8 minimal project
62+
run: |
63+
source .venv1/bin/activate
64+
# stop the build if there are Python syntax errors or undefined names
65+
cd \{\{cookiecutter.project_name\}\}/template_minimal
66+
flake8 app --count --exit-zero --statistics
67+
- name: Lint with flake8 fastapi_users project
4968
run: |
50-
source .venv/bin/activate
69+
source .venv2/bin/activate
5170
# stop the build if there are Python syntax errors or undefined names
52-
cd \{\{cookiecutter.project_name\}\}/
71+
cd \{\{cookiecutter.project_name\}\}/template_fastapi_users
5372
flake8 app --count --exit-zero --statistics
54-
- name: Test new cookiecuttered project is passing pytest test
73+
- name: Test minimal project is passing pytest test
74+
run: |
75+
source .venv1/bin/activate
76+
python tests/create_minimal_project.py
77+
export TEST_DATABASE_HOSTNAME=localhost
78+
export TEST_DATABASE_USER=test
79+
export TEST_DATABASE_PASSWORD=test
80+
export TEST_DATABASE_PORT=30000
81+
export TEST_DATABASE_DB=test
82+
83+
pytest minimal_project
84+
85+
- name: Test fastapi_users project is passing pytest test
5586
run: |
56-
source .venv/bin/activate
57-
python tests/create_test_project.py
87+
source .venv2/bin/activate
88+
python tests/create_fastapi_users_project.py
5889
export TEST_DATABASE_HOSTNAME=localhost
5990
export TEST_DATABASE_USER=test
6091
export TEST_DATABASE_PASSWORD=test
6192
export TEST_DATABASE_PORT=30000
6293
export TEST_DATABASE_DB=test
6394
64-
pytest generated_project_for_tests
95+
pytest fastapi_users_project

.gitignore

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,5 @@
1-
.vscode
1+
.vscode
2+
.venv
3+
venv
4+
.venv1
5+
.venv2

README.md

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,22 @@
22
<img src="https://github.com/rafsaf/minimal-fastapi-postgres-template/workflows/tests/badge.svg" alt="Test">
33
</a>
44

5-
<a href="https://github.com/rafsaf/respo/blob/main/LICENSE" target="_blank">
6-
<img src="https://img.shields.io/github/license/rafsaf/respo" alt="License">
5+
<a href="https://github.com/rafsaf/minimal-fastapi-postgres-template/blob/main/LICENSE" target="_blank">
6+
<img src="https://img.shields.io/github/license/rafsaf/minimal-fastapi-postgres-template" alt="License">
77
</a>
88

9+
## Minimal FastAPI Postgres Template
10+
11+
There are two templates to choose from, first **minimal template** is a minimal template for FastAPI backend + postgresql db as of 2021.11, async style for database sessions, endpoints and tests. It provides basic codebase that almost every application has, but nothing more.
12+
13+
Seconds one, **fastapi-users template** is similar to minimal in structure, but based on popular library [FastAPI Users](https://fastapi-users.github.io/fastapi-users/) and very powerful (yet it require using it for user accounts management).
14+
15+
:bangbang: | This repository contains two different templates to choose from.
16+
:---: | :---
17+
918
## Minimal async FastAPI + postgresql template
1019

11-
![OpenAPIexample](./docs/OpenAPI_example.png)
20+
1221

1322
- SQLAlchemy using new 2.0 API + async queries
1423
- Postgresql database under `asyncpg`
@@ -20,13 +29,24 @@
2029
- `pre-push.sh` script with poetry export, autoflake, black, isort and flake8
2130
- Setup for async tests, one func test for token flow and very extensible `conftest.py`
2231

23-
## What this repo is
32+
![template-fastapi-minimal-openapi-example](./docs/template-minimal-openapi-example.png)
33+
34+
## Async FastAPI + postgresql template based on [fastapi-users](https://fastapi-users.github.io/fastapi-users/)
2435

25-
This is a minimal template for FastAPI backend + postgresql db as of 2021.11, `async` style for database sessions, endpoints and tests. It provides basic codebase that almost every application has, but nothing more.
36+
- Extensible base user model
37+
- Ready-to-use register, login, reset password and verify e-mail routes
38+
- Ready-to-use social OAuth2 login flow
39+
- SQLAlchemy using new 2.0 API + async queries
40+
- Postgresql database under `asyncpg`
41+
- Alembic migrations
42+
- Very minimal project structure yet ready for quick start building new api
43+
- Two databases in docker-compose.yml (second one for tests)
44+
- poetry
45+
- `pre-push.sh` script with poetry export, autoflake, black, isort and flake8
46+
- Setup for async tests, one func test for token flow and very extensible `conftest.py`
2647

27-
## What this repo is not
48+
![template-fastapi-users-openapi-example](./docs/template-fastapi-users-openapi-example.png)
2849

29-
It is not complex, full featured solutions for all human kind problems. It doesn't include any third party that isn't necessary for most of apps (dashboards, queues) or implementation differs so much in every project that it's pointless (complex User model, emails, RBAC, permissions).
3050

3151
## Quickstart
3252

@@ -37,6 +57,9 @@ pip install cookiecutter
3757
# And cookiecutter this project :)
3858
cookiecutter https://github.com/rafsaf/minimal-fastapi-postgres-template
3959

60+
# decide if u want fastapi-users based template by
61+
# changing "use_fastapi_users" in cookiecutter option to True, by default it is minimal template.
62+
4063
cd project_name
4164
# Poetry install (and activate environment!)
4265
poetry install
@@ -62,9 +85,11 @@ This project is heavily base on official template https://github.com/tiangolo/fu
6285

6386
`2.0` style SQLAlchemy API is good enough so there is no need to write everything in `crud` and waste our time... The `core` folder was also rewritten. There is great base for writting tests in `tests`, but I didn't want to write hundreds of them, I noticed that usually after changes in the structure of the project, auto tests are useless and you have to write them from scratch anyway (delete old ones...), hence less than more. Similarly with the `User` model, it is very modest, because it will be adapted to the project anyway (and there are no tests for these endpoints, you would remove them probably).
6487

65-
## Step by step example
88+
On January 30 I added another template to this repository, that is based on [FastAPI Users](https://fastapi-users.github.io/fastapi-users/) project. The main assumptions have not changed and project structure is very similar. What it gives is not buggy, extensible, tested Users accounts managing system. This is a lot, if your project needs to manage accounts, this is definitely better than minimal template.
89+
90+
## Step by step example for minimal template
6691

67-
I always enjoy to to have some kind of example in templates (even if I don't like it much, _some_ parts may be useful and save my time...), so let's create `POST` endpoint for creating dogs.
92+
I always enjoy to to have some kind of example in templates (even if I don't like it much, _some_ parts may be useful and save my time...), so let's create `POST` endpoint for creating dogs. For second template, there may be some differences (imports etc.)
6893

6994
### 1. Add `HappyDog` model
7095

cookiecutter.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
{
2-
"project_name": "Base Project"
3-
}
2+
"project_name": "Base Project",
3+
"use_fastapi_users": false
4+
}

docs/OpenAPI_example.png

-55.2 KB
Binary file not shown.
Loading
127 KB
Loading

hooks/post_gen_project.py

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,44 @@
11
"""
2+
Copy template choosen from TEMPLATES to main folder.
3+
24
Copy content from env template (autogenerated passwords etc)
3-
to .env which is gitignored, then remove template env file as it is redundant
5+
to .env which is gitignored, then remove template env file as it is redundant.
46
"""
5-
67
from pathlib import Path
8+
from shutil import copytree, rmtree
9+
10+
PROJECT_NAME = "{{ cookiecutter.project_name }}"
11+
USE_FASTAPI_USERS = "{{ cookiecutter.use_fastapi_users }}"
12+
TEMPLATES = ["template_fastapi_users", "template_minimal"]
13+
14+
15+
def copy_choosen_template_to_main_dir(used_template: str):
16+
if used_template not in TEMPLATES:
17+
raise ValueError(f"{used_template} not in {TEMPLATES}")
18+
19+
copytree(used_template, "./", dirs_exist_ok=True)
20+
21+
for template in TEMPLATES:
22+
rmtree(Path(template))
23+
24+
25+
def create_env_file_and_remove_env_template():
26+
env_template_file = Path(".env.template")
27+
env_file = Path(".env")
28+
29+
env_file.write_text(env_template_file.read_text())
30+
env_template_file.unlink()
31+
732

8-
env_template_file = Path("./.env.template")
9-
env_file = Path("./.env")
33+
if __name__ == "__main__":
34+
truthy = ["T", "t", "true", "True", 1]
35+
falsy = ["F", "f", "false", "False", 0]
36+
if USE_FASTAPI_USERS in truthy:
37+
used_template = "template_fastapi_users"
38+
elif USE_FASTAPI_USERS in falsy:
39+
used_template = "template_minimal"
40+
else:
41+
raise ValueError(f"use_fastapi_users param must be in {truthy + falsy}")
1042

11-
env_file.write_text(env_template_file.read_text())
12-
env_template_file.unlink()
43+
copy_choosen_template_to_main_dir(used_template=used_template)
44+
create_env_file_and_remove_env_template()

tests/create_fastapi_users_project.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
"""
2+
Creates fastapi_users template project in root folder with default values
3+
"""
4+
5+
from pathlib import Path
6+
7+
from cookiecutter.main import cookiecutter
8+
9+
ROOT_FOLDER = Path(__file__).parent.parent
10+
11+
12+
def main():
13+
cookiecutter(
14+
template=str(ROOT_FOLDER),
15+
no_input=True,
16+
extra_context={
17+
"project_name": "fastapi_users_project",
18+
"use_fastapi_users": True,
19+
},
20+
)
21+
22+
23+
if __name__ == "__main__":
24+
main()

tests/create_test_project.py renamed to tests/create_minimal_project.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"""
2-
Creates template project in root folder with default values
2+
Creates minimal template project in root folder with default values
33
"""
44

55
from pathlib import Path
@@ -14,7 +14,8 @@ def main():
1414
template=str(ROOT_FOLDER),
1515
no_input=True,
1616
extra_context={
17-
"project_name": "generated_project_for_tests",
17+
"project_name": "minimal_project",
18+
"use_fastapi_users": False,
1819
},
1920
)
2021

{{cookiecutter.project_name}}/nginx-unit-config.json

Lines changed: 0 additions & 15 deletions
This file was deleted.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
SECRET_KEY="DVnFmhwvjEhJZpuhndxjhlezxQPJmBIIkMDEmFREWQADPcUnrG"
2+
ENVIRONMENT="DEV"
3+
ACCESS_TOKEN_EXPIRE_MINUTES="11520"
4+
BACKEND_CORS_ORIGINS="http://localhost:3000,http://localhost:8001"
5+
6+
DEFAULT_DATABASE_HOSTNAME="localhost"
7+
DEFAULT_DATABASE_USER="rDGJeEDqAz"
8+
DEFAULT_DATABASE_PASSWORD="XsPQhCoEfOQZueDjsILetLDUvbvSxAMnrVtgVZpmdcSssUgbvs"
9+
DEFAULT_DATABASE_PORT="5387"
10+
DEFAULT_DATABASE_DB="default_db"
11+
12+
TEST_DATABASE_HOSTNAME="localhost"
13+
TEST_DATABASE_USER="test"
14+
TEST_DATABASE_PASSWORD="ywRCUjJijmQoBmWxIfLldOoITPzajPSNvTvHyugQoSqGwNcvQE"
15+
TEST_DATABASE_PORT="37270"
16+
TEST_DATABASE_DB="test_db"
17+
18+
FIRST_SUPERUSER_EMAIL="example@example.com"
19+
FIRST_SUPERUSER_PASSWORD="OdLknKQJMUwuhpAVHvRC"
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
SECRET_KEY="{{ random_ascii_string(50) }}"
2+
ENVIRONMENT="DEV"
3+
ACCESS_TOKEN_EXPIRE_MINUTES="11520"
4+
BACKEND_CORS_ORIGINS="http://localhost:3000,http://localhost:8001"
5+
6+
DEFAULT_DATABASE_HOSTNAME="localhost"
7+
DEFAULT_DATABASE_USER="{{ random_ascii_string(10) }}"
8+
DEFAULT_DATABASE_PASSWORD="{{ random_ascii_string(50) }}"
9+
DEFAULT_DATABASE_PORT="{{ range(4000, 7000) | random }}"
10+
DEFAULT_DATABASE_DB="default_db"
11+
12+
TEST_DATABASE_HOSTNAME="localhost"
13+
TEST_DATABASE_USER="test"
14+
TEST_DATABASE_PASSWORD="{{ random_ascii_string(50) }}"
15+
TEST_DATABASE_PORT="{{ range(30000, 40000) | random }}"
16+
TEST_DATABASE_DB="test_db"
17+
18+
FIRST_SUPERUSER_EMAIL="example@example.com"
19+
FIRST_SUPERUSER_PASSWORD="{{ random_ascii_string(20) }}"
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,29 @@
11
# See https://unit.nginx.org/installation/#docker-images
22

3-
FROM nginx/unit:1.25.0-python3.9
3+
FROM nginx/unit:1.26.1-python3.9
44

55
ENV PYTHONUNBUFFERED 1
66

7-
# Nginx unit config and init.sh will be consumed at container startup.
8-
COPY ./app/init.sh /docker-entrypoint.d/init.sh
9-
COPY ./nginx-unit-config.json /docker-entrypoint.d/config.json
10-
RUN chmod +x /docker-entrypoint.d/init.sh
7+
RUN apt update && apt install -y python3-pip
118

129
# Build folder for our app, only stuff that matters copied.
1310
RUN mkdir build
1411
WORKDIR /build
1512

16-
COPY ./app ./app
17-
COPY ./alembic ./alembic
18-
COPY ./alembic.ini .
13+
# Update, install requirements and then cleanup.
1914
COPY ./requirements.txt .
2015

21-
# Update, install requirements and then cleanup.
22-
RUN apt update && apt install -y python3-pip \
23-
&& pip3 install -r requirements.txt \
16+
RUN pip3 install -r requirements.txt \
2417
&& apt remove -y python3-pip \
2518
&& apt autoremove --purge -y \
26-
&& rm -rf /var/lib/apt/lists/* /etc/apt/sources.list.d/*.list
19+
&& rm -rf /var/lib/apt/lists/* /etc/apt/sources.list.d/*.list
20+
21+
# Copy the rest of app
22+
COPY ./app ./app
23+
COPY ./alembic ./alembic
24+
COPY ./alembic.ini .
25+
26+
# Nginx unit config and init.sh will be consumed at container startup.
27+
COPY ./app/init.sh /docker-entrypoint.d/init.sh
28+
COPY ./nginx-unit-config.json /docker-entrypoint.d/config.json
29+
RUN chmod a+x /docker-entrypoint.d/init.sh

{{cookiecutter.project_name}}/alembic/env.py renamed to {{cookiecutter.project_name}}/template_fastapi_users/alembic/env.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from sqlalchemy.ext.asyncio import AsyncEngine
66
from asyncio import get_event_loop
77

8-
from app.core.config import settings
8+
from app.core import config as app_config
99
from alembic import context
1010

1111
# this is the Alembic Config object, which provides
@@ -31,7 +31,7 @@
3131

3232

3333
def get_database_uri():
34-
return settings.DEFAULT_SQLALCHEMY_DATABASE_URI
34+
return app_config.settings.DEFAULT_SQLALCHEMY_DATABASE_URI
3535

3636

3737
def run_migrations_offline():

0 commit comments

Comments
 (0)