2026년 7월 21일 화요일

src/lib.rs



use pyo3::prelude::*;
use reqwest::{
  header::{HeaderMap, HeaderName, HeaderValue},
  Method,
};
use std::collections::HashMap;

#[pyfunction]
#[pyo3(signature = (url, method="GET", headers=None, body=None))]
fn curl_async<'py>(
  py: Python<'py>,
  url: String,
  method: &str,
  headers: Option>,
  body: Option,
) -> PyResult> {
  // 1. HTTP 메서드 파싱 (GET, POST 등)
  let http_method = Method::from_bytes(method.as_bytes())
    .map_err(|_| pyo3::exceptions::PyValueError::new_err("Invalid HTTP method"))?;

  // 2. 헤더 구성
  let mut header_map = HeaderMap::new();
  if let Some(headers_hash) = headers {
    for (k, v) in headers_hash {
      let name = HeaderName::from_bytes(k.as_bytes()).map_err(|_| {
        pyo3::exceptions::PyValueError::new_err(format!("Invalid header name: {}", k))
      })?;
      let value = HeaderValue::from_str(&v).map_err(|_| {
        pyo3::exceptions::PyValueError::new_err(format!("Invalid header value: {}", v))
      })?;
      header_map.insert(name, value);
    }
  }

  // 3. 비동기 런타임으로 전달
  pyo3_async_runtimes::tokio::future_into_py(py, async move {
    let client = reqwest::Client::new();
    let mut request_builder = client.request(http_method, &url).headers(header_map);

    // 바디 데이터가 있으면 요청에 주입
    if let Some(body_content) = body {
      request_builder = request_builder.body(body_content);
    }

    // 요청 전송 및 리스폰스 수신
    let response = request_builder
      .send()
      .await
      .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("Request failed: {}", e)))?;

    // 상태 코드와 본문 문자열을 가져옴
    let status = response.status().as_u16();
    let text = response.text().await.map_err(|e| {
      pyo3::exceptions::PyRuntimeError::new_err(format!("Failed to read body: {}", e))
    })?;

    // 파이썬으로 반환할 튜플 구성 (상태코드, 본문텍스트)
    Ok((status, text))
  })
}

#[pymodule]
fn rust_curl(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> {
  m.add_function(wrap_pyfunction!(curl_async, m)?)?;
  Ok(())
}


database.py

import os from contextlib import contextmanager # contextmanager 임포트 from typing import Generator from dotenv...