Crates.io | pyo3-twisted-web |
lib.rs | pyo3-twisted-web |
version | 0.1.1 |
source | src |
created_at | 2022-10-07 15:20:31.301342 |
updated_at | 2023-02-02 10:34:59.707026 |
description | Handle Python Twisted requests through a Tower service in Rust |
homepage | |
repository | https://github.com/sandhose/pyo3-twisted-web/ |
max_upload_size | |
id | 682914 |
size | 29,727 |
tower::Service
This library helps converting Twisted's IRequest
to an http::Request
, and then sending the http::Response
back.
use std::convert::Infallible;
use bytes::Bytes;
use http::{Request, Response};
use http_body::Full;
use pyo3::prelude::*;
use tower::util::BoxCloneService;
use pyo3_twisted_web::handle_twisted_request_through_service;
#[pyclass]
struct Handler {
service: BoxCloneService<Request<Full<Bytes>>, Response<String>, Infallible>,
}
#[pymethods]
impl Handler {
#[new]
fn new() -> Self {
let service = tower::service_fn(|_request: Request<_>| async move {
let response = Response::new(String::from("hello"));
Ok(response)
});
Self {
service: BoxCloneService::new(service),
}
}
fn handle<'a>(&self, twisted_request: &'a PyAny) -> PyResult<&'a PyAny> {
let service = self.service.clone();
handle_twisted_request_through_service(service, twisted_request)
}
}
#[pymodule]
fn my_handler(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_class::<Handler>()?;
Ok(())
}
And on the Python side:
from twisted.internet import asyncioreactor
asyncioreactor.install()
import asyncio
from twisted.internet.defer import ensureDeferred, Deferred
from twisted.web.server import NOT_DONE_YET
from twisted.web import server, resource
from twisted.internet import endpoints, reactor
from my_handler import Handler
class MyResource(resource.Resource):
isLeaf = True
def __init__(self) -> None:
super().__init__()
self.handler = Handler()
def render(self, request):
f = self.handler.handle(request)
ensureDeferred(Deferred.fromFuture(f))
return NOT_DONE_YET
endpoints.serverFromString(reactor, "tcp:8888").listen(server.Site(MyResource()))
reactor.run()
Resource
out of a Serviceuse std::convert::Infallible;
use bytes::Bytes;
use http::{Request, Response};
use pyo3::prelude::*;
use pyo3_twisted_web::Resource;
// Via a (sub)class
#[pyclass(extends=Resource)]
struct MyResource;
#[pymethods]
impl MyResource {
#[new]
fn new() -> (Self, Resource) {
let service = tower::service_fn(|_request: Request<_>| async move {
let response = Response::new(String::from("hello"));
Ok(response)
});
let super_ = Resource::from_service::<_, _, Infallible>(service);
(Self, super_)
}
}
// Via a function
#[pyfunction]
fn get_resource(py: Python) -> PyResult<Py<Resource>> {
let service = tower::service_fn(|_request: Request<_>| async move {
let response = Response::new(String::from("hello"));
Ok(response)
});
Py::new(py, Resource::from_service::<_, _, Infallible>(service))
}
#[pymodule]
fn my_handler(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_class::<MyResource>()?;
m.add_function(wrap_pyfunction!(get_resource, m)?)?;
Ok(())
}
And on the Python side:
from twisted.internet import asyncioreactor
asyncioreactor.install()
import asyncio
from twisted.web.server import Site
from twisted.internet import endpoints, reactor
from my_handler import MyResource, get_resource
endpoints.serverFromString(reactor, "tcp:8888").listen(Site(MyResource()))
# or
endpoints.serverFromString(reactor, "tcp:8888").listen(Site(get_resource()))
reactor.run()
The Twisted asyncioreactor
should be installed. Futures are executed by the tokio
runtime through pyo3-asyncio
, which requires an asyncio event loop to be running.
The Service must accept an http::Request<bytes::Bytes>
, and return an http::Request<impl bytes::Buf>
.
The Service::Error
must convert to pyo3::PyErr
(std::convert::Infallible
is a good candidate if you don't want your handler to throw a Python exception).