Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
4a0cf80
chore: 의존성 추가 (pydantic, sqlalchemy), dev 의존성 추가 (pytest, httpx, pyte…
apaals2 Jul 21, 2025
27a9b99
feat: Python import로 DB 드라이버 설치 여부 확인 기능 구현
apaals2 Jul 22, 2025
ad8d138
test: DB 드라이버 설치 상태 조회 API 및 단위 테스트 코드 추가
apaals2 Jul 22, 2025
aa32a8d
feat: Python 실행 환경 존재 여부 확인 함수 추가
apaals2 Jul 22, 2025
bede728
test: Python 환경 존재 여부 API 테스트 코드 추가
apaals2 Jul 22, 2025
57c1092
feat: macOS 및 Windows 환경에서 OS 레벨 드라이버 설치 여부 확인 기능 추가
apaals2 Jul 22, 2025
e8694a5
fix: 드라이버명이 단일 문자열일 때 발생하는 오류 수정 — 모든 드라이버명을 리스트로 통일
apaals2 Jul 22, 2025
59256f9
test: OS 레벨 드라이버 설치 확인 기능 테스트 코드 추가
apaals2 Jul 24, 2025
929b567
chore: DB 드라이버 의존성 추가 (PostgreSQL, MySQL, Oracle 등)
apaals2 Jul 24, 2025
8adb031
feat: 지원 DB 드라이버 사전 설치로 파이썬 환경, OS레벨 드라이버 체크 생략 및 드라이버 정보 간소화
apaals2 Jul 24, 2025
5dd1832
test: 드라이버 정보 확인에 대한 단위테스트, api 테스트 진행
apaals2 Jul 24, 2025
26b9c64
test: 코드 커버리지 측정
apaals2 Jul 24, 2025
a589add
feat: 동적 포트 할당 -> 고정 포트
apaals2 Jul 26, 2025
8283e13
feat: API에서 지원 드라이버 목록 검증 추가 (DriverEnum)
apaals2 Jul 26, 2025
65b9a52
feat: DriverEnum 및 DriverInfo 응답 모델 추가
apaals2 Jul 26, 2025
36fb86b
refactor: 드라이버 정보 응답을 dict → 모델 기반으로 전환
apaals2 Jul 26, 2025
0d1a86f
refactor: 파일명 변경(connectioins.py -> connect_driver.py)
apaals2 Jul 26, 2025
d4475db
refactor: 변경된 구조에 맞게 테스트 코드 리팩토링
apaals2 Jul 26, 2025
1450d66
docs: Health 라우터에 Swagger 태그 지정
apaals2 Jul 26, 2025
2170ad9
docs: README.md 수정 (동적 포트 할당-> 고정포트)
apaals2 Jul 26, 2025
c0e7651
docs: README.md 수정 (동적 포트 할당-> 고정포트)
apaals2 Jul 26, 2025
c63189a
refactor: connect_driver router -> api_router로 이동
apaals2 Aug 2, 2025
4c3b761
feat: 422 상태코드 추가
apaals2 Aug 2, 2025
e3fbb4b
refactor: 공통 응답으로 수정
apaals2 Aug 2, 2025
a5d622a
feat: 서비스 로직 정리를 위한 모델 팩토리 메서드 생성 및 Enum 변수-> 객체
apaals2 Aug 2, 2025
4b3927b
refactor: driver_enum.py /core 아래로 이동
apaals2 Aug 2, 2025
5fa9358
refactor: 422 상태코드 메시지 범위 좁게 변경
apaals2 Aug 2, 2025
2250308
refactor: 변수담아서 반환했던 걸 변수 제거하고 반환
apaals2 Aug 2, 2025
a988e61
feat: 드라이버 정보 처리를 객체 기반으로 변경
apaals2 Aug 3, 2025
21bf04f
refactor: DB 드라이버 Enum 값 단일화
apaals2 Aug 3, 2025
6120bc2
refactor: 라우터에서 from_enum 팩토리 메서드 사용하도록 변경
apaals2 Aug 3, 2025
aa7d8c1
feat: from_enum 메서드 추가
apaals2 Aug 3, 2025
3252541
refactor: 객체 새생성이 아닌 기존 객체 수정으로 변경 및 파일명 api, service 의미 동일하게 수정
apaals2 Aug 3, 2025
c1fc742
refactor: driver_info 에서 return 누락 부분 추가
apaals2 Aug 3, 2025
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
17 changes: 8 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,13 +88,13 @@

```bash
poetry shell
uvicorn app.main:app --reload
uvicorn app.main:app --host 0.0.0.0 --port 39722 --reload
```

또는 Poetry Run을 사용하여 직접 실행할 수 있습니다.

```bash
poetry run uvicorn main:app --reload
poetry run uvicorn main:app --host 0.0.0.0 --port 39722 --reload
```

### **코드 컨벤션 (PEP 8, Ruff, Black)**
Expand Down Expand Up @@ -141,24 +141,23 @@

1. **브라우저 확인**

- 기본 루트 엔드포인트: <http://localhost:8000/>
- 헬스 체크 엔드포인트: <http://127.0.0.1:8000/health>
- API 문서: <http://127.0.0.1:8000/docs>
- 기본 루트 엔드포인트: <http://localhost:39722/>
- 헬스 체크 엔드포인트: <http://127.0.0.1:39722/health>
- API 문서: <http://127.0.0.1:39722/docs>

2. **CLI로 접속 확인하기**

FastAPI 앱이 poetry run uvicorn main:app --reload 명령어로 실행 중인 상태에서, 새로운 터미널을 열고 다음 curl 명령어를 입력하여 각 엔드포인트의 응답을 확인할 수 있습니다.

- 기본 루트 엔드포인트:
```bash
curl http://localhost:8000/
curl http://localhost:39722/
```
- 헬스 체크 엔드포인트:
```bash
curl http://localhost:8000/health
curl http://localhost:39722/health
```
- API 문서:
```bash
curl http://localhost:8000/openapi.json
curl http://localhost:39722/openapi.json
```

5 changes: 3 additions & 2 deletions app/api/api_router.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
from fastapi import APIRouter

from app.api import test_api
from app.api import driver_api, test_api

api_router = APIRouter()

# 테스트 라우터
api_router.include_router(test_api.router, prefix="/test", tags=["Test"])

# 라우터
# api_router.include_router(connect_driver.router, prefix="/connections", tags=["Driver"])
api_router.include_router(driver_api.router, prefix="/connections", tags=["Driver"])
24 changes: 24 additions & 0 deletions app/api/driver_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# app/api/driver_api.py

from fastapi import APIRouter

from app.core.db_driver_enum import DBTypesEnum
from app.core.exceptions import APIException
from app.core.status import CommonCode
from app.schemas.driver_info import DriverInfo
from app.schemas.response import ResponseMessage
from app.services.driver_service import db_driver_info

router = APIRouter()


@router.get("/drivers/{driverId}", response_model=ResponseMessage[DriverInfo], summary="DB 드라이버 정보 조회 API")
def read_driver_info(driverId: str):
"""DB 드라이버 정보 조회"""
try:
# DBTypesEnum에서 driverID에 맞는 객체를 가져옵니다.
db_type_enum = DBTypesEnum[driverId.lower()]
return ResponseMessage.success(value=db_driver_info(DriverInfo.from_enum(db_type_enum)))
# db_type_enum 유효성 검사 실패
except KeyError:
raise APIException(CommonCode.INVALID_ENUM_VALUE)
2 changes: 1 addition & 1 deletion app/api/health.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# app/api/health.py
from fastapi import APIRouter

router = APIRouter()
router = APIRouter(tags=["Health"])


@router.get("/health")
Expand Down
13 changes: 13 additions & 0 deletions app/core/db_driver_enum.py

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Enum 이름(db_type) = 값(driver_name) 단일화 성공하였으나,
만약 이 상태에서 app/api/connect_driver.py def read_driver_info(driverId: str): 부분을 def read_driver_info(driverId: DBTypesEnum): 으로 사용할 경우 오류가 납니다. 왜냐면 enum은 이름과 값이 같아야 유효성 검사를 하기 때문이라고 합니다.

Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# app/core/db_driver_enum.py
from enum import Enum


class DBTypesEnum(Enum):
"""지원되는 데이터베이스 드라이버 타입"""

postgresql = "psycopg2"
mysql = "mysql.connector"
sqlite = "sqlite3"
oracle = "cx_Oracle"
sqlserver = "pyodbc"
mariadb = "pymysql"
23 changes: 0 additions & 23 deletions app/core/port.py

This file was deleted.

5 changes: 3 additions & 2 deletions app/core/status.py

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기에 왜 저렇게 줄바꿈이 여러개 생겼는지 모르겠네요. 참 이상허네.

Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from enum import Enum

from fastapi import status


class CommonCode(Enum):
"""
애플리케이션의 모든 상태 코드를 중앙에서 관리합니다.
Expand All @@ -21,13 +23,13 @@ class CommonCode(Enum):
NO_VALUE = (status.HTTP_400_BAD_REQUEST, "4000", "필수 값이 존재하지 않습니다.")
DUPLICATION = (status.HTTP_409_CONFLICT, "4001", "이미 존재하는 데이터입니다.")
NO_SEARCH_DATA = (status.HTTP_404_NOT_FOUND, "4002", "요청한 데이터를 찾을 수 없습니다.")
INVALID_ENUM_VALUE = (status.HTTP_422_UNPROCESSABLE_ENTITY, "4003", "지원하지 않는 데이터베이스 값입니다.")

# ==================================
# 서버 오류 (Server Error) - 5xx
# ==================================
FAIL = (status.HTTP_500_INTERNAL_SERVER_ERROR, "9999", "서버 처리 중 오류가 발생했습니다.")


def __init__(self, http_status: int, code: str, message: str):
"""Enum 멤버가 생성될 때 각 값을 속성으로 할당합니다."""
self.http_status = http_status
Expand All @@ -39,4 +41,3 @@ def get_message(self, *args) -> str:
메시지 포맷팅이 필요한 경우, 인자를 받아 완성된 메시지를 반환합니다.
"""
return self.message % args if args else self.message

17 changes: 2 additions & 15 deletions app/main.py

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. 동적 포트 할당은 지난 번에 얘기했을 때 할지 말지 고민했던 거 같은데 어떻게 되었나요??
  2. get으로 /를 받는 부분은 메시지를 전달하는데 어떤 역할을 하나요??

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. 동적 할당 부분은 해당 브랜치 종료 후 수정예정입니다. 생각해둔 포트 번호는 39722 입니다. 어떠신가요?
  2. 해당 질문은 @app.get("/") 이 부분에 해당하는 걸까요?
@app.get("/")
async def read_root():
    return {"message": "Hello, FastAPI Backend!"}

보시는 거와 같이 브라우저 첫 화면 역할입니다. 초기 개발 세팅 후 제거를 안한 흔적이죠.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. 네, 좋습니다
  2. 필요 없는 부분이라면 제거 부탁 드립니다.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1,2 수정 및 제거 하였습니다.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. app.include_router(api_router, prefix="/api")로 전체 엔드포인트에 api를 달아주고
  2. api_route.py를 만들어 모든 흐름제어를 한곳에서 하는 건 어떠신가요??
    예시 입니다!
# app/api_router.py
from fastapi import APIRouter
from app.routers import users, items # 가정: items 라우터도 있다고 가정

# 최상위 라우터 객체 생성
api_router = APIRouter()

# 기능별 라우터를 여기에 포함 (이때는 prefix를 사용하지 않음)
api_router.include_router(users.router, prefix="/users")
api_router.include_router(items.router, prefix="/items")

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1,2 확인했습니다. 해당 사항 공부하고 진행하겠습니다.

Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,8 @@
from fastapi import FastAPI

from app.api import health # 헬스 체크
from app.core.port import get_available_port # 동적 포트 할당
from app.api.api_router import api_router


from app.core.exceptions import (
APIException,
api_exception_handler,
generic_exception_handler
)
from app.core.exceptions import APIException, api_exception_handler, generic_exception_handler

app = FastAPI()

Expand All @@ -24,13 +17,7 @@
app.include_router(health.router)
app.include_router(api_router, prefix="/api")

@app.get("/")
async def read_root():
return {"message": "Hello, FastAPI Backend!"}


if __name__ == "__main__":
# 동적 할당 로직
port = get_available_port()
# Uvicorn 서버를 시작합니다.
uvicorn.run(app, host="0.0.0.0", port=port)
uvicorn.run(app, host="0.0.0.0", port=39722)
38 changes: 38 additions & 0 deletions app/schemas/driver_info.py

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Enum은 효율적인 관리와 재사용성을 위해 따로 파일로 빼서 관리하는 건 어떠신가요?
  2. Enum 반환을 obj가 아닌 객체로 관리하는 건 어떠신가요??
  3. Response 또한 모든 객체에서 동일한 형식 유지를 위해 따로 빼서 관리하는 건 어떻게 생각하시나요?
    이후 api에서 반환할 때 해당 객체를 사용하는 방향은 어떠신가요??
    예시)
