Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 58 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -271,8 +271,43 @@ tests/main_test.py ...... [100%
- test_update_group : update 테스트
- test_delete_group: delete 테스트


### 5) mongoDB 관련 API

### 4) docker compose 실행
#### (1) `/files` : CSV 파일 업로드 및 JSON insert

```bash
$ curl -F 'file=@../assets/data/test.csv' -X POST "http://localhost:58000/files/upload"
[{"name":"Alice","age":"20","height":"62","weight":"120.6","_id":"1"},{"name":"Freddie","age":"21","height":"74","weight":"190.6","_id":"2"},{"name":"Bob","age":"17","height":"68","weight":"120.0","_id":"3"}]%

$ curl -X GET "http://localhost:8000/files/test"
[{"_id":"1","name":"Alice","age":20,"height":62,"weight":120.6},{"_id":"2","name":"Freddie","age":21,"height":74,"weight":190.6},{"_id":"3","name":"Bob","age":17,"height":68,"weight":120.0}]%
```

#### (2) `/books` : Book 모델 insert

```bash
$ curl -X POST "http://localhost:8000/books/" -H "Content-Type: application/json" -d '''{ "_id": "066de609-b04a-4b30-b46c-32537c7f1f6e",
"title": "Don Quixote",
"author": "Miguel de Cervantes",
"synopsis": "..."
}
'''
{"_id":"066de609-b04a-4b30-b46c-32537c7f1f6e","title":"Don Quixote","author":"Miguel de Cervantes","synopsis":"..."}%

$ curl -X PUT "http://localhost:8000/books/066de609-b04a-4b30-b46c-32537c7f1f6e" -H "Content-Type: application/json" -d '''{
"title": "Don Quixote",
"author": "Miguel de Cervantes",
"synopsis": "Don Quixote is a Spanish novel by Miguel de Cervantes..."
}
'''
{"_id":"066de609-b04a-4b30-b46c-32537c7f1f6e","title":"Don Quixote","author":"Miguel de Cervantes","synopsis":"Don Quixote is a Spanish novel by Miguel de Cervantes..."}%

$ curl -X GET "http://localhost:8000/books/"
[{"_id":"066de609-b04a-4b30-b46c-32537c7f1f6e","title":"Don Quixote","author":"Miguel de Cervantes","synopsis":"Don Quixote is a Spanish novel by Miguel de Cervantes..."}]%
```

## 4. docker compose 실행

```bash
# 도커 컴포즈에서 linux/amd64 이미지 생성 (Mac M1)
Expand Down Expand Up @@ -301,7 +336,7 @@ $ docker compose down -v
⠿ Network sqlmodel-pg-api_default Rem... 0.1s
```

> 참고
> 참고: git

```
# 신규 리포지토리에 연결시
Expand All @@ -316,4 +351,25 @@ git push -u origin main
git remote add origin https://github.com/maxmin93/fastapi-sqlmodel-heroes.git
git branch -M main
git push -u origin main

# 새로운 브랜치 생성/변경
git checkout -b db-mongo

# 새로운 브랜치로 push (최초)
git push --set-upstream origin db-mongo

# main 브랜치로 변경
git checkout main

# 변경사항 로그 조회
git log --graph --decorate --oneline

# 변경사항 파일 조회
git status -u

# 파일 변경사항 (이전 캐시와 비교)
git diff --cached ../README.md

# 파일 변경사항 취소
git restore ../README.md
```
2 changes: 2 additions & 0 deletions api.env
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
# for api
CONN_URL="postgresql://tonyne:tonyne@db:5432/company_db"
MONGO_URI="mongodb://tonyne:tonyne@mongo:27017/"
MONGO_DB="tutorial"
4 changes: 4 additions & 0 deletions assets/data/test.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
id,name,age,height,weight
1,Alice,20,62,120.6
2,Freddie,21,74,190.6
3,Bob,17,68,120.0
21 changes: 20 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,27 @@ services:
db:
image: postgres:14
container_name: smp-db
restart: always
env_file:
- db.env
ports:
- "5432:5432/tcp"
volumes:
- smpdb_data:/var/lib/postgresql/data

mongo:
image: mongo:6
container_name: smp-mongo
restart: always
ports:
- 27017:27017/tcp
environment:
MONGO_INITDB_ROOT_USERNAME: tonyne
MONGO_INITDB_ROOT_PASSWORD: tonyne
volumes:
- smpmg_data:/data/db
- smpmg_cfg:/data/configdb

api:
image: py39-slim
container_name: smp-api
Expand All @@ -20,16 +34,21 @@ services:
- api.env
depends_on:
- db
- mongo
links:
- db
- mongo
ports:
- 58000:8000
volumes:
- smpapi_data:/app


volumes:
smpdb_data:
name: smpdb_data
smpmg_data:
name: smpmg_data
smpmg_cfg:
name: smpmg_cfg
smpapi_data:
name: smpapi_data
10 changes: 6 additions & 4 deletions smp-api/app/api/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from .heroes import router as hero_router
from .teams import router as team_router
from .tutorials import router as tutorial_router
from api.books import router as book_router
from api.files import router as file_router
from api.heroes import router as hero_router
from api.teams import router as team_router
from api.tutorials import router as tutorial_router

__all__ = [hero_router, team_router, tutorial_router]
__all__ = [hero_router, team_router, tutorial_router, file_router, book_router]
98 changes: 98 additions & 0 deletions smp-api/app/api/books.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
from typing import List

from core import get_mongodb
from fastapi import APIRouter, Body, Depends, HTTPException, Response, status
from fastapi.encoders import jsonable_encoder
from loguru import logger
from models import Book, BookUpdate

# https://pymongo.readthedocs.io/en/stable/tutorial.html
from pymongo.database import Database

router = APIRouter(
prefix="/books",
tags=["books"],
dependencies=[Depends(get_mongodb)],
responses={404: {"description": "API Not found"}},
)


@router.post(
"/",
response_description="Create a new book",
status_code=status.HTTP_201_CREATED,
response_model=Book,
)
def create_book(book: Book = Body(...), *, db: Database = Depends(get_mongodb)):
"""
curl -X POST "http://localhost:8000/books/" -H "Content-Type: application/json" -d '''{ "_id": "066de609-b04a-4b30-b46c-32537c7f1f6e",
"title": "Don Quixote",
"author": "Miguel de Cervantes",
"synopsis": "..."
}
'''
"""
book = jsonable_encoder(book)
new_book = db["books"].insert_one(book)
created_book = db["books"].find_one({"_id": new_book.inserted_id})
logger.info(f"created_book = {created_book}")
return created_book


@router.get("/", response_description="List all books", response_model=List[Book])
def list_books(*, db: Database = Depends(get_mongodb)):
books = list(db["books"].find(limit=100))
logger.info(f"books.size = {len(books)}")
return books


@router.get("/{id}", response_description="Get a single book by id", response_model=Book)
def find_book(id: str, *, db: Database = Depends(get_mongodb)):
if (book := db["books"].find_one({"_id": id})) is not None:
return book

raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail=f"Book with ID {id} not found"
)


