Skip to content

Commit

Permalink
feat support odmantic 1.x
Browse files Browse the repository at this point in the history
  • Loading branch information
art049 committed Dec 12, 2023
1 parent 88f5acf commit da3757e
Show file tree
Hide file tree
Showing 9 changed files with 63 additions and 77 deletions.
45 changes: 18 additions & 27 deletions src/endpoints/article.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
from typing import Optional

from fastapi import APIRouter, Body, Depends
from odmantic import AIOEngine

from core.article import (
add_article_to_favorite,
Expand All @@ -21,27 +20,26 @@
SingleArticleResponse,
UpdateArticle,
)
from settings import EngineD
from settings import Engine
from utils.security import get_current_user_instance, get_current_user_optional_instance

router = APIRouter()


@router.get("/articles", response_model=MultipleArticlesResponse)
async def get_articles(
author: str = None,
favorited: str = None,
tag: str = None,
author: str | None = None,
favorited: str | None = None,
tag: str | None = None,
limit: int = 20,
offset: int = 0,
user_instance: Optional[UserModel] = Depends(get_current_user_optional_instance),
engine: AIOEngine = EngineD,
):
query = await build_get_articles_query(engine, author, favorited, tag)
query = await build_get_articles_query(Engine, author, favorited, tag)
if query is None:
return MultipleArticlesResponse(articles=[], articles_count=0)
response = await get_multiple_articles_response(
engine, user_instance, query, limit, offset
Engine, user_instance, query, limit, offset
)
return response

Expand All @@ -51,11 +49,10 @@ async def get_feed_articles(
limit: int = 20,
offset: int = 0,
user_instance: UserModel = Depends(get_current_user_instance),
engine: AIOEngine = EngineD,
):
query = build_get_feed_articles_query(user_instance)
response = await get_multiple_articles_response(
engine, user_instance, query, limit, offset
Engine, user_instance, query, limit, offset
)
return response

