
복잡한 데이터라고 했지만 아주 복잡한 녀석은 아니고,
List 와 Dict 를 다루는 예제다.
Python의 List 는 Rust의 Vec 으로 작성되며
Python의 Dict 는 Rust의 HashMap 으로 작성된다.
~/workspace$ mkdir complex-data && cd complex-data ~/workspace/complex-data$ python3 -m venv venv ~/workspace/complex-data$ source venv/bin/activate ~/workspace/complex-data$ pip install maturin fastapi uvicorn ~/workspace/complex-data$ maturin init ~/workspace/complex-data$ # 선택지 중 기본값인 PyO3 선택 ~/workspace/complex-data$ # Cargo.toml과 src/lib.rs가 자동 생성된다 ~/workspace/complex-data$ # Python 코드는 직접 생성해 주어야 한다 ~/workspace/complex-data$ mkdir app && touch app/main.py ~/workspace/complex-data$ tree -I venv . ├── app │ └── main.py ├── Cargo.toml ├── pyproject.toml └── src └── lib.rs
Cargo.toml 파일을 열어 라이브러리 이름을 수정해 주겠다.
Cargo.toml[package] name = "complex-data" version = "0.1.0" edition = "2024" [lib] name = "rust_engine" crate-type = ["cdylib"] [dependencies] pyo3 = "0.28.0"
List 역할을 하는 Vec 는 Rust의 기본 자료형 중 하나이므로
따로 불러오지 않아도 사용할 수 있지만
Dict 역할을 하는 HashMap 의 경우 std::collections::HashMap 을 불러와야 한다.
Python에서 List 로 숫자를 받아 Rust에 전해주면
Rust가 그것을 Vec 로서 처리하여 HashMap 에 결과값을 넣고 반환하며
Python이 반환받은 값을 Dict 로서 처리하는 코드를 작성할 것이다.
src/lib.rsuse pyo3::prelude::*; use std::collections::HashMap; #[pyfunction] fn process_data(data: Vec<i32>) -> PyResult<HashMap<String, f64>> { let sum: i32 = data.iter().sum(); let count = data.len(); let avg = if count > 0 { sum as f64 / count as f64 } else { 0.0 }; 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(()) }
여기서는 정수 값을 담는 List 를 속성으로 가진 DataInput 클래스를 만들어 사용할 것이다.
이것은 데이터 검증 라이브러리 pydantic 의 BaseModel 클래스를 상속받아 생성한다.
과거 버전의 경우 from typing import List 를 통해 List 를 가져와야
자료형을 명시하여 사용할 수 있었지만
Python 3.9부터는 기본 list 에도 타입 힌트를 포함할 수 있게 되었다.
이 실습에서는 3.9+ 버전의 Python을 상정하고 코드를 작성한다.
app/main.pyfrom fastapi import FastAPI from pydantic import BaseModel import rust_engine app = FastAPI() class DataInput(BaseModel): numbers: list[int] @app.post("/analyze") def analyze_data(input_data: DataInput): stats = rust_engine.process_data(input_data.numbers) return { "source": "FastAPI", "engine": "Rust", "analysis": stats }
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