Skip to content

Commit a3e3b71

Browse files
committed
new version 1
1 parent 4beed04 commit a3e3b71

24 files changed

+334
-289
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.vscode

README.md

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
# Full featured FastAPI template, all boring and tedious things are covered
1+
## Small FastAPI template, all boring and tedious things covered
2+
3+
![OpenAPIexample](./docs/OpenAPI_example.png)
24

35
- SQLAlchemy using new 2.0 API + async queries
46
- Postgresql database under `asyncpg`
@@ -10,10 +12,18 @@
1012
- `pre-push.sh` script with poetry export, autoflake, black, isort and flake8
1113
- Setup for tests, one big test for token flow and very extensible `conftest.py`
1214

13-
# Quickstart
15+
## What this repo is
16+
17+
This is a minimal template for FastAPI backend + postgresql db as of 2021, `async` style for database sessions, endpoints and tests. It provides basic codebase that almost every application has, but nothing more.
18+
19+
## What this repo is not
20+
21+
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).
22+
23+
## Quickstart
1424

1525
```bash
16-
# You can install it globally
26+
# You can even install it globally
1727
pip install cookiecutter
1828

1929
# And cookiecutter this project :)
@@ -38,8 +48,12 @@ pytest
3848
# in official template
3949
```
4050

41-
# About
51+
## About
4252

4353
This project is heavily base on official template https://github.com/tiangolo/full-stack-fastapi-postgresql (and on my previous work: [link1](https://github.com/rafsaf/fastapi-plan), [link2](https://github.com/rafsaf/docker-fastapi-projects)), but as it is now not too much up-to-date, it is much easier to create new one than change official. I didn't like some of conventions over there also (`crud` and `db` folders for example).
4454

45-
`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, 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)
55+
`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).
56+
57+
## Learn by example
58+
59+
Let's imagine we need to create API for a website where users brag about their dogs... or whatever, they just can crud dogs in user panel for some reason. We will add dummy model Dog to our API, with relation to the default table User and crud auth endpoints, then test it shortly.

docs/OpenAPI_example.png

55.2 KB
Loading
Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,15 @@
11
DEBUG=true
22
SECRET_KEY=string
33
ACCESS_TOKEN_EXPIRE_MINUTES=11520
4+
REFRESH_TOKEN_EXPIRE_MINUTES=40320
45

5-
6-
VERSION="0.1.0"
7-
DESCRIPTION=string
8-
PROJECT_NAME=string
9-
API_STR=
106
BACKEND_CORS_ORIGINS=http://localhost:3000,http://localhost:8001
117

12-
POSTGRES_USER=postgres
13-
POSTGRES_PASSWORD=strong_password
8+
DEFAULT_DATABASE_USER=postgres
9+
DEFAULT_DATABASE_PASSWORD=strong_password
1410
POSTGRES_SERVER=db
15-
POSTGRES_DB=db
16-
POSTGRES_PORT=4999
11+
DEFAULT_DATABASE_DB=db
12+
DEFAULT_DATABASE_PORT=4999
1713

1814
FIRST_SUPERUSER_EMAIL=example@example.com
1915
FIRST_SUPERUSER_PASSWORD=string_password
Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,42 @@
11
DEBUG=true
22
SECRET_KEY="{{ random_ascii_string(50) }}"
33
ACCESS_TOKEN_EXPIRE_MINUTES=11520
4+
REFRESH_TOKEN_EXPIRE_MINUTES=40320
45

5-
6-
VERSION="0.1.0"
7-
DESCRIPTION="{{ cookiecutter.project_name }}"
8-
PROJECT_NAME="{{ cookiecutter.project_name }}"
9-
API_STR=
106
BACKEND_CORS_ORIGINS=http://localhost:3000,http://localhost:8001
117

12-
POSTGRES_USER=postgres
13-
POSTGRES_PASSWORD="{{ random_ascii_string(50) }}"
8+
DEFAULT_DATABASE_USER="{{ random_ascii_string(10) }}"
9+
DEFAULT_DATABASE_PASSWORD="{{ random_ascii_string(50) }}"
1410
POSTGRES_SERVER=db
15-
POSTGRES_DB=db
16-
POSTGRES_PORT=4999
11+
DEFAULT_DATABASE_DB=db
12+
DEFAULT_DATABASE_PORT=4999
1713

