Skip to content

Commit dfab46d

Browse files
committed
migration to 2.0 style, new project outfit
1 parent 2a3d33c commit dfab46d

26 files changed

+822
-945
lines changed

.vscode/settings.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
{
2-
"python.formatting.provider": "black"
2+
"python.formatting.provider": "black",
3+
"python.analysis.typeCheckingMode": "basic",
34
}

{{cookiecutter.project_name}}/.env

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ POSTGRES_USER=postgres
1313
POSTGRES_PASSWORD="{{ random_ascii_string(50) }}"
1414
POSTGRES_SERVER=db
1515
POSTGRES_DB=db
16-
POSTGRES_PORT=5432
16+
POSTGRES_PORT=4999
1717

1818
FIRST_SUPERUSER_EMAIL=example@example.com
1919
FIRST_SUPERUSER_PASSWORD="{{ random_ascii_string(20) }}"

{{cookiecutter.project_name}}/alembic.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
script_location = alembic
66

77
# template used to generate migration files
8-
# file_template = %%(rev)s_%%(slug)s
8+
file_template = %%(year)d%%(month).2d%%(day).2d%%(hour).2d%%(minute).2d%%(second).2d_%%(rev)s_%%(slug)s
99

1010
# sys.path path, will be prepended to sys.path if present.
1111
# defaults to the current working directory.

{{cookiecutter.project_name}}/alembic/versions/ccdd01c9a0c6_init.py renamed to {{cookiecutter.project_name}}/alembic/versions/20211023022523_b6b74331d700_init.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
"""init
22
3-
Revision ID: ccdd01c9a0c6
3+
Revision ID: b6b74331d700
44
Revises:
5-
Create Date: 2021-09-24 21:52:47.513823
5+
Create Date: 2021-10-23 02:25:23.653930
66
77
"""
88
from alembic import op
99
import sqlalchemy as sa
1010

1111

1212
# revision identifiers, used by Alembic.
13-
revision = 'ccdd01c9a0c6'
13+
revision = 'b6b74331d700'
1414
down_revision = None
1515
branch_labels = None
1616
depends_on = None
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from fastapi import APIRouter
22

3-
from app.api.endpoints import login, users
3+
from app.api.endpoints import auth, users
44

55
api_router = APIRouter()
6-
api_router.include_router(login.router, tags=["login"])
6+
api_router.include_router(auth.router, prefix="/auth", tags=["auth"])
77
api_router.include_router(users.router, prefix="/users", tags=["users"])
Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,35 @@
1-
from typing import Generator
1+
from typing import Generator, Optional
22

33
from fastapi import Depends, HTTPException, status
44
from fastapi.security import OAuth2PasswordBearer
55
from jose import jwt
66
from pydantic import ValidationError
7+
from sqlalchemy import select
78
from sqlalchemy.orm import Session
89

9-
from app import crud, models, schemas
10+
from app import schemas
1011
from app.core import security
1112
from app.core.config import settings
12-
from app.db.session import SessionLocal
13+
from app.models import User
14+
from app.session import SessionLocal
1315

1416
reusable_oauth2 = OAuth2PasswordBearer(
1517
tokenUrl=f"{settings.API_STR}/login/access-token"
1618
)
1719

1820

19-
def get_db() -> Generator:
21+
def get_session() -> Generator:
2022
try:
21-
db: Session = SessionLocal()
22-
yield db
23+
session: Session = SessionLocal()
24+
yield session
2325
except:
2426
raise Exception
25-
db.close()
27+
session.close()
2628

2729

2830
def get_current_user(
29-
db: Session = Depends(get_db), token: str = Depends(reusable_oauth2)
30-
) -> models.User:
31+
session: Session = Depends(get_session), token: str = Depends(reusable_oauth2)
32+
) -> User:
3133

3234
try:
3335
payload = jwt.decode(
@@ -39,13 +41,16 @@ def get_current_user(
3941
status_code=status.HTTP_403_FORBIDDEN,
4042
detail="Could not validate credentials",
4143
)
42-
user = crud.user.get(db, id=token_data.sub)
44+
user: Optional[User] = (
45+
session.execute(select(User).where(User.id == token_data.sub)).scalars().first()
46+
)
47+
4348
if not user:
4449
raise HTTPException(status_code=404, detail="User not found")
4550
return user
4651

4752

4853
def get_current_active_user(
49-
current_user: models.User = Depends(get_current_user),
50-
) -> models.User:
54+
current_user: User = Depends(get_current_user),
55+
) -> User:
5156
return current_user

{{cookiecutter.project_name}}/app/api/endpoints/login.py renamed to {{cookiecutter.project_name}}/app/api/endpoints/auth.py

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,40 @@
1-
from typing import Any
1+
from typing import Optional
22

33
from fastapi import APIRouter, Depends, HTTPException, status
44
from fastapi.security import OAuth2PasswordRequestForm
55
from jose import jwt
66
from pydantic import ValidationError
7+
from sqlalchemy import select
78
from sqlalchemy.orm import Session
89

9-
from app import crud, models, schemas
10+
from app import schemas
1011
from app.api import deps
1112
from app.core import security
1213
from app.core.config import settings
14+
from app.models import User
1315

1416
router = APIRouter()
1517

1618

