Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions oxapy/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from .oxapy import *
from .oxapy import * # ty:ignore[unresolved-import]

import os
import mimetypes
Expand All @@ -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

Expand All @@ -31,15 +31,15 @@ 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)

return handler


def send_file(path: str) -> Response:
def send_file(path: str) -> Response: # ty:ignore[unresolved-reference]
r"""
Create Response for sending file.

Expand All @@ -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__ = (
Expand Down
24 changes: 12 additions & 12 deletions src/into_response.rs
Original file line number Diff line number Diff line change
@@ -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<dyn std::error::Error>;
Expand All @@ -22,16 +22,16 @@ impl TryFrom<String> for Response {
}
}

impl TryFrom<Py<PyAny>> for Response {
impl TryFrom<Bound<'_, PyAny>> for Response {
type Error = Error;

fn try_from(val: Py<PyAny>) -> Result<Self, Self::Error> {
fn try_from(val: Bound<PyAny>) -> Result<Self, Self::Error> {
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()),
})
}
}
Expand All @@ -50,16 +50,16 @@ impl TryFrom<(String, Status)> for Response {
}
}

impl TryFrom<(Py<PyAny>, Status)> for Response {
impl TryFrom<(Bound<'_, PyAny>, Status)> for Response {
type Error = Error;

fn try_from(val: (Py<PyAny>, Status)) -> Result<Self, Self::Error> {
fn try_from(val: (Bound<PyAny>, Status)) -> Result<Self, Self::Error> {
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()),
})
}
}
Expand Down Expand Up @@ -179,8 +179,8 @@ pub fn convert_to_response(result: Py<PyAny>, py: Python<'_>) -> PyResult<Respon
Response,
Status,
(String, Status),
(Py<PyAny>, Status),
(Bound<PyAny>, Status),
String,
Py<PyAny>
Bound<PyAny>
)
}
53 changes: 20 additions & 33 deletions src/json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,51 +3,38 @@ use serde::{Deserialize, Serialize};

static ORJSON: PyOnceLock<Py<PyModule>> = PyOnceLock::new();

#[inline]
pub fn dumps(data: &Py<PyAny>) -> PyResult<String> {
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<PyModule>> {
ORJSON.get_or_try_init(py, || PyModule::import(py, "orjson").map(|m| m.into()))
}

#[inline]
pub fn loads(data: &str) -> PyResult<Py<PyDict>> {
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<PyAny>, py: Python<'_>) -> PyResult<String> {
let serialized_data = orjson(py)?
.call_method1(py, "dumps", (data,))?
.call_method1(py, "decode", ("utf-8",))?;
Ok(serialized_data.extract(py)?)
}

pub struct Wrap<T>(pub T);
#[inline]
pub fn loads(data: &str, py: Python<'_>) -> PyResult<Py<PyDict>> {
let deserialized_data = orjson(py)?.call_method1(py, "loads", (data,))?;
Ok(deserialized_data.extract(py)?)
}

impl<T> TryFrom<Bound<'_, PyDict>> for Wrap<T>
pub fn from_pydict2rstruct<T>(dict: &Bound<'_, PyDict>, py: Python<'_>) -> PyResult<T>
where
T: for<'de> Deserialize<'de>,
{
type Error = PyErr;

fn try_from(value: Bound<'_, PyDict>) -> Result<Self, Self::Error> {
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<T> TryFrom<Wrap<T>> for Py<PyDict>
pub fn from_rstruct2pydict<T>(rstruct: T, py: Python<'_>) -> PyResult<Py<PyDict>>
where
T: Serialize,
{
type Error = PyErr;

fn try_from(value: Wrap<T>) -> Result<Self, Self::Error> {
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)
}
9 changes: 5 additions & 4 deletions src/jwt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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) = claims.try_into()?;
let claims: Claims = json::from_pydict2rstruct(&claims, claims.py())?;

let token = jsonwebtoken::encode(
&Header::default(),
Expand Down Expand Up @@ -187,7 +187,7 @@ impl Jwt {
/// except jwt.JwtDecodingError:
/// raise exceptions.UnauthorizedError("Invalid or expired token")
/// ```
pub fn verify_token(&self, token: &str) -> PyResult<Py<PyDict>> {
pub fn verify_token(&self, token: &str, py: Python<'_>) -> PyResult<Py<PyDict>> {
let token_data = jsonwebtoken::decode::<Claims>(
token,
&DecodingKey::from_secret(self.secret.as_bytes()),
Expand All @@ -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)
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,12 +123,12 @@ impl Request {
/// value = data["key"]
/// return {"received": value}
/// ```
pub fn json(&self) -> PyResult<Py<PyDict>> {
pub fn json(&self, py: Python<'_>) -> PyResult<Py<PyDict>> {
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.
Expand Down
2 changes: 1 addition & 1 deletion src/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ impl Response {
fn from_json(obj: Bound<PyAny>, status: Status, content_type: HeaderValue) -> PyResult<Self> {
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)]),
})
}
Expand Down
8 changes: 4 additions & 4 deletions src/serializer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ impl Serializer {
#[pyo3(signature=())]
fn schema(slf: Bound<'_, Self>, py: Python<'_>) -> PyResult<Py<PyDict>> {
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`.
Expand All @@ -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::<Option<String>>()?
.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<PyDict> = slf.call_method1("validate", (attr,))?.extract()?;

Expand Down Expand Up @@ -193,7 +193,7 @@ impl Serializer {
attr: Bound<'a, PyDict>,
py: Python<'a>,
) -> PyResult<Bound<'a, PyDict>> {
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)?;

Expand Down
2 changes: 1 addition & 1 deletion src/templating/minijinja.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ impl Jinja {
.into_py_exception()?;
let mut ctx_values: HashMap<String, serde_json::Value> = 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()
Expand Down
7 changes: 4 additions & 3 deletions src/templating/tera.rs
Original file line number Diff line number Diff line change
@@ -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::*;
Expand Down Expand Up @@ -31,8 +31,9 @@ impl Tera {
) -> PyResult<String> {
let mut tera_context = tera::Context::new();
if let Some(context) = context {
let map: json::Wrap<HashMap<String, serde_json::Value>> = context.try_into()?;
for (key, value) in map.0 {
let map: HashMap<String, serde_json::Value> =
json::from_pydict2rstruct(&context, context.py())?;
for (key, value) in map {
tera_context.insert(key, &value);
}
}
Expand Down