1814
FIRST_SUPERUSER_EMAIL=example@example.com
1915
FIRST_SUPERUSER_PASSWORD="{{ random_ascii_string(20) }}"
16+
17+
18+
19+
SECRET_KEY="{{ random_ascii_string(50) }}"
20+
ENVIRONMENT="DEV"
21+
ACCESS_TOKEN_EXPIRE_MINUTES=11520
22+
REFRESH_TOKEN_EXPIRE_MINUTES=40320
23+
BACKEND_CORS_ORIGINS=http://localhost:3000,http://localhost:8001
24+
25+
DEFAULT_DATABASE_HOSTNAME=localhost
26+
DEFAULT_DATABASE_USER="{{ random_ascii_string(10) }}"
27+
DEFAULT_DATABASE_PASSWORD="{{ random_ascii_string(50) }}"
28+
DEFAULT_DATABASE_PORT={{ range(4000, 7000) | random }}
29+
DEFAULT_DATABASE_DB: str
30+
DEFAULT_SQLALCHEMY_DATABASE_URI: str = ""
31+
32+
# POSTGRESQL TEST DATABASE
33+
TEST_DATABASE_HOSTNAME: str
34+
TEST_DATABASE_USER: str
35+
TEST_DATABASE_PASSWORD: str
36+
TEST_DATABASE_PORT = {{ range(30000, 40000) | random }}
37+
TEST_DATABASE_DB: str
38+
TEST_SQLALCHEMY_DATABASE_URI: str = ""
39+
40+
# FIRST SUPERUSER
41+
FIRST_SUPERUSER_EMAIL: EmailStr
42+
FIRST_SUPERUSER_PASSWORD: str

{{cookiecutter.project_name}}/.flake8

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,7 @@ ignore = E203, E501, W503
55
exclude =
66
__init__.py,
77
.venv,
8+
venv,
89
__pycache__,
910
.github,
1011
.vscode,
11-
config,
12-
locale,
13-
webhook
14-
app/tests/conftest.py

{{cookiecutter.project_name}}/Dockerfile

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
1+
# See https://hub.docker.com/r/nginx/unit/
12
FROM nginx/unit:1.25.0-python3.9
23

3-
# Our Debian with Python and Nginx for python apps.
4-
# See https://hub.docker.com/r/nginx/unit/
54
ENV PYTHONUNBUFFERED 1
65

7-
COPY ./app/initial.sh /docker-entrypoint.d/initial.sh
8-
COPY ./config/config.json /docker-entrypoint.d/config.json
6+
# Nginx unit config and init.sh will be used at container startup
7+
COPY ./app/init.sh /docker-entrypoint.d/init.sh
8+
COPY ./nginx-unit-config.json /docker-entrypoint.d/config.json
9+
RUN chmod +x /docker-entrypoint.d/init.sh
910

11+
# Build folder for our app, only stuff that matters copied.
1012
RUN mkdir build
11-
12-
# We create folder named build for our app.
1313
WORKDIR /build
1414

1515
COPY ./app ./app
16+
COPY ./alembic ./alembic
1617
COPY ./alembic.ini .
1718
COPY ./requirements.txt .
1819

19-
# We copy our app folder to the /build
20-
20+
# Requirements and then cleanup
2121
RUN apt update && apt install -y python3-pip \
2222
&& pip3 install -r requirements.txt \
2323
&& apt remove -y python3-pip \

{{cookiecutter.project_name}}/alembic/env.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,9 @@
3131

3232

3333
def get_database_uri():
34-
return settings.SQLALCHEMY_DATABASE_URI
34+
if settings.ENVIRONMENT == "PYTEST":
35+
return settings.TEST_SQLALCHEMY_DATABASE_URI
36+
return settings.DEFAULT_SQLALCHEMY_DATABASE_URI
3537

3638

3739
def run_migrations_offline():
@@ -52,14 +54,17 @@ def run_migrations_offline():
5254
target_metadata=target_metadata,
5355
literal_binds=True,
5456
dialect_opts={"paramstyle": "named"},
57+
compare_type=True,
5558
)
5659

5760
with context.begin_transaction():
5861
context.run_migrations()
5962

6063

6164
def do_run_migrations(connection):
62-
context.configure(connection=connection, target_metadata=target_metadata)
65+
context.configure(
66+
connection=connection, target_metadata=target_metadata, compare_type=True
67+
)
6368

6469
with context.begin_transaction():
6570
context.run_migrations()

{{cookiecutter.project_name}}/app/api/deps.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from app.models import User
1414
from app.session import async_session
1515

16-
reusable_oauth2 = OAuth2PasswordBearer(tokenUrl=f"{settings.API_STR}/auth/access-token")
16+
reusable_oauth2 = OAuth2PasswordBearer(tokenUrl="/auth/access-token")
1717

1818

1919
async def get_session() -> AsyncGenerator[AsyncSession, None]:

{{cookiecutter.project_name}}/app/api/endpoints/auth.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ async def login_access_token(
2929
if user is None:
3030
raise HTTPException(status_code=400, detail="Incorrect email or password")
3131

32-
if not security.verify_password(form_data.password, user.hashed_password):
32+
if not security.verify_password(form_data.password, user.hashed_password): # type: ignore
3333
raise HTTPException(status_code=400, detail="Incorrect email or password")
3434

3535
access_token, expire_at = security.create_access_token(user.id)

0 commit comments

Comments
 (0)