Expand All @@ -64,22 +61,20 @@ async def get_feed_articles(
async def create_article(
new_article: NewArticle = Body(..., embed=True, alias="article"),
user_instance: UserModel = Depends(get_current_user_instance),
engine: AIOEngine = EngineD,
):
print(new_article.tag_list)
article = ArticleModel(author=user_instance, **new_article.dict())
article = ArticleModel(author=user_instance, **new_article.model_dump())
article.tag_list.sort()
await engine.save(article)
await Engine.save(article)
return SingleArticleResponse.from_article_instance(article, user_instance)


@router.get("/articles/{slug}", response_model=SingleArticleResponse)
async def get_single_article(
slug: str,
engine: AIOEngine = EngineD,
user_instance: Optional[UserModel] = Depends(get_current_user_optional_instance),
):
article = await get_article_by_slug(engine, slug)
article = await get_article_by_slug(Engine, slug)
return SingleArticleResponse.from_article_instance(article, user_instance)


Expand All @@ -88,47 +83,43 @@ async def update_article(
slug: str,
update_data: UpdateArticle = Body(..., embed=True, alias="article"),
current_user: UserModel = Depends(get_current_user_instance),
engine: AIOEngine = EngineD,
):
article = await get_article_by_slug(engine, slug)
article = await get_article_by_slug(Engine, slug)
ensure_is_author(current_user, article)

patch_dict = update_data.dict(exclude_unset=True)
for name, value in patch_dict.items():
setattr(article, name, value)
article.updated_at = datetime.utcnow()
await engine.save(article)
await Engine.save(article)
return SingleArticleResponse.from_article_instance(article, current_user)


@router.delete("/articles/{slug}")
async def delete_article(
slug: str,
current_user: UserModel = Depends(get_current_user_instance),
engine: AIOEngine = EngineD,
):
article = await get_article_by_slug(engine, slug)
article = await get_article_by_slug(Engine, slug)
ensure_is_author(current_user, article)
await engine.delete(article)
await Engine.delete(article)


@router.post("/articles/{slug}/favorite", response_model=SingleArticleResponse)
async def favorite_article(
slug: str,
current_user: UserModel = Depends(get_current_user_instance),
engine: AIOEngine = EngineD,
):
article = await get_article_by_slug(engine, slug)
await add_article_to_favorite(engine, user=current_user, article=article)
article = await get_article_by_slug(Engine, slug)
await add_article_to_favorite(Engine, user=current_user, article=article)
return SingleArticleResponse.from_article_instance(article, current_user)


@router.delete("/articles/{slug}/favorite", response_model=SingleArticleResponse)
async def unfavorite_article(
slug: str,
current_user: UserModel = Depends(get_current_user_instance),
engine: AIOEngine = EngineD,
):
article = await get_article_by_slug(engine, slug)
await remove_article_from_favorites(engine, user=current_user, article=article)
article = await get_article_by_slug(Engine, slug)
await remove_article_from_favorites(Engine, user=current_user, article=article)
return SingleArticleResponse.from_article_instance(article, current_user)
20 changes: 9 additions & 11 deletions src/endpoints/comment.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from fastapi import APIRouter, Body, Depends
from odmantic import AIOEngine
from odmantic.bson import ObjectId

from core.article import get_article_by_slug
Expand All @@ -14,7 +13,7 @@
from models.user import UserModel
from schemas.comment import MultipleCommentsResponse, NewComment, SingleCommentResponse
from schemas.user import User
from settings import EngineD
from settings import Engine
from utils.security import get_current_user_instance

router = APIRouter()
Expand All @@ -23,9 +22,8 @@
@router.get("/articles/{slug}/comments", response_model=MultipleCommentsResponse)
async def get_article_comments(
slug: str,
engine: AIOEngine = EngineD,
):
data = await get_article_comments_and_authors_by_slug(engine, slug)
data = await get_article_comments_and_authors_by_slug(Engine, slug)
return MultipleCommentsResponse.from_comments_and_authors(data)


Expand All @@ -34,11 +32,12 @@ async def add_article_comment(
slug: str,
new_comment: NewComment = Body(..., embed=True, alias="comment"),
user_instance: UserModel = Depends(get_current_user_instance),
engine: AIOEngine = EngineD,
):
article = await get_article_by_slug(engine, slug)
comment_instance = CommentModel(authorId=user_instance.id, **new_comment.dict())
await add_new_comment(engine, article, comment_instance)
article = await get_article_by_slug(Engine, slug)
comment_instance = CommentModel(
authorId=user_instance.id, **new_comment.model_dump()
)
await add_new_comment(Engine, article, comment_instance)
return SingleCommentResponse(
comment={**comment_instance.dict(), "author": user_instance}
)
Expand All @@ -49,9 +48,8 @@ async def delete_article_comment(
slug: str,
id: ObjectId,
user_instance: User = Depends(get_current_user_instance),
engine: AIOEngine = EngineD,
):
article = await get_article_by_slug(engine, slug)
article = await get_article_by_slug(Engine, slug)
comment, index = get_comment_and_index_from_id(article, id)
ensure_is_comment_author(user_instance, comment)
await delete_comment_by_index(engine, article, index)
await delete_comment_by_index(Engine, article, index)
16 changes: 6 additions & 10 deletions src/endpoints/profile.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
from typing import Optional

from fastapi import APIRouter, Depends
from odmantic import AIOEngine

from core.user import get_user_by_username
from models.user import UserModel
from schemas.user import Profile, ProfileResponse
from settings import EngineD
from settings import Engine
from utils.security import get_current_user_instance, get_current_user_optional_instance

router = APIRouter()
Expand All @@ -16,9 +15,8 @@
async def get_profile(
username: str,
logged_user: Optional[UserModel] = Depends(get_current_user_optional_instance),
engine: AIOEngine = EngineD,
):
user = await get_user_by_username(engine, username)
user = await get_user_by_username(Engine, username)
following = False
if logged_user is not None and user.id in logged_user.following_ids:
following = True
Expand All @@ -28,26 +26,24 @@ async def get_profile(
@router.post("/profiles/{username}/follow", response_model=ProfileResponse)
async def follow_user(
username: str,
engine: AIOEngine = EngineD,
user_instance: UserModel = Depends(get_current_user_instance),
):
user_to_follow = await get_user_by_username(engine, username)
user_to_follow = await get_user_by_username(Engine, username)
following_set = set(user_instance.following_ids) | set((user_to_follow.id,))
user_instance.following_ids = tuple(following_set)
await engine.save(user_instance)
await Engine.save(user_instance)
profile = Profile(following=True, **user_to_follow.dict())
return ProfileResponse(profile=profile)


@router.delete("/profiles/{username}/follow", response_model=ProfileResponse)
async def unfollow_user(
username: str,
engine: AIOEngine = EngineD,
user_instance: UserModel = Depends(get_current_user_instance),
):
user_to_unfollow = await get_user_by_username(engine, username)
user_to_unfollow = await get_user_by_username(Engine, username)
following_set = set(user_instance.following_ids) - set((user_to_unfollow.id,))
user_instance.following_ids = tuple(following_set)
await engine.save(user_instance)
await Engine.save(user_instance)
profile = Profile(following=False, **user_to_unfollow.dict())
return ProfileResponse(profile=profile)
7 changes: 3 additions & 4 deletions src/endpoints/tag.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
from fastapi import APIRouter
from odmantic import AIOEngine

from core.tag import get_all_tags
from schemas.tag import TagsResponse
from settings import EngineD
from settings import Engine

router = APIRouter()


@router.get("/tags", response_model=TagsResponse)
async def get_tags(engine: AIOEngine = EngineD):
tags = await get_all_tags(engine)
async def get_tags():
tags = await get_all_tags(Engine)
return TagsResponse(tags=tags)
13 changes: 5 additions & 8 deletions src/endpoints/user.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
from fastapi import APIRouter, Body, Depends
from odmantic import AIOEngine

from core.exceptions import InvalidCredentialsException
from models.user import UserModel
from schemas.user import LoginUser, NewUser, UpdateUser, User, UserResponse
from settings import EngineD
from settings import Engine
from utils.security import (
OAUTH2_SCHEME,
authenticate_user,
Expand All @@ -19,23 +18,22 @@

@router.post("/users", response_model=UserResponse)
async def register_user(
user: NewUser = Body(..., embed=True), engine: AIOEngine = EngineD
user: NewUser = Body(..., embed=True),
):
instance = UserModel(
**user.dict(), hashed_password=get_password_hash(user.password)
)
await engine.save(instance)
await Engine.save(instance)
token = create_access_token(instance)
return UserResponse(user=User(token=token, **user.dict()))


@router.post("/users/login", response_model=UserResponse)
async def login_user(
user_input: LoginUser = Body(..., embed=True, alias="user"),
engine: AIOEngine = EngineD,
):
user = await authenticate_user(
engine, user_input.email, user_input.password.get_secret_value()
Engine, user_input.email, user_input.password.get_secret_value()
)
if user is None:
raise InvalidCredentialsException()
Expand All @@ -56,10 +54,9 @@ async def update_user(
update_user: UpdateUser = Body(..., embed=True, alias="user"),
user_instance: User = Depends(get_current_user_instance),
token: str = Depends(OAUTH2_SCHEME),
engine: AIOEngine = EngineD,
):
patch_dict = update_user.dict(exclude_unset=True)
for name, value in patch_dict.items():
setattr(user_instance, name, value)
await engine.save(user_instance)
await Engine.save(user_instance)
return UserResponse(user=User(token=token, **user_instance.dict()))
10 changes: 6 additions & 4 deletions src/schemas/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@


class BaseSchema(BaseModel):
class Config:
allow_population_by_field_name = True
json_encoders = {
model_config = {
"populate_by_name": True,
"json_encoders": {
datetime: lambda d: d.strftime("%Y-%m-%dT%H:%M:%S.%fZ"),
**BSON_TYPES_ENCODERS,
}
},
"from_attributes": True,
}
4 changes: 2 additions & 2 deletions src/schemas/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ class User(BaseSchema):
email: str
token: str
username: str
bio: Optional[str]
image: Optional[str]
bio: Optional[str] = None
image: Optional[str] = None


class UserResponse(BaseSchema):
Expand Down
15 changes: 9 additions & 6 deletions src/settings.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
from typing import Optional

from odmantic.fastapi import AIOEngineDependency
from pydantic import BaseSettings
from motor.motor_asyncio import AsyncIOMotorClient
from odmantic import AIOEngine
from pydantic import Field
from pydantic.types import SecretStr
from pydantic_settings import BaseSettings


class _Settings(BaseSettings):
SECRET_KEY: SecretStr = (
SECRET_KEY: SecretStr = Field(
"09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7"
)
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
ALGORITHM: str = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES: int = 30
MONGO_URI: Optional[str] = None


# Make this a singleton to avoid reloading it from the env everytime
SETTINGS = _Settings()

EngineD = AIOEngineDependency(mongo_uri=SETTINGS.MONGO_URI)
MotorClient = AsyncIOMotorClient(SETTINGS.MONGO_URI)
Engine = AIOEngine(MotorClient, database="test")
Loading

0 comments on commit da3757e

Please sign in to comment.