Skip to content

Commit

Permalink
✨ Add Items (crud, models, endpoints), utils, refactor (#14)
Browse files Browse the repository at this point in the history
* Update CRUD utils to use types better.
* Simplify Pydantic model names, from `UserInCreate` to `UserCreate`, etc.
* Upgrade packages.
* Add new generic "Items" models, crud utils, endpoints, and tests. To facilitate re-using them to create new functionality. As they are simple and generic (not like Users), it's easier to copy-paste and adapt them to each use case.
* Update endpoints/*path operations* to simplify code and use new utilities, prefix and tags in `include_router`.
* Update testing utils.
* Update linting rules, relax vulture to reduce false positives.
* Update migrations to include new Items.
* Update project README.md with tips about how to start with backend.
  • Loading branch information
tiangolo authored Apr 19, 2019
1 parent 1fe4908 commit ecd634e
Show file tree
Hide file tree
Showing 32 changed files with 426 additions and 1,091 deletions.
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,17 @@ After using this generator, your new project (the directory created) will contai

### Next release

* PR <a href="https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/14" target="_blank">#14</a>:
* Update CRUD utils to use types better.
* Simplify Pydantic model names, from `UserInCreate` to `UserCreate`, etc.
* Upgrade packages.
* Add new generic "Items" models, crud utils, endpoints, and tests. To facilitate re-using them to create new functionality. As they are simple and generic (not like Users), it's easier to copy-paste and adapt them to each use case.
* Update endpoints/*path operations* to simplify code and use new utilities, prefix and tags in `include_router`.
* Update testing utils.
* Update linting rules, relax vulture to reduce false positives.
* Update migrations to include new Items.
* Update project README.md with tips about how to start with backend.

* Upgrade Python to 3.7 as Celery is now compatible too. <a href="https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/10" target="_blank">PR #10</a> by <a href="https://github.com/ebreton" target="_blank">@ebreton</a>.

### 0.2.2
Expand Down
4 changes: 3 additions & 1 deletion {{cookiecutter.project_slug}}/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,9 @@ If your Docker is not running in `localhost` (the URLs above wouldn't work) chec

### General workflow

Add and modify SQLAlchemy models in `./backend/app/app/db_models/`, Pydantic models in `./backend/app/app/models` and API endpoints in `./backend/app/app/api/`.
Open your editor at `./backend/app/` (instead of the project root: `./`), so that you see an `./app/` directory with your code inside. That way, your editor will be able to find all the imports, etc.

Modify or add SQLAlchemy models in `./backend/app/app/db_models/`, Pydantic models in `./backend/app/app/models/`, API endpoints in `./backend/app/app/api/`, CRUD (Create, Read, Update, Delete) utils in `./backend/app/app/crud/`. The easiest might be to copy the ones for Items (models, endpoints, and CRUD utils) and update them to your needs.

Add and modify tasks to the Celery worker in `./backend/app/app/worker.py`.

Expand Down
2 changes: 1 addition & 1 deletion {{cookiecutter.project_slug}}/backend/app/Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ pyjwt = "*"
python-multipart = "*"
email-validator = "*"
requests = "*"
celery = "~=4.3"
celery = "*"
passlib = {extras = ["bcrypt"],version = "*"}
tenacity = "*"
pydantic = "*"
Expand Down
1,022 changes: 0 additions & 1,022 deletions {{cookiecutter.project_slug}}/backend/app/Pipfile.lock

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
"""First revision
Revision ID: e6ae69e9dcb9
Revision ID: d4867f3a4c0a
Revises:
Create Date: 2019-02-13 14:27:57.038583
Create Date: 2019-04-17 13:53:32.978401
"""
from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = 'e6ae69e9dcb9'
revision = 'd4867f3a4c0a'
down_revision = None
branch_labels = None
depends_on = None
Expand All @@ -30,11 +30,26 @@ def upgrade():
op.create_index(op.f('ix_user_email'), 'user', ['email'], unique=True)
op.create_index(op.f('ix_user_full_name'), 'user', ['full_name'], unique=False)
op.create_index(op.f('ix_user_id'), 'user', ['id'], unique=False)
op.create_table('item',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('title', sa.String(), nullable=True),
sa.Column('description', sa.String(), nullable=True),
sa.Column('owner_id', sa.Integer(), nullable=True),
sa.ForeignKeyConstraint(['owner_id'], ['user.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_item_description'), 'item', ['description'], unique=False)
op.create_index(op.f('ix_item_id'), 'item', ['id'], unique=False)
op.create_index(op.f('ix_item_title'), 'item', ['title'], unique=False)
# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_index(op.f('ix_item_title'), table_name='item')
op.drop_index(op.f('ix_item_id'), table_name='item')
op.drop_index(op.f('ix_item_description'), table_name='item')
op.drop_table('item')
op.drop_index(op.f('ix_user_id'), table_name='user')
op.drop_index(op.f('ix_user_full_name'), table_name='user')
op.drop_index(op.f('ix_user_email'), table_name='user')
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from fastapi import APIRouter

from app.api.api_v1.endpoints import token, user, utils
from app.api.api_v1.endpoints import items, login, users, utils

api_router = APIRouter()
api_router.include_router(token.router)
api_router.include_router(user.router)
api_router.include_router(utils.router)
api_router.include_router(login.router, tags=["login"])
api_router.include_router(users.router, prefix="/users", tags=["users"])
api_router.include_router(utils.router, prefix="/utils", tags=["utils"])
api_router.include_router(items.router, prefix="/items", tags=["items"])
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
from typing import List

from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session

from app import crud
from app.api.utils.db import get_db
from app.api.utils.security import get_current_active_user
from app.db_models.user import User as DBUser
from app.models.item import Item, ItemCreate, ItemUpdate

router = APIRouter()


@router.get("/", response_model=List[Item])
def read_items(
db: Session = Depends(get_db),
skip: int = 0,
limit: int = 100,
current_user: DBUser = Depends(get_current_active_user),
):
"""
Retrieve items.
"""
if crud.user.is_superuser(current_user):
items = crud.item.get_multi(db, skip=skip, limit=limit)
else:
items = crud.item.get_multi_by_owner(
db_session=db, owner_id=current_user.id, skip=skip, limit=limit
)
return items


@router.post("/", response_model=Item)
def create_item(
*,
db: Session = Depends(get_db),
item_in: ItemCreate,
current_user: DBUser = Depends(get_current_active_user),
):
"""
Create new item.
"""
item = crud.item.create(db_session=db, item_in=item_in, owner_id=current_user.id)
return item


@router.put("/{id}", response_model=Item)
def update_item(
*,
db: Session = Depends(get_db),
id: int,
item_in: ItemUpdate,
current_user: DBUser = Depends(get_current_active_user),
):
"""
Update an item.
"""
item = crud.item.get(db_session=db, id=id)
if not item:
raise HTTPException(status_code=404, detail="Item not found")
if not crud.user.is_superuser(current_user) and (item.owner_id != current_user.id):
raise HTTPException(status_code=400, detail="Not enough permissions")
item = crud.item.update(db_session=db, item=item, item_in=item_in)
return item


@router.get("/{id}", response_model=Item)
def read_user_me(
*,
db: Session = Depends(get_db),
id: int,
current_user: DBUser = Depends(get_current_active_user),
):
"""
Get item by ID.
"""
item = crud.item.get(db_session=db, id=id)
if not item:
raise HTTPException(status_code=400, detail="Item not found")
if not crud.user.is_superuser(current_user) and (item.owner_id != current_user.id):
raise HTTPException(status_code=400, detail="Not enough permissions")
return item


@router.delete("/{id}", response_model=Item)
def delete_item(
*,
db: Session = Depends(get_db),
id: int,
current_user: DBUser = Depends(get_current_active_user),
):
"""
Delete an item.
"""
item = crud.item.get(db_session=db, id=id)
if not item:
raise HTTPException(status_code=404, detail="Item not found")
if not crud.user.is_superuser(current_user) and (item.owner_id != current_user.id):
raise HTTPException(status_code=400, detail="Not enough permissions")
item = crud.item.remove(db_session=db, id=id)
return item
Original file line number Diff line number Diff line change
Expand Up @@ -10,35 +10,35 @@
from app.api.utils.security import get_current_active_superuser, get_current_active_user
from app.core import config
from app.db_models.user import User as DBUser
from app.models.user import User, UserInCreate, UserInDB, UserInUpdate
from app.models.user import User, UserCreate, UserInDB, UserUpdate
from app.utils import send_new_account_email

router = APIRouter()


@router.get("/users/", tags=["users"], response_model=List[User])
@router.get("/", response_model=List[User])
def read_users(
db: Session = Depends(get_db),
skip: int = 0,
limit: int = 100,
current_user: DBUser = Depends(get_current_active_superuser),
):
"""
Retrieve users
Retrieve users.
"""
users = crud.user.get_multi(db, skip=skip, limit=limit)
return users


@router.post("/users/", tags=["users"], response_model=User)
@router.post("/", response_model=User)
def create_user(
*,
db: Session = Depends(get_db),
user_in: UserInCreate,
user_in: UserCreate,
current_user: DBUser = Depends(get_current_active_superuser),
):
"""
Create new user
Create new user.
"""
user = crud.user.get_by_email(db, email=user_in.email)
if user:
Expand All @@ -54,7 +54,7 @@ def create_user(
return user


@router.put("/users/me", tags=["users"], response_model=User)
@router.put("/me", response_model=User)
def update_user_me(
*,
db: Session = Depends(get_db),
Expand All @@ -64,10 +64,10 @@ def update_user_me(
current_user: DBUser = Depends(get_current_active_user),
):
"""
Update own user
Update own user.
"""
current_user_data = jsonable_encoder(current_user)
user_in = UserInUpdate(**current_user_data)
user_in = UserUpdate(**current_user_data)
if password is not None:
user_in.password = password
if full_name is not None:
Expand All @@ -78,18 +78,18 @@ def update_user_me(
return user


@router.get("/users/me", tags=["users"], response_model=User)
@router.get("/me", response_model=User)
def read_user_me(
db: Session = Depends(get_db),
current_user: DBUser = Depends(get_current_active_user),
):
"""
Get current user
Get current user.
"""
return current_user


@router.post("/users/open", tags=["users"], response_model=User)
@router.post("/open", response_model=User)
def create_user_open(
*,
db: Session = Depends(get_db),
Expand All @@ -98,7 +98,7 @@ def create_user_open(
full_name: str = Body(None),
):
"""
Create new user without the need to be logged in
Create new user without the need to be logged in.
"""
if not config.USERS_OPEN_REGISTRATION:
raise HTTPException(
Expand All @@ -111,19 +111,19 @@ def create_user_open(
status_code=400,
detail="The user with this username already exists in the system",
)
user_in = UserInCreate(password=password, email=email, full_name=full_name)
user_in = UserCreate(password=password, email=email, full_name=full_name)
user = crud.user.create(db, user_in=user_in)
return user


@router.get("/users/{user_id}", tags=["users"], response_model=User)
@router.get("/{user_id}", response_model=User)
def read_user_by_id(
user_id: int,
current_user: DBUser = Depends(get_current_active_user),
db: Session = Depends(get_db),
):
"""
Get a specific user by id
Get a specific user by id.
"""
user = crud.user.get(db, user_id=user_id)
if user == current_user:
Expand All @@ -135,19 +135,18 @@ def read_user_by_id(
return user


@router.put("/users/{user_id}", tags=["users"], response_model=User)
@router.put("/{user_id}", response_model=User)
def update_user(
*,
db: Session = Depends(get_db),
user_id: int,
user_in: UserInUpdate,
user_in: UserUpdate,
current_user: UserInDB = Depends(get_current_active_superuser),
):
"""
Update a user
Update a user.
"""
user = crud.user.get(db, user_id=user_id)

if not user:
raise HTTPException(
status_code=404,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,23 @@
router = APIRouter()


@router.post("/test-celery/", tags=["utils"], response_model=Msg, status_code=201)
@router.post("/test-celery/", response_model=Msg, status_code=201)
def test_celery(
msg: Msg, current_user: UserInDB = Depends(get_current_active_superuser)
):
"""
Test Celery worker
Test Celery worker.
"""
celery_app.send_task("app.worker.test_celery", args=[msg.msg])
return {"msg": "Word received"}


@router.post("/test-email/", tags=["utils"], response_model=Msg, status_code=201)
@router.post("/test-email/", response_model=Msg, status_code=201)
def test_email(
email_to: EmailStr, current_user: UserInDB = Depends(get_current_active_superuser)
):
"""
Test emails
Test emails.
"""
send_test_email(email_to=email_to)
return {"msg": "Test email sent"}
Original file line number Diff line number Diff line change
@@ -1 +1 @@
from . import user
from . import item, user
Loading

0 comments on commit ecd634e

Please sign in to comment.