pyo3-twisted-web

Crates.iopyo3-twisted-web
lib.rspyo3-twisted-web
version0.1.1
sourcesrc
created_at2022-10-07 15:20:31.301342
updated_at2023-02-02 10:34:59.707026
descriptionHandle Python Twisted requests through a Tower service in Rust
homepage
repositoryhttps://github.com/sandhose/pyo3-twisted-web/
max_upload_size
id682914
size29,727
Quentin Gliech (sandhose)

documentation

README

Handle Twisted requests through a tower::Service

This library helps converting Twisted's IRequest to an http::Request, and then sending the http::Response back.

Usage

Handle a Twisted request through a Service

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()

Define a Twisted Resource out of a Service

use 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()

Limitations

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).

Commit count: 13

cargo fmt