-
Notifications
You must be signed in to change notification settings - Fork 1
디비 커넥션 작업 #37
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
디비 커넥션 작업 #37
Changes from all commits
0d282b1
8409647
cc27d95
6900329
9fa2ec2
df07f0b
8bca2e6
fc8e819
db0ecd9
87d2b6c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,11 +1,14 @@ | ||
| # app/api/api_router.py | ||
|
|
||
| from fastapi import APIRouter | ||
|
|
||
| from app.api import driver_api, test_api | ||
| from app.api import driver_api, test_api, user_db_api | ||
|
|
||
| api_router = APIRouter() | ||
|
|
||
| # 테스트 라우터 | ||
| api_router.include_router(test_api.router, prefix="/test", tags=["Test"]) | ||
|
|
||
| # 라우터 | ||
| api_router.include_router(driver_api.router, prefix="/connections", tags=["Driver"]) | ||
| api_router.include_router(driver_api.router, prefix="/driver", tags=["Driver"]) | ||
| api_router.include_router(user_db_api.router, prefix="/user/db", tags=["UserDb"]) |
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 부분 뭐가 다른건가요?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 간단한 리팩토링이랑 포맷팅 적용 같습니다 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,24 +1,34 @@ | ||
| # app/api/driver_api.py | ||
|
|
||
| from fastapi import APIRouter | ||
| from fastapi import APIRouter, Depends | ||
|
|
||
| from app.core.enum.db_driver import DBTypesEnum | ||
| from app.core.exceptions import APIException | ||
| from app.core.response import ResponseMessage | ||
| from app.core.status import CommonCode | ||
| from app.schemas.driver_info import DriverInfo | ||
| from app.services.driver_service import db_driver_info | ||
| from app.schemas.driver.driver_info_model import DriverInfo | ||
| from app.services.driver_service import DriverService, driver_service | ||
|
|
||
| driver_service_dependency = Depends(lambda: driver_service) | ||
|
|
||
| router = APIRouter() | ||
|
|
||
|
|
||
| @router.get("/drivers/{driverId}", response_model=ResponseMessage[DriverInfo], summary="DB 드라이버 정보 조회 API") | ||
| def read_driver_info(driverId: str): | ||
| """DB 드라이버 정보 조회""" | ||
| @router.get( | ||
| "/info/{driver_id}", | ||
| response_model=ResponseMessage[DriverInfo], | ||
| summary="DB 드라이버 정보 조회", | ||
| ) | ||
| def read_driver_info( | ||
| driver_id: str, | ||
| service: DriverService = driver_service_dependency, | ||
| ) -> ResponseMessage[DriverInfo]: | ||
| """경로 파라미터로 받은 driver_id에 해당하는 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) from KeyError | ||
| db_type_enum = DBTypesEnum[driver_id.lower()] | ||
| except KeyError as e: | ||
| raise APIException(CommonCode.INVALID_DB_DRIVER, *e.args) from e | ||
| driver_info_data = DriverInfo.from_enum(db_type_enum) | ||
| return ResponseMessage.success( | ||
| value=service.read_driver_info(driver_info_data), code=CommonCode.SUCCESS_DRIVER_INFO | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| # app/api/health.py | ||
| # app/api/health_api.py | ||
| from fastapi import APIRouter | ||
|
|
||
| router = APIRouter(tags=["Health"]) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| # app/api/user_db_api.py | ||
|
|
||
| from fastapi import APIRouter, Depends | ||
|
|
||
| from app.core.exceptions import APIException | ||
| from app.core.response import ResponseMessage | ||
| from app.schemas.user_db.db_profile_model import DBProfileCreate | ||
| from app.services.user_db_service import UserDbService, user_db_service | ||
|
|
||
| user_db_service_dependency = Depends(lambda: user_db_service) | ||
|
|
||
| router = APIRouter() | ||
|
|
||
|
|
||
| @router.post( | ||
| "/connect/test", | ||
| response_model=ResponseMessage[bool], | ||
| summary="DB 연결 테스트", | ||
| ) | ||
| def connection_test( | ||
| db_info: DBProfileCreate, | ||
| service: UserDbService = user_db_service_dependency, | ||
| ) -> ResponseMessage[bool]: | ||
| """DB 연결 정보를 받아 연결 가능 여부를 테스트합니다.""" | ||
| db_info.validate_required_fields() | ||
|
|
||
| result = service.connection_test(db_info) | ||
| if not result.is_successful: | ||
| raise APIException(result.code) | ||
| return ResponseMessage.success(value=result.is_successful, code=result.code) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,45 +1,68 @@ | ||
| import traceback | ||
| from typing import Any | ||
|
|
||
| from fastapi import Request | ||
| from fastapi.exceptions import RequestValidationError | ||
| from fastapi.responses import JSONResponse | ||
|
|
||
| from app.core.status import CommonCode | ||
|
|
||
|
|
||
| def _create_error_response(code: CommonCode, data: Any | None = None) -> JSONResponse: | ||
| """ | ||
| 모든 에러 응답에 사용될 표준 JSONResponse 객체를 생성하는 헬퍼 함수. | ||
| """ | ||
| error_content = { | ||
| "code": code.code, | ||
| "message": code.message, | ||
| "data": data, | ||
| } | ||
| return JSONResponse( | ||
| status_code=code.http_status, | ||
| content=error_content, | ||
| ) | ||
|
|
||
|
|
||
| class APIException(Exception): | ||
| """ | ||
| API 로직 내에서 발생하는 모든 예상된 오류에 사용할 기본 예외 클래스입니다. | ||
| """ | ||
|
|
||
| def __init__(self, code: CommonCode, *args): | ||
| self.code_enum = code | ||
| self.message = code.get_message(*args) | ||
| self.message = code.message | ||
| self.args = args | ||
| super().__init__(self.message) | ||
|
|
||
|
|
||
| async def validation_exception_handler(request: Request, exc: RequestValidationError): | ||
| """ | ||
| Pydantic 모델의 유효성 검사 실패(RequestValidationError)를 감지하여 | ||
| 표준화된 JSON 오류 응답을 반환합니다. | ||
| """ | ||
| error_details = [] | ||
| for error in exc.errors(): | ||
| field_name = ".".join(map(str, error["loc"][1:])) | ||
| error_details.append({"field": field_name, "message": error["msg"]}) | ||
|
|
||
| return _create_error_response(code=CommonCode.INVALID_PARAMETER, data={"details": error_details}) | ||
|
|
||
|
|
||
| async def api_exception_handler(request: Request, exc: APIException): | ||
| """ | ||
| APIException이 발생했을 때, 이를 감지하여 표준화된 JSON 오류 응답을 반환합니다. | ||
| """ | ||
| return JSONResponse( | ||
| status_code=exc.code_enum.http_status, | ||
| content={"code": exc.code_enum.code, "message": exc.message, "data": None}, | ||
| ) | ||
| return _create_error_response(code=exc.code_enum, data=exc.args) | ||
|
|
||
|
|
||
| async def generic_exception_handler(request: Request, exc: Exception): | ||
| """ | ||
| 처리되지 않은 모든 예외를 잡아, 일관된 500 서버 오류를 반환합니다. | ||
| """ | ||
| # 운영 환경에서는 파일 로그나 모니터링 시스템으로 보내야 합니다. | ||
| error_traceback = traceback.format_exc() | ||
|
|
||
| print("=" * 20, "UNEXPECTED ERROR", "=" * 20) | ||
| traceback.print_exc() | ||
| print(error_traceback) | ||
| print("=" * 50) | ||
|
|
||
| # 사용자에게는 간단한 500 에러 메시지만 보여줍니다. | ||
| error_response = {"code": CommonCode.FAIL.code, "message": CommonCode.FAIL.message, "data": None} | ||
|
|
||
| return JSONResponse( | ||
| status_code=CommonCode.FAIL.http_status, | ||
| content=error_response, | ||
| ) | ||
| return _create_error_response(code=CommonCode.FAIL, data={"traceback": error_traceback}) |
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이렇게 보니 서비스 별로 백의 자리 숫자에 차이를 두는 것도 좋은 것 같습니다.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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,39 @@ | ||
| from typing import Any | ||
|
|
||
| import oracledb | ||
|
|
||
| from app.core.status import CommonCode | ||
| from app.schemas.user_db.connect_test_result_model import TestConnectionResult | ||
|
|
||
|
|
||
| class UserDbRepository: | ||
| def test_db_connection(self, driver_module: Any, **kwargs: Any) -> TestConnectionResult: | ||
| """ | ||
| DB 드라이버와 연결에 필요한 매개변수들을 받아 연결을 테스트합니다. | ||
| """ | ||
| connection = None | ||
| try: | ||
| if driver_module is oracledb: | ||
| if kwargs.get("user").lower() == "sys": | ||
| kwargs["mode"] = oracledb.AUTH_MODE_SYSDBA | ||
| connection = driver_module.connect(**kwargs) | ||
| # MSSQL과 같이 전체 연결 문자열이 제공된 경우 | ||
| elif "connection_string" in kwargs: | ||
| connection = driver_module.connect(kwargs["connection_string"]) | ||
| # SQLite와 같이 파일 이름만 필요한 경우 | ||
| elif "db_name" in kwargs: | ||
| connection = driver_module.connect(kwargs["db_name"]) | ||
| # 그 외 (MySQL, PostgreSQL, Oracle 등) 일반적인 키워드 인자 방식 연결 | ||
| else: | ||
| connection = driver_module.connect(**kwargs) | ||
|
|
||
| return TestConnectionResult(is_successful=True, code=CommonCode.SUCCESS_USER_DB_CONNECT_TEST) | ||
|
|
||
| except Exception: | ||
| return TestConnectionResult(is_successful=False, code=CommonCode.FAIL_CONNECT_DB) | ||
| finally: | ||
| if connection: | ||
| connection.close() | ||
|
|
||
|
|
||
| user_db_repository = UserDbRepository() |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| # app/schemas/user_db/connect_test_result_model.py | ||
|
|
||
| from pydantic import BaseModel, Field | ||
|
|
||
| from app.core.status import CommonCode | ||
|
|
||
|
|
||
| class TestConnectionResult(BaseModel): | ||
| is_successful: bool = Field(..., description="성공 여부") | ||
| code: CommonCode = Field(None, description="결과 코드") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
수고 하셨습니다.
그러나 스키마쪽 폴더 아래
/driver는 괜찮은데/user_db는 뭔가 사용자 디비같아서 저희 프로그램이 설치하는 db 같지가 않네요