@router.put("/{id}", response_description="Update a book", response_model=Book)
def update_book(id: str, *, db: Database = Depends(get_mongodb), book: BookUpdate = Body(...)):
"""
curl -X PUT "http://localhost:8000/books/066de609-b04a-4b30-b46c-32537c7f1f6e" -H "Content-Type: application/json" -d '''{
"title": "Don Quixote",
"author": "Miguel de Cervantes",
"synopsis": "Don Quixote is a Spanish novel by Miguel de Cervantes..."
}
'''
"""
book = {k: v for k, v in book.dict().items() if v is not None}

if len(book) >= 1:
update_result = db["books"].update_one({"_id": id}, {"$set": book})

if update_result.modified_count == 0:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail=f"Book with ID {id} not found"
)

# Walrus Operator (since Python 3.8)
if (existing_book := db["books"].find_one({"_id": id})) is not None:
return existing_book

raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail=f"Book with ID {id} not found"
)


@router.delete("/{id}", response_description="Delete a book")
def delete_book(id: str, *, db: Database = Depends(get_mongodb), response: Response):
delete_result = db["books"].delete_one({"_id": id})

if delete_result.deleted_count == 1:
response.status_code = status.HTTP_204_NO_CONTENT
return response

raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail=f"Book with ID {id} not found"
)
81 changes: 81 additions & 0 deletions smp-api/app/api/files.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import codecs
import csv
import os
from typing import Any, List