17-
@router.post("/login/access-token", response_model=schemas.Token)
19+
@router.post("/access-token", response_model=schemas.Token)
1820
def login_access_token(
19-
db: Session = Depends(deps.get_db), form_data: OAuth2PasswordRequestForm = Depends()
20-
) -> Any:
21+
session: Session = Depends(deps.get_session),
22+
form_data: OAuth2PasswordRequestForm = Depends(),
23+
):
2124
"""
22-
OAuth2 compatible token login, get an access token for future requests
25+
OAuth2 compatible token, get an access token for future requests using username and password
2326
"""
24-
user = crud.user.authenticate(
25-
db, email=form_data.username, password=form_data.password
27+
28+
user: Optional[User] = (
29+
session.execute(select(User).where(User.email == form_data.username))
30+
.scalars()
31+
.first()
2632
)
27-
if not user:
33+
34+
if user is None:
35+
raise HTTPException(status_code=400, detail="Incorrect email or password")
36+
37+
if not security.verify_password(form_data.password, user.hashed_password):
2838
raise HTTPException(status_code=400, detail="Incorrect email or password")
2939

3040
access_token, expire_at = security.create_access_token(user.id)
@@ -38,16 +48,19 @@ def login_access_token(
3848
}
3949

4050

41-
@router.post("/login/test-token", response_model=schemas.User)
42-
def test_token(current_user: models.User = Depends(deps.get_current_user)) -> Any:
51+
@router.post("/test-token", response_model=schemas.User)
52+
def test_token(current_user: User = Depends(deps.get_current_user)):
4353
"""
4454
Test access token
4555
"""
4656
return current_user
4757

4858

4959
@router.post("/refresh-token", response_model=schemas.Token)
50-
async def refresh_token(refresh_token: str, db: Session = Depends(deps.get_db)):
60+
def refresh_token(refresh_token: str, session: Session = Depends(deps.get_session)):
61+
"""
62+
OAuth2 compatible token, get an access token for future requests using refresh token
63+
"""
5164
try:
5265
payload = jwt.decode(
5366
refresh_token, settings.SECRET_KEY, algorithms=[security.ALGORITHM]
@@ -63,8 +76,12 @@ async def refresh_token(refresh_token: str, db: Session = Depends(deps.get_db)):
6376
status_code=status.HTTP_403_FORBIDDEN,
6477
detail="Could not validate credentials",
6578
)
66-
user = crud.user.get(db, id=token_data.sub)
67-
if not user:
79+
80+
user: Optional[User] = (
81+
session.execute(select(User).where(User.id == token_data.sub)).scalars().first()
82+
)
83+
84+
if user is None:
6885
raise HTTPException(status_code=404, detail="User not found")
6986
access_token, expire_at = security.create_access_token(user.id)
7087
refresh_token, refresh_expire_at = security.create_refresh_token(user.id)

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

Lines changed: 19 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,41 @@
11
from typing import Any
22

3-
from fastapi import APIRouter, Body, Depends
4-
from fastapi.encoders import jsonable_encoder
5-
from pydantic.networks import EmailStr
3+
from fastapi import APIRouter, Depends
64
from sqlalchemy.orm import Session
75

8-
from app import crud, models, schemas
6+
from app import models, schemas
97
from app.api import deps
8+
from app.core.security import get_password_hash
109

1110
router = APIRouter()
1211

1312

1413
@router.put("/me", response_model=schemas.User)
1514
def update_user_me(
16-
*,
17-
db: Session = Depends(deps.get_db),
18-
password: str = Body(None),
19-
full_name: str = Body(None),
20-
email: EmailStr = Body(None),
15+
user_update: schemas.UserUpdate,
16+
session: Session = Depends(deps.get_session),
2117
current_user: models.User = Depends(deps.get_current_active_user),
2218
) -> Any:
2319
"""
24-
Update own user.
20+
Update me.
2521
"""
26-
current_user_data = jsonable_encoder(current_user)
27-
user_in = schemas.UserUpdate(**current_user_data)
28-
if password is not None:
29-
user_in.password = password
30-
if full_name is not None:
31-
user_in.full_name = full_name
32-
if email is not None:
33-
user_in.email = email
34-
user = crud.user.update(db, db_obj=current_user, obj_in=user_in)
35-
return user
22+
if user_update.password is not None:
23+
current_user.hashed_password = get_password_hash(user_update.password)
24+
if user_update.full_name is not None:
25+
current_user.full_name = user_update.full_name
26+
if user_update.email is not None:
27+
current_user.email = user_update.email
28+
29+
session.add(current_user)
30+
session.commit()
31+
session.refresh(current_user)
32+
33+
return current_user
3634

3735

3836
@router.get("/me", response_model=schemas.User)
3937
def read_user_me(
40-
db: Session = Depends(deps.get_db),
38+
session: Session = Depends(deps.get_session),
4139
current_user: models.User = Depends(deps.get_current_active_user),
4240
) -> Any:
4341
"""

{{cookiecutter.project_name}}/app/core/config.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from pathlib import Path
2-
from typing import Dict, List, Union
2+
from typing import Dict, List, Optional, Union
33

44
from pydantic import AnyHttpUrl, AnyUrl, BaseSettings, EmailStr, validator
55

@@ -42,12 +42,16 @@ def _assemble_cors_origins(cls, cors_origins):
4242
return cors_origins
4343

4444
@validator("SQLALCHEMY_DATABASE_URI", pre=True)
45-
def _assemble_db_connection(cls, v: str, values: Dict[str, str]) -> str:
45+
def _assemble_db_connection(cls, v: str, values: Dict[str, Optional[str]]) -> str:
4646
if v != "":
4747
return v
4848
if values.get("DEBUG"):
4949
postgres_server = "localhost"
5050
else:
51+
assert (
52+
values.get("POSTGRES_SERVER") is not None
53+
), "Variable POSTGRES_SERVER cannot be None"
54+
5155
postgres_server = values.get("POSTGRES_SERVER")
5256

5357
return AnyUrl.build(

{{cookiecutter.project_name}}/app/crud/__init__.py

Lines changed: 0 additions & 9 deletions
This file was deleted.

0 commit comments

Comments
 (0)