# app/schemas/response.py
from typing import Generic, TypeVar, Optional, Any
from pydantic import BaseModel

# T라는 이름의 제네릭 타입 변수 생성. 어떤 타입이든 될 수 있음을 의미.
T = TypeVar('T')

class ResponseMessage(BaseModel, Generic[T]):
    """공용 응답 모델"""
    message: str
    data: Optional[T] = None

    @classmethod
    def success(cls, value: T, msg: str = "성공적으로 불러왔습니다."):
        return cls(message=msg, data=value)

    @classmethod
    def error(cls, e: Exception | None = None, msg: str = "처리 중 오류가 발생했습니다."):
        if e:
            # 실제 운영 환경에서는 logging 라이브러리 사용을 권장합니다.
            print(f"[ERROR] {type(e).__name__}: {e}")
        return cls(message=msg, data=None)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

해당 사항 공부 좀 하고 다시 답변드리겠습니다.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

말씀 주신 그대로 수정하였습니다.

Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# app/schemas/driver_info.py
from pydantic import BaseModel

from app.core.db_driver_enum import DBTypesEnum


class DriverInfo(BaseModel):
db_type: str
is_installed: bool
driver_name: str | None
driver_version: str | None
driver_size_bytes: int | None

def update_from_module(self, version: str | None, size: int | None):
"""
객체 자신의 속성을 직접 업데이트하여 설치된 드라이버 정보를 채웁니다.
"""
self.is_installed = True
self.driver_version = version
self.driver_size_bytes = size

return self

@classmethod
def from_enum(cls, db_type_enum: DBTypesEnum):
"""
DBTypesEnum 객체를 인자로 받아, db_type, driver_name만으로 driverInfo 객체를 생성합니다.
`is_installed`는 False로 설정됩니다.
"""
db_type = db_type_enum.name
driver_name = db_type_enum.value
return cls(
db_type=db_type,
is_installed=False,
driver_name=driver_name,
driver_version=None,
driver_size_bytes=None,
)
20 changes: 20 additions & 0 deletions app/services/driver_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# app/service/driver_service.py
import importlib
import os

from app.core.exceptions import APIException
from app.core.status import CommonCode
from app.schemas.driver_info import DriverInfo


def db_driver_info(driver_info: DriverInfo):
try:
mod = importlib.import_module(driver_info.driver_name)
version = getattr(mod, "__version__", None)
path = getattr(mod.__spec__, "origin", None)
size = os.path.getsize(path) if path else None

return driver_info.update_from_module(version, size)

except (ModuleNotFoundError, AttributeError, OSError):
raise APIException(CommonCode.FAIL)
Loading