from core import get_mongodb
from fastapi import APIRouter, Depends, File, HTTPException, UploadFile

# from fastapi.encoders import jsonable_encoder
from loguru import logger
from models import Person

# https://pymongo.readthedocs.io/en/stable/tutorial.html
from pymongo.collection import Collection
from pymongo.database import Database

router = APIRouter(
prefix="/files",
tags=["files"],
dependencies=[Depends(get_mongodb)],
responses={404: {"description": "API Not found"}},
)


@router.get("/hello")
def reader_hello(*, db: Database = Depends(get_mongodb)):
db.drop_collection("hello")

coll: Collection = db["hello"]
# coll.delete_many({})
coll.insert_one({"id": 1001, "name": "tonyne", "score": 100.5})
result = coll.insert_many(
[
{"id": 1002, "name": "tonyne", "score": 100.5},
{"id": 1003, "name": "tonyne", "score": 100.5},
{"id": 1004, "name": "tonyne", "score": 100.5},
{"id": 1005, "name": "tonyne", "score": 100.5},
]
)
logger.info(f"_ids = {result.inserted_ids}")
cusor = coll.find({})
for doc in cusor:
logger.info(f"\n{doc}")
return {"msg": "Hello, Files", "collections": db.list_collection_names()}


@router.get("/{name}", response_model=List[Person])
def reader_collection(*, db=Depends(get_mongodb), name: str):
list_of_collections = db.list_collection_names()
if name not in list_of_collections:
raise HTTPException(status_code=404, detail=f"Collection['{name}'] not found")
logger.info(f"Collection['{name}'] found")
collection: Collection = db[name]
return [Person(**r) for r in collection.find({})]


def insert_data(collection: Collection, data: List[Any]):
try:
collection.drop()
result = collection.insert_many(data)
except Exception as e:
logger.error(e)
return result.inserted_ids


@router.post("/upload")
def reader_upload(file: UploadFile = File(...), *, db: Database = Depends(get_mongodb)):
"""
curl -F 'file=@../assets/data/test.csv' -X POST "http://localhost:8000/files/upload"
"""
csvReader = csv.DictReader(codecs.iterdecode(file.file, "utf-8"))
rows = []
for row in csvReader:
row["_id"] = str(row.pop("id"))
rows.append(row)
file.file.close()

collection_name = os.path.splitext(file.filename)[0]
inserted_ids = insert_data(db[collection_name], rows)
logger.info(f"coll['{collection_name}'].ids = {inserted_ids}")
return rows
7 changes: 6 additions & 1 deletion smp-api/app/api/heroes.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@
from models import Hero, HeroCreate, HeroRead, HeroReadWithTeam, HeroUpdate
from sqlmodel import Session, desc, select

router = APIRouter()
router = APIRouter(
prefix="/pgdb",
tags=["heroes"],
dependencies=[Depends(get_session)],
responses={404: {"description": "API Not found"}},
)


@router.get("/heroes/last", response_model=HeroRead)
Expand Down
7 changes: 6 additions & 1 deletion smp-api/app/api/teams.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@
from models import Team, TeamCreate, TeamRead, TeamReadWithHeroes, TeamUpdate
from sqlmodel import Session, desc, select

router = APIRouter()
router = APIRouter(
prefix="/pgdb",
tags=["teams"],
dependencies=[Depends(get_session)],
responses={404: {"description": "API Not found"}},
)


# select the last team
Expand Down
7 changes: 6 additions & 1 deletion smp-api/app/api/tutorials.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@
from models import Hero, Team
from sqlmodel import Session, select

router = APIRouter()
router = APIRouter(
prefix="/pgdb",
tags=["tutorials"],
dependencies=[Depends(get_session)],
responses={404: {"description": "API Not found"}},
)


@router.get("/tutorial/0")
Expand Down
5 changes: 3 additions & 2 deletions smp-api/app/core/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from core.app import app
from core.db import get_session
from core.mgdb import get_mongodb
from core.pgdb import get_session

__all__ = [app, get_session]
__all__ = [app, get_session, get_mongodb]
Loading