Skip to content

Commit b471997

Browse files
authored
Merge pull request #2 from maxmin93/db-mongo
mongoDB 및 API 추가
2 parents cceade1 + a177f9f commit b471997

File tree

20 files changed

+677
-29
lines changed

20 files changed

+677
-29
lines changed

README.md

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -271,8 +271,43 @@ tests/main_test.py ...... [100%
271271
- test_update_group : update 테스트
272272
- test_delete_group: delete 테스트
273273

274+
275+
### 5) mongoDB 관련 API
274276

275-
### 4) docker compose 실행
277+
#### (1) `/files` : CSV 파일 업로드 및 JSON insert
278+
279+
```bash
280+
$ curl -F 'file=@../assets/data/test.csv' -X POST "http://localhost:58000/files/upload"
281+
[{"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"}]%
282+
283+
$ curl -X GET "http://localhost:8000/files/test"
284+
[{"_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}]%
285+
```
286+
287+
#### (2) `/books` : Book 모델 insert
288+
289+
```bash
290+
$ curl -X POST "http://localhost:8000/books/" -H "Content-Type: application/json" -d '''{ "_id": "066de609-b04a-4b30-b46c-32537c7f1f6e",
291+
"title": "Don Quixote",
292+
"author": "Miguel de Cervantes",
293+
"synopsis": "..."
294+
}
295+
'''
296+
{"_id":"066de609-b04a-4b30-b46c-32537c7f1f6e","title":"Don Quixote","author":"Miguel de Cervantes","synopsis":"..."}%
297+
298+
$ curl -X PUT "http://localhost:8000/books/066de609-b04a-4b30-b46c-32537c7f1f6e" -H "Content-Type: application/json" -d '''{
299+
"title": "Don Quixote",
300+
"author": "Miguel de Cervantes",
301+
"synopsis": "Don Quixote is a Spanish novel by Miguel de Cervantes..."
302+
}
303+
'''
304+
{"_id":"066de609-b04a-4b30-b46c-32537c7f1f6e","title":"Don Quixote","author":"Miguel de Cervantes","synopsis":"Don Quixote is a Spanish novel by Miguel de Cervantes..."}%
305+
306+
$ curl -X GET "http://localhost:8000/books/"
307+
[{"_id":"066de609-b04a-4b30-b46c-32537c7f1f6e","title":"Don Quixote","author":"Miguel de Cervantes","synopsis":"Don Quixote is a Spanish novel by Miguel de Cervantes..."}]%
308+
```
309+
310+
## 4. docker compose 실행
276311

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

304-
> 참고
339+
> 참고: git
305340
306341
```
307342
# 신규 리포지토리에 연결시
@@ -316,4 +351,25 @@ git push -u origin main
316351
git remote add origin https://github.com/maxmin93/fastapi-sqlmodel-heroes.git
317352
git branch -M main
318353
git push -u origin main
354+
355+
# 새로운 브랜치 생성/변경
356+
git checkout -b db-mongo
357+
358+
# 새로운 브랜치로 push (최초)
359+
git push --set-upstream origin db-mongo
360+
361+
# main 브랜치로 변경
362+
git checkout main
363+
364+
# 변경사항 로그 조회
365+
git log --graph --decorate --oneline
366+
367+
# 변경사항 파일 조회
368+
git status -u
369+
370+
# 파일 변경사항 (이전 캐시와 비교)
371+
git diff --cached ../README.md
372+
373+
# 파일 변경사항 취소
374+
git restore ../README.md
319375
```

api.env

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
# for api
22
CONN_URL="postgresql://tonyne:tonyne@db:5432/company_db"
3+
MONGO_URI="mongodb://tonyne:tonyne@mongo:27017/"
4+
MONGO_DB="tutorial"

assets/data/test.csv

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
id,name,age,height,weight
2+
1,Alice,20,62,120.6
3+
2,Freddie,21,74,190.6
4+
3,Bob,17,68,120.0

docker-compose.yml

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,27 @@ services:
44
db:
55
image: postgres:14
66
container_name: smp-db
7+
restart: always
78
env_file:
89
- db.env
910
ports:
1011
- "5432:5432/tcp"
1112
volumes:
1213
- smpdb_data:/var/lib/postgresql/data
1314

15+
mongo:
16+
image: mongo:6
17+
container_name: smp-mongo
18+
restart: always
19+
ports:
20+
- 27017:27017/tcp
21+
environment:
22+
MONGO_INITDB_ROOT_USERNAME: tonyne
23+
MONGO_INITDB_ROOT_PASSWORD: tonyne
24+
volumes:
25+
- smpmg_data:/data/db
26+
- smpmg_cfg:/data/configdb
27+
1428
api:
1529
image: py39-slim
1630
container_name: smp-api
@@ -20,16 +34,21 @@ services:
2034
- api.env
2135
depends_on:
2236
- db
37+
- mongo
2338
links:
2439
- db
40+
- mongo
2541
ports:
2642
- 58000:8000
2743
volumes:
2844
- smpapi_data:/app
2945

30-
3146
volumes:
3247
smpdb_data:
3348
name: smpdb_data
49+
smpmg_data:
50+
name: smpmg_data
51+
smpmg_cfg:
52+
name: smpmg_cfg
3453
smpapi_data:
3554
name: smpapi_data

smp-api/app/api/__init__.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
from .heroes import router as hero_router
2-
from .teams import router as team_router
3-
from .tutorials import router as tutorial_router
1+
from api.books import router as book_router
2+
from api.files import router as file_router
3+
from api.heroes import router as hero_router
4+
from api.teams import router as team_router
5+
from api.tutorials import router as tutorial_router
46

5-
__all__ = [hero_router, team_router, tutorial_router]
7+
__all__ = [hero_router, team_router, tutorial_router, file_router, book_router]

smp-api/app/api/books.py

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
from typing import List
2+
3+
from core import get_mongodb
4+
from fastapi import APIRouter, Body, Depends, HTTPException, Response, status
5+
from fastapi.encoders import jsonable_encoder
6+
from loguru import logger
7+
from models import Book, BookUpdate
8+
9+
# https://pymongo.readthedocs.io/en/stable/tutorial.html
10+
from pymongo.database import Database
11+
12+
router = APIRouter(
13+
prefix="/books",
14+
tags=["books"],
15+
dependencies=[Depends(get_mongodb)],
16+
responses={404: {"description": "API Not found"}},
17+
)
18+
19+
20+
@router.post(
21+
"/",
22+
response_description="Create a new book",
23+
status_code=status.HTTP_201_CREATED,
24+
response_model=Book,
25+
)
26+
def create_book(book: Book = Body(...), *, db: Database = Depends(get_mongodb)):
27+
"""
28+
curl -X POST "http://localhost:8000/books/" -H "Content-Type: application/json" -d '''{ "_id": "066de609-b04a-4b30-b46c-32537c7f1f6e",
29+
"title": "Don Quixote",
30+
"author": "Miguel de Cervantes",
31+
"synopsis": "..."
32+
}
33+
'''
34+
"""
35+
book = jsonable_encoder(book)
36+
new_book = db["books"].insert_one(book)
37+
created_book = db["books"].find_one({"_id": new_book.inserted_id})
38+
logger.info(f"created_book = {created_book}")
39+
return created_book
40+
41+
42+
@router.get("/", response_description="List all books", response_model=List[Book])
43+
def list_books(*, db: Database = Depends(get_mongodb)):
44+
books = list(db["books"].find(limit=100))
45+
logger.info(f"books.size = {len(books)}")
46+
return books
47+
48+
49+
@router.get("/{id}", response_description="Get a single book by id", response_model=Book)
50+
def find_book(id: str, *, db: Database = Depends(get_mongodb)):
51+
if (book := db["books"].find_one({"_id": id})) is not None:
52+
return book
53+
54+
raise HTTPException(
55+
status_code=status.HTTP_404_NOT_FOUND, detail=f"Book with ID {id} not found"
56+
)
57+
58+
59+
@router.put("/{id}", response_description="Update a book", response_model=Book)
60+
def update_book(id: str, *, db: Database = Depends(get_mongodb), book: BookUpdate = Body(...)):
61+
"""
62+
curl -X PUT "http://localhost:8000/books/066de609-b04a-4b30-b46c-32537c7f1f6e" -H "Content-Type: application/json" -d '''{
63+
"title": "Don Quixote",
64+
"author": "Miguel de Cervantes",
65+
"synopsis": "Don Quixote is a Spanish novel by Miguel de Cervantes..."
66+
}
67+
'''
68+
"""
69+
book = {k: v for k, v in book.dict().items() if v is not None}
70+
71+
if len(book) >= 1:
72+
update_result = db["books"].update_one({"_id": id}, {"$set": book})
73+
74+
if update_result.modified_count == 0:
75+
raise HTTPException(
76+
status_code=status.HTTP_404_NOT_FOUND, detail=f"Book with ID {id} not found"
77+
)
78+
79+
# Walrus Operator (since Python 3.8)
80+
if (existing_book := db["books"].find_one({"_id": id})) is not None:
81+
return existing_book
82+
83+
raise HTTPException(
84+
status_code=status.HTTP_404_NOT_FOUND, detail=f"Book with ID {id} not found"
85+
)
86+
87+
88+
@router.delete("/{id}", response_description="Delete a book")
89+
def delete_book(id: str, *, db: Database = Depends(get_mongodb), response: Response):
90+
delete_result = db["books"].delete_one({"_id": id})
91+
92+
if delete_result.deleted_count == 1:
93+
response.status_code = status.HTTP_204_NO_CONTENT
94+
return response
95+
96+
raise HTTPException(
97+
status_code=status.HTTP_404_NOT_FOUND, detail=f"Book with ID {id} not found"
98+
)

smp-api/app/api/files.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import codecs
2+
import csv
3+
import os
4+
from typing import Any, List
5+
6+
from core import get_mongodb
7+
from fastapi import APIRouter, Depends, File, HTTPException, UploadFile
8+
9+
# from fastapi.encoders import jsonable_encoder
10+
from loguru import logger
11+
from models import Person
12+
13+
# https://pymongo.readthedocs.io/en/stable/tutorial.html
14+
from pymongo.collection import Collection
15+
from pymongo.database import Database
16+
17+
router = APIRouter(
18+
prefix="/files",
19+
tags=["files"],
20+
dependencies=[Depends(get_mongodb)],
21+
responses={404: {"description": "API Not found"}},
22+
)
23+
24+
25+
@router.get("/hello")
26+
def reader_hello(*, db: Database = Depends(get_mongodb)):
27+
db.drop_collection("hello")
28+
29+
coll: Collection = db["hello"]
30+
# coll.delete_many({})
31+
coll.insert_one({"id": 1001, "name": "tonyne", "score": 100.5})
32+
result = coll.insert_many(
33+
[
34+
{"id": 1002, "name": "tonyne", "score": 100.5},
35+
{"id": 1003, "name": "tonyne", "score": 100.5},
36+
{"id": 1004, "name": "tonyne", "score": 100.5},
37+
{"id": 1005, "name": "tonyne", "score": 100.5},
38+
]
39+
)
40+
logger.info(f"_ids = {result.inserted_ids}")
41+
cusor = coll.find({})
42+
for doc in cusor:
43+
logger.info(f"\n{doc}")
44+
return {"msg": "Hello, Files", "collections": db.list_collection_names()}
45+
46+
47+
@router.get("/{name}", response_model=List[Person])
48+
def reader_collection(*, db=Depends(get_mongodb), name: str):
49+
list_of_collections = db.list_collection_names()
50+
if name not in list_of_collections:
51+
raise HTTPException(status_code=404, detail=f"Collection['{name}'] not found")
52+
logger.info(f"Collection['{name}'] found")
53+
collection: Collection = db[name]
54+
return [Person(**r) for r in collection.find({})]
55+
56+
57+
def insert_data(collection: Collection, data: List[Any]):
58+
try:
59+
collection.drop()
60+
result = collection.insert_many(data)
61+
except Exception as e:
62+
logger.error(e)
63+
return result.inserted_ids
64+
65+
66+
@router.post("/upload")
67+
def reader_upload(file: UploadFile = File(...), *, db: Database = Depends(get_mongodb)):
68+
"""
69+
curl -F 'file=@../assets/data/test.csv' -X POST "http://localhost:8000/files/upload"
70+
"""
71+
csvReader = csv.DictReader(codecs.iterdecode(file.file, "utf-8"))
72+
rows = []
73+
for row in csvReader:
74+
row["_id"] = str(row.pop("id"))
75+
rows.append(row)
76+
file.file.close()
77+
78+
collection_name = os.path.splitext(file.filename)[0]
79+
inserted_ids = insert_data(db[collection_name], rows)
80+
logger.info(f"coll['{collection_name}'].ids = {inserted_ids}")
81+
return rows

smp-api/app/api/heroes.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,12 @@
55
from models import Hero, HeroCreate, HeroRead, HeroReadWithTeam, HeroUpdate
66
from sqlmodel import Session, desc, select
77

8-
router = APIRouter()
8+
router = APIRouter(
9+
prefix="/pgdb",
10+
tags=["heroes"],
11+
dependencies=[Depends(get_session)],
12+
responses={404: {"description": "API Not found"}},
13+
)
914

1015

1116
@router.get("/heroes/last", response_model=HeroRead)

smp-api/app/api/teams.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,12 @@
55
from models import Team, TeamCreate, TeamRead, TeamReadWithHeroes, TeamUpdate
66
from sqlmodel import Session, desc, select
77

8-
router = APIRouter()
8+
router = APIRouter(
9+
prefix="/pgdb",
10+
tags=["teams"],
11+
dependencies=[Depends(get_session)],
12+
responses={404: {"description": "API Not found"}},
13+
)
914

1015

1116
# select the last team

smp-api/app/api/tutorials.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@
44
from models import Hero, Team
55
from sqlmodel import Session, select
66

7-
router = APIRouter()
7+
router = APIRouter(
8+
prefix="/pgdb",
9+
tags=["tutorials"],
10+
dependencies=[Depends(get_session)],
11+
responses={404: {"description": "API Not found"}},
12+
)
813

914

1015
@router.get("/tutorial/0")

0 commit comments

Comments
 (0)