Skip to content

Commit

Permalink
fix: introduce PyRequest and PyResponse struct
Browse files Browse the repository at this point in the history
  • Loading branch information
AntoineRR committed Mar 19, 2023
1 parent 1ff3bb3 commit 450cad1
Show file tree
Hide file tree
Showing 22 changed files with 411 additions and 427 deletions.
32 changes: 16 additions & 16 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ futures-util = "0.3.27"
matchit = "0.7.0"
socket2 = { version = "0.5.1", features = ["all"] }
uuid = { version = "1.3.0", features = ["serde", "v4"] }
serde = "1.0.155"
serde = "1.0.157"
serde_json = "1.0.94"
log = "0.4.17"

Expand Down
50 changes: 30 additions & 20 deletions integration_tests/base_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,17 +57,13 @@ def shutdown_handler():

@app.before_request("/sync/middlewares")
def sync_before_request(request: Request):
new_headers = request.headers
new_headers["before"] = "sync_before_request"
request.headers = new_headers
request.headers["before"] = "sync_before_request"
return request


@app.after_request("/sync/middlewares")
def sync_after_request(response: Response):
new_headers = response.headers
new_headers["after"] = "sync_after_request"
response.headers = new_headers
response.headers["after"] = "sync_after_request"
response.body = response.body + " after"
return response

Expand All @@ -81,17 +77,13 @@ def sync_middlewares(request: Request):

@app.before_request("/async/middlewares")
async def async_before_request(request: Request):
new_headers = request.headers
new_headers["before"] = "async_before_request"
request.headers = new_headers
request.headers["before"] = "async_before_request"
return request


@app.after_request("/async/middlewares")
async def async_after_request(response: Response):
new_headers = response.headers
new_headers["after"] = "async_after_request"
response.headers = new_headers
response.headers["after"] = "async_after_request"
response.body = response.body + " after"
return response

Expand Down Expand Up @@ -274,28 +266,46 @@ async def async_param(request: Request):


@app.get("/sync/extra/*extra")
def sync_param_extra(request):
extra = request["params"]["extra"]
def sync_param_extra(request: Request):
extra = request.params["extra"]
return extra


@app.get("/async/extra/*extra")
async def async_param_extra(request):
extra = request["params"]["extra"]
async def async_param_extra(request: Request):
extra = request.params["extra"]
return extra


# Request Info


@app.get("/sync/http/param")
def sync_http_param(request):
return jsonify({"url": request["url"], "method": request["method"]})
def sync_http_param(request: Request):
return jsonify(
{
"url": {
"scheme": request.url.scheme,
"host": request.url.host,
"path": request.url.path,
},
"method": request.method,
}
)


@app.get("/async/http/param")
async def async_http_param(request):
return jsonify({"url": request["url"], "method": request["method"]})
async def async_http_param(request: Request):
return jsonify(
{
"url": {
"scheme": request.url.scheme,
"host": request.url.host,
"path": request.url.path,
},
"method": request.method,
}
)


# HTML serving
Expand Down
2 changes: 2 additions & 0 deletions integration_tests/test_middlewares.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
@pytest.mark.parametrize("function_type", ["sync", "async"])
def test_middlewares(function_type: str, session):
r = get(f"/{function_type}/middlewares")
# We do not want the request headers to be in the response
assert "before" not in r.headers
assert "after" in r.headers
assert r.headers["after"] == f"{function_type}_after_request"
assert r.text == f"{function_type} middlewares after"
7 changes: 1 addition & 6 deletions integration_tests/views/async_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,4 @@ async def get():
return "Hello, world!"

async def post(request: Request):
body = request.body
return {
"status": 200,
"body": body,
"headers": {"Content-Type": "text/json"},
}
return request.body
7 changes: 1 addition & 6 deletions integration_tests/views/sync_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,4 @@ def get():
return "Hello, world!"

def post(request: Request):
body = request.body
return {
"status": 200,
"body": body,
"headers": {"Content-Type": "text/json"},
}
return request.body
23 changes: 10 additions & 13 deletions robyn/robyn.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -15,30 +15,27 @@ class FunctionInfo:
is_async: bool
number_of_params: int

@dataclass
class Body:
content: Union[str, bytes]

def as_bytes(self) -> bytes:
pass
def set(self, content: Union[str, bytes]):
pass
class Url:
scheme: str
host: str
path: str

@dataclass
class Request:
queries: dict[str, str]
headers: dict[str, str]
params: dict[str, str]
body: Body
body: Union[str, bytes]
method: str
url: Url

@dataclass
class Response:
status_code: int
response_type: str
headers: dict[str, str]
body: Body

def set_file_path(self, file_path: str):
pass
body: Union[str, bytes]
file_path: str

class Server:
def __init__(self) -> None:
Expand Down
2 changes: 1 addition & 1 deletion robyn/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def _format_response(self, res):
response = Response(status_code=status_code, headers=headers, body=body)
file_path = res.get("file_path")
if file_path is not None:
response.set_file_path(file_path)
response.file_path = file_path
elif isinstance(res, Response):
response = res
elif isinstance(res, bytes):
Expand Down
40 changes: 21 additions & 19 deletions src/executors/mod.rs
Original file line number Diff line number Diff line change
@@ -1,55 +1,56 @@
/// This is the module that has all the executor functions
/// i.e. the functions that have the responsibility of parsing and executing functions.
use crate::types::{FunctionInfo, Request, Response};

use std::sync::Arc;

use anyhow::{anyhow, Context, Result};
use anyhow::{Context, Result};
use log::debug;
use pyo3::prelude::*;
use pyo3_asyncio::TaskLocals;
// pyO3 module
use pyo3::{prelude::*, PyClass};

use crate::types::{function_info::FunctionInfo, request::Request, response::Response};

fn get_function_output<'a, T>(
function: &'a FunctionInfo,
py: Python<'a>,
input: &T,
) -> Result<&'a PyAny, PyErr>
where
T: Clone + IntoPy<Py<PyAny>>,
T: ToPyObject,
{
let handler = function.handler.as_ref(py);

// this makes the request object accessible across every route
match function.number_of_params {
0 => handler.call0(),
1 => handler.call1((input.clone(),)),
1 => handler.call1((input.to_object(py),)),
// this is done to accommodate any future params
2_u8..=u8::MAX => handler.call1((input.clone(),)),
2_u8..=u8::MAX => handler.call1((input.to_object(py),)),
}
}

pub async fn execute_middleware_function<'a, T>(input: &T, function: FunctionInfo) -> Result<T>
pub async fn execute_middleware_function<T>(input: &T, function: FunctionInfo) -> Result<T>
where
T: Clone + PyClass + IntoPy<Py<PyAny>>,
T: for<'a> FromPyObject<'a> + ToPyObject,
{
if function.is_async {
let output = Python::with_gil(|py| {
let output: Py<PyAny> = Python::with_gil(|py| {
pyo3_asyncio::tokio::into_future(get_function_output(&function, py, input)?)
})?
.await?;

Python::with_gil(|py| -> PyResult<T> {
let output: (T,) = output.extract(py)?;
Python::with_gil(|py| -> Result<T> {
let output: (T,) = output
.extract(py)
.context("Failed to get middleware response")?;
Ok(output.0)
})
.map_err(|e| anyhow!(e))
} else {
Python::with_gil(|py| -> PyResult<T> {
let output: (T,) = get_function_output(&function, py, input)?.extract()?;
Python::with_gil(|py| -> Result<T> {
let output: (T,) = get_function_output(&function, py, input)?
.extract()
.context("Failed to get middleware response")?;
Ok(output.0)
})
.map_err(|e| anyhow!(e))
}
}

Expand All @@ -67,8 +68,9 @@ pub async fn execute_http_function(request: &Request, function: FunctionInfo) ->
})
} else {
Python::with_gil(|py| -> Result<Response> {
let output = get_function_output(&function, py, request)?;
output.extract().context("Failed to get route response")
get_function_output(&function, py, request)?
.extract()
.context("Failed to get route response")
})
}
}
Expand Down
8 changes: 4 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,17 @@ use shared_socket::SocketHeld;

// pyO3 module
use pyo3::prelude::*;
use types::{ActixBytesWrapper, FunctionInfo, Request, Response};
use types::{function_info::FunctionInfo, request::PyRequest, response::PyResponse, Url};

#[pymodule]
pub fn robyn(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
// the pymodule class to make the rustPyFunctions available
m.add_class::<Server>()?;
m.add_class::<SocketHeld>()?;
m.add_class::<FunctionInfo>()?;
m.add_class::<Request>()?;
m.add_class::<Response>()?;
m.add_class::<ActixBytesWrapper>()?;
m.add_class::<PyRequest>()?;
m.add_class::<PyResponse>()?;
m.add_class::<Url>()?;
pyo3::prepare_freethreaded_python();
Ok(())
}
5 changes: 3 additions & 2 deletions src/routers/const_router.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ use std::sync::Arc;
use std::sync::RwLock;

use crate::executors::execute_http_function;
use crate::types::Response;
use crate::types::{FunctionInfo, Request};
use crate::types::function_info::FunctionInfo;
use crate::types::request::Request;
use crate::types::response::Response;
use anyhow::Context;
use log::debug;
use matchit::Router as MatchItRouter;
Expand Down
Loading

0 comments on commit 450cad1

Please sign in to comment.