에러 핸들링

Pt J·2026년 3월 21일
post-thumbnail

에러 핸들링

이 실습은 이전 실습에서 이어서 진행한다.
따라서 작업공간 생성 및 구조 확인은 생략한다.

코드 작성

Rust 코드

Rust에서 발생한 에러를 Python 에러로 내보내기 위한 모듈
pyo3::exceptions::PyValueError 을 불러온다.

기존 예제에서는 평균값을 구할 때 리스트가 비어 있을 경우 0으로 나누게 되는 문제를
조건문을 사용하여 count > 0 인 경우에만 sum / count 연산을 수행했는데
빈 리스트가 전달되었을 때 에러를 내도록 코드를 수정하겠다.

이 때, panic! 을 사용하지 않고 unwrap() 또는 ? 연산자를 사용하여
Rust 프로세스를 죽이지 말고 PyResult 로 오류를 반환하도록 해야 한다.
그렇지 않으면 Rust 프로세스가 죽었을 때 Python 프로세스도 같이 죽어 버린다.

발생할 수 있는 모든 오류를 Rust에서 처리하려고 할 필요는 없다.
로직에서 발생하는 오류는 Rust에서 처리하되,
데이터 형식이 맞지 않는 등의 오류는 Python에서
데이터 검증 라이브러리 Pydantic 을 통해 확인하는 편이 코드가 간결해진다.
FastAPI의 자동 문서화 Swagger UI도 이렇게 작성했을 때 더 정확해진다.

src/lib.rs

use pyo3::prelude::*;
use pyo3::exceptions::PyValueError; // NEW!
use std::collections::HashMap;

#[pyfunction]
fn process_data(data: Vec<i32>) -> PyResult<HashMap<String, f64>> {
    // 추가된 내용
    if data.is_empty() {
        return Err(PyValueError::new_err("입력 리스트가 비어 있어 계산할 수 없습니다."));
    }
    //! 추가된 내용

    let sum: i32 = data.iter().sum();
    let count = data.len();

    let avg = sum as f64 / count as f64; // MODIFIED!

    let mut stats = HashMap::new();
    stats.insert("sum".to_string(), sum as f64);
    stats.insert("average".to_string(), avg);
    stats.insert("count".to_string(), count as f64);

    Ok(stats)
}

#[pymodule]
fn rust_engine(m: &Bound<'_, PyModule>) -> PyResult<()> {
    m.add_function(wrap_pyfunction!(process_data, m)?)?;

    Ok(())
}

Python 코드

try:except: 를 사용하여 에러를 처리할 것이다.
이를 위해 fastapi 에서 HTTPException 도 불러와야 한다.

정상적인 실행에서는 결과값이 담긴 딕셔너리가 반환되고
Rust에서 명시적으로 반환한 ValueError 를 받을 경우 400 에러를,
그 외 예상치 못한 오류 발생 시 500 에러를 전달한다.

필요에 따라 Rust에서 Python 용 에러 클래스를 정의하여
except: 를 보다 정밀하게 사용할 수도 있지만 여기서는 다루지 않겠다.

app/main.py

from fastapi import FastAPI, HTTPException # MODIFIED!
from pydantic import BaseModel
import rust_engine

app = FastAPI()

class DataInput(BaseModel):
    numbers: list[int]

@app.post("/analyze")
def analyze_data(input_data: DataInput):
    try: # NEW!
        stats = rust_engine.process_data(input_data.numbers)

        return {
            "status": "success",
            "source": "FastAPI",
            "engine": "Rust",
            "analysis": stats
        }

	# 추가된 내용
    except ValueError as e:
        raise HTTPException(status_code=400, detail=str(e))

    except Exception as e:
        raise HTTPException(status_code=500, detail="서버 내부 오류가 발생했습니다.")
	#! 추가된 내용

빌드 및 실행

Maturin 라이브러리를 통해 Rust 코드를 Python에서 호출 가능한 형태로 컴파일한다.
컴파일 후 pip list 명령어를 사용해 보면 Cargo.toml 파일에 작성한 패키지 이름을 확인할 수 있다.

uvicorn 라이브러리를 통해 FastAPI를 실행한다.

~/workspace/complex-data$ maturin develop
~/workspace/complex-data$ uvicorn app.main:app --reload

curl 명령어 또는 브라우저를 통해 테스트를 해볼 수 있다.

이 예제의 경우 GET 메서드가 아닌 POST 메서드로 통신하므로
브라우저를 통한 테스트 시 Swagger UI를 사용해야 한다.
FastAPI의 경우 다음과 같은 주소로 Swagger UI가 내장되어 있다.

  • http://127.0.0.1:8000/docs

빈 리스트를 전달할 경우 400 에러가 발생하고
numbers 를 전달하지 않는 등 다른 입력 데이터에 대해서는
500 에러가 발생하는 것을 확인할 수 있다.

profile
Peter J Online Space - since July 2020 | 아무데서나 채용해줬으면 좋겠다

0개의 댓글