diff --git a/oxapy/__init__.py b/oxapy/__init__.py index 6e05c4f..587d5a6 100644 --- a/oxapy/__init__.py +++ b/oxapy/__init__.py @@ -1,4 +1,4 @@ -from .oxapy import * +from .oxapy import * # ty:ignore[unresolved-import] import os import mimetypes @@ -9,7 +9,7 @@ def secure_join(base: str, *paths: str) -> str: target = os.path.realpath(os.path.join(base, *paths)) if target != base and not target.startswith(base + os.sep): - raise exceptions.ForbiddenError("Access denied") + raise exceptions.ForbiddenError("Access denied") # ty:ignore[unresolved-reference] return target @@ -31,7 +31,7 @@ def static_file(path: str = "/static", directory: str = "./static"): ``` """ - @get(f"{path}/{{*path}}") + @get(f"{path}/{{*path}}") # ty:ignore[unresolved-reference] def handler(_request, path: str): file_path = secure_join(directory, path) return send_file(file_path) @@ -39,7 +39,7 @@ def handler(_request, path: str): return handler -def send_file(path: str) -> Response: +def send_file(path: str) -> Response: # ty:ignore[unresolved-reference] r""" Create Response for sending file. @@ -49,15 +49,15 @@ def send_file(path: str) -> Response: Response: A Response with file content """ if not os.path.exists(path): - raise exceptions.NotFoundError("Requested file not found") + raise exceptions.NotFoundError("Requested file not found") # ty:ignore[unresolved-reference] if not os.path.isfile(path): - raise exceptions.ForbiddenError("Not a file") + raise exceptions.ForbiddenError("Not a file") # ty:ignore[unresolved-reference] with open(path, "rb") as f: content = f.read() content_type, _ = mimetypes.guess_type(path) - return Response(content, content_type=content_type or "application/octet-stream") + return Response(content, content_type=content_type or "application/octet-stream") # ty:ignore[unresolved-reference] __all__ = ( diff --git a/src/into_response.rs b/src/into_response.rs index d2cd092..3fd1778 100644 --- a/src/into_response.rs +++ b/src/into_response.rs @@ -1,9 +1,9 @@ -use hyper::{body::Bytes, header::CONTENT_TYPE, HeaderMap}; -use pyo3::{prelude::*, types::PyAny, Py}; +use hyper::{HeaderMap, body::Bytes, header::CONTENT_TYPE}; +use pyo3::{Py, prelude::*, types::PyAny}; use crate::{ - cors::Cors, exceptions::IntoPyException, exceptions::*, json, response::ResponseBody, - status::Status, Response, + Response, cors::Cors, exceptions::IntoPyException, exceptions::*, json, response::ResponseBody, + status::Status, }; type Error = Box; @@ -22,16 +22,16 @@ impl TryFrom for Response { } } -impl TryFrom> for Response { +impl TryFrom> for Response { type Error = Error; - fn try_from(val: Py) -> Result { + fn try_from(val: Bound) -> Result { let mut headers = HeaderMap::new(); headers.insert(CONTENT_TYPE, "application/json".parse()?); Ok(Response { status: Status::OK, headers, - body: ResponseBody::Bytes(json::dumps(&val)?.into()), + body: ResponseBody::Bytes(json::dumps(&val, val.py())?.into()), }) } } @@ -50,16 +50,16 @@ impl TryFrom<(String, Status)> for Response { } } -impl TryFrom<(Py, Status)> for Response { +impl TryFrom<(Bound<'_, PyAny>, Status)> for Response { type Error = Error; - fn try_from(val: (Py, Status)) -> Result { + fn try_from(val: (Bound, Status)) -> Result { let mut headers = HeaderMap::new(); headers.insert(CONTENT_TYPE, "application/json".parse()?); Ok(Response { status: val.1, headers, - body: ResponseBody::Bytes(json::dumps(&val.0)?.into()), + body: ResponseBody::Bytes(json::dumps(&val.0, val.0.py())?.into()), }) } } @@ -179,8 +179,8 @@ pub fn convert_to_response(result: Py, py: Python<'_>) -> PyResult, Status), + (Bound, Status), String, - Py + Bound ) } diff --git a/src/json.rs b/src/json.rs index 2cb9447..ceefa23 100644 --- a/src/json.rs +++ b/src/json.rs @@ -3,51 +3,38 @@ use serde::{Deserialize, Serialize}; static ORJSON: PyOnceLock> = PyOnceLock::new(); -#[inline] -pub fn dumps(data: &Py) -> PyResult { - Python::attach(|py| { - let serialized_data = ORJSON - .get_or_try_init(py, || PyModule::import(py, "orjson").map(|m| m.into()))? - .call_method1(py, "dumps", (data,))? - .call_method1(py, "decode", ("utf-8",))?; - Ok(serialized_data.extract(py)?) - }) +fn orjson(py: Python<'_>) -> PyResult<&Py> { + ORJSON.get_or_try_init(py, || PyModule::import(py, "orjson").map(|m| m.into())) } #[inline] -pub fn loads(data: &str) -> PyResult> { - Python::attach(|py| { - let deserialized_data = ORJSON - .get_or_try_init(py, || PyModule::import(py, "orjson").map(|m| m.into()))? - .call_method1(py, "loads", (data,))?; - Ok(deserialized_data.extract(py)?) - }) +pub fn dumps(data: &Bound, py: Python<'_>) -> PyResult { + let serialized_data = orjson(py)? + .call_method1(py, "dumps", (data,))? + .call_method1(py, "decode", ("utf-8",))?; + Ok(serialized_data.extract(py)?) } -pub struct Wrap(pub T); +#[inline] +pub fn loads(data: &str, py: Python<'_>) -> PyResult> { + let deserialized_data = orjson(py)?.call_method1(py, "loads", (data,))?; + Ok(deserialized_data.extract(py)?) +} -impl TryFrom> for Wrap +pub fn from_pydict2rstruct(dict: &Bound<'_, PyDict>, py: Python<'_>) -> PyResult where T: for<'de> Deserialize<'de>, { - type Error = PyErr; - - fn try_from(value: Bound<'_, PyDict>) -> Result { - let json_string = dumps(&value.into())?; - let value = serde_json::from_str(&json_string) - .map_err(|e| pyo3::exceptions::PyValueError::new_err(e.to_string()))?; - Ok(Wrap(value)) - } + let json_string = dumps(&dict, py)?; + let value = serde_json::from_str(&json_string) + .map_err(|e| pyo3::exceptions::PyValueError::new_err(e.to_string()))?; + Ok(value) } -impl TryFrom> for Py +pub fn from_rstruct2pydict(rstruct: T, py: Python<'_>) -> PyResult> where T: Serialize, { - type Error = PyErr; - - fn try_from(value: Wrap) -> Result { - let json_string = serde_json::json!(value.0).to_string(); - loads(&json_string) - } + let json_string = serde_json::json!(rstruct).to_string(); + loads(&json_string, py) } diff --git a/src/jwt.rs b/src/jwt.rs index f809955..fa9ebde 100644 --- a/src/jwt.rs +++ b/src/jwt.rs @@ -11,7 +11,7 @@ use std::str::FromStr; use std::time::{Duration, SystemTime, UNIX_EPOCH}; use crate::exceptions::IntoPyException; -use crate::json::Wrap; +use crate::json; /// Base class for all JWT related exceptions. #[gen_stub_pyclass] @@ -148,7 +148,7 @@ impl Jwt { .ok_or_else(|| JwtError::new_err("Failed to compute expiration"))?; claims.set_item("exp", exp.as_secs())?; - let Wrap::(claims) = claims.try_into()?; + let claims: Claims = json::from_pydict2rstruct(&claims, claims.py())?; let token = jsonwebtoken::encode( &Header::default(), @@ -187,7 +187,7 @@ impl Jwt { /// except jwt.JwtDecodingError: /// raise exceptions.UnauthorizedError("Invalid or expired token") /// ``` - pub fn verify_token(&self, token: &str) -> PyResult> { + pub fn verify_token(&self, token: &str, py: Python<'_>) -> PyResult> { let token_data = jsonwebtoken::decode::( token, &DecodingKey::from_secret(self.secret.as_bytes()), @@ -201,7 +201,8 @@ impl Jwt { ErrorKind::InvalidAlgorithm => JwtInvalidAlgorithm::new_err("Algorithm mismatch"), _ => JwtDecodingError::new_err(format!("JWT decoding error: {e}")), })?; - Wrap(token_data.claims).try_into() + + crate::json::from_rstruct2pydict(token_data.claims, py) } } diff --git a/src/request.rs b/src/request.rs index b26abb0..eb85bbc 100644 --- a/src/request.rs +++ b/src/request.rs @@ -123,12 +123,12 @@ impl Request { /// value = data["key"] /// return {"received": value} /// ``` - pub fn json(&self) -> PyResult> { + pub fn json(&self, py: Python<'_>) -> PyResult> { let data = self .data .as_ref() .ok_or_else(|| PyException::new_err("The body is not present"))?; - json::loads(data) + json::loads(data, py) } /// Get application-wide data that was set with HttpServer.app_data. diff --git a/src/response.rs b/src/response.rs index c2dcbc7..4597ed6 100644 --- a/src/response.rs +++ b/src/response.rs @@ -230,7 +230,7 @@ impl Response { fn from_json(obj: Bound, status: Status, content_type: HeaderValue) -> PyResult { Ok(Self { status, - body: ResponseBody::Bytes(Bytes::from(json::dumps(&obj.into())?)), + body: ResponseBody::Bytes(Bytes::from(json::dumps(&obj, obj.py())?)), headers: HeaderMap::from_iter([(CONTENT_TYPE, content_type)]), }) } diff --git a/src/serializer/mod.rs b/src/serializer/mod.rs index d153849..9bdf7e1 100644 --- a/src/serializer/mod.rs +++ b/src/serializer/mod.rs @@ -130,7 +130,7 @@ impl Serializer { #[pyo3(signature=())] fn schema(slf: Bound<'_, Self>, py: Python<'_>) -> PyResult> { let schema_value = Self::json_schema_value(&slf.get_type(), false, py)?; - json::loads(&schema_value.to_string()) + json::loads(&schema_value.to_string(), py) } /// Validate the raw JSON data and store the result in `validated_data`. @@ -152,13 +152,13 @@ impl Serializer { /// print(serializer.validated_data["email"]) /// ``` #[pyo3(signature=())] - fn is_valid(slf: &Bound<'_, Self>) -> PyResult<()> { + fn is_valid(slf: &Bound<'_, Self>, py: Python<'_>) -> PyResult<()> { let raw_data = slf .getattr("raw_data")? .extract::>()? .ok_or_else(|| ValidationException::new_err("data is empty"))?; - let attr = json::loads(&raw_data)?; + let attr = json::loads(&raw_data, py)?; let validated_data: Bound = slf.call_method1("validate", (attr,))?.extract()?; @@ -193,7 +193,7 @@ impl Serializer { attr: Bound<'a, PyDict>, py: Python<'a>, ) -> PyResult> { - let json::Wrap(json_value) = attr.clone().try_into()?; + let json_value = json::from_pydict2rstruct(&attr, py)?; let schema_value = Self::json_schema_value(&slf.get_type(), false, py)?; diff --git a/src/templating/minijinja.rs b/src/templating/minijinja.rs index b57d7e2..721894d 100644 --- a/src/templating/minijinja.rs +++ b/src/templating/minijinja.rs @@ -56,7 +56,7 @@ impl Jinja { .into_py_exception()?; let mut ctx_values: HashMap = HashMap::default(); if let Some(context) = context { - let json::Wrap::<_>(value) = context.try_into()?; + let value = json::from_pydict2rstruct(&context, context.py())?; ctx_values = value; } template.render(ctx_values).into_py_exception() diff --git a/src/templating/tera.rs b/src/templating/tera.rs index 6392600..0791a20 100644 --- a/src/templating/tera.rs +++ b/src/templating/tera.rs @@ -1,7 +1,7 @@ use std::sync::Arc; -use crate::json; use crate::IntoPyException; +use crate::json; use ahash::HashMap; use pyo3::{prelude::*, types::PyDict}; use pyo3_stub_gen::derive::*; @@ -31,8 +31,9 @@ impl Tera { ) -> PyResult { let mut tera_context = tera::Context::new(); if let Some(context) = context { - let map: json::Wrap> = context.try_into()?; - for (key, value) in map.0 { + let map: HashMap = + json::from_pydict2rstruct(&context, context.py())?; + for (key, value) in map { tera_context.insert(key, &value); } }