py03 sharing Juniper GraphQL request module (sharing state between dependencies) - rust

Thanks in advance to #brunorijsman for his excellent self answered example of callbacks between rust and python using py03 How to pass a Rust function as a callback to Python using pyo3, which my code is heavily influenced by.
I'm trying to interrupt a GraphQL request using callbacks to a python file which is importing from rust using py03.
I've been able to use a Mutex to store shared state, but i'm stuck at how to store or share with Juniper a callback. I'm planning to eventually await the callbacks on both sides with async and futures, but at the moment I can't find a way to make the callback in rust callable from a Juniper resolver once its been registered...
GQLwrapper rust module lib.rs:
#[macro_use]
extern crate lazy_static;
use actix_cors::Cors;
use actix_web::{
middleware, route,
web::{self, Data},
App, HttpResponse, HttpServer, Responder,
};
use juniper::http::{GraphQLRequest};
use juniper::{EmptySubscription, FieldResult, RootNode, EmptyMutation};
use pyo3::prelude::*;
use std::thread;
use std::{io, sync::Arc};
mod schema;
use crate::schema::{Model, Params};
pub struct QueryRoot;
#[juniper::graphql_object]
impl QueryRoot {
fn model<'mdl>(&self, _params: Params) -> FieldResult<Model> {
// I WANT TO BE ABLE TO CALL THE CALLBACK HERE WITH MY GRAPHQLDATA!
Ok(Model {
prompt: _params.prompt.to_owned(),
})
}
}
type Schema = RootNode<'static, QueryRoot, EmptyMutation, EmptySubscription>;
fn create_schema() -> Schema {
Schema::new(QueryRoot {}, EmptyMutation::new(), EmptySubscription::new())
}
/// GraphQL endpoint
#[route("/graphql", method = "GET", method = "POST")]
async fn graphql(st: web::Data<Schema>, data: web::Json<GraphQLRequest>) -> impl Responder {
let user = data.execute(&st, &()).await;
HttpResponse::Ok().json(user)
}
#[actix_web::main]
async fn main() -> io::Result<()> {
std::env::set_var("RUST_LOG", "actix_web=info");
env_logger::init();
let schema = Arc::new(create_schema());
HttpServer::new(move || {
#[pyfunction]
fn init() -> PyResult<String> {
thread::spawn(move || main());
Ok("GQL server started...".to_string())
}
#[pyclass]
struct Callback {
#[allow(dead_code)]
callback_function: Box<dyn Fn(&PyAny) -> PyResult<()> + Send>,
}
#[pymethods]
impl Callback {
fn __call__(&self, python_api: &PyAny) -> PyResult<()> {
(self.callback_function)(python_api)
}
}
#[pyfunction]
fn rust_register_callback(python_api: &PyAny) -> PyResult<()> {
// this registers a python callback which will in turn be used to send rust_callback
// along with a message when a request is recieved.
Python::with_gil(|py| {
// THIS IS THE CALLBACK I WANT TO BE ABLE TO CALL FROM THE RESOLVER...
let callback = Box::new(Callback {
callback_function: Box::new(move |python_api| {
rust_callback(python_api)
}),
});
println!("Rust: register callback");
python_api
.getattr("set_response_callback")?
.call1((callback.into_py(py),"data piped from request".to_string()))?;
Ok(())
})
}
fn rust_callback(message: &PyAny) -> PyResult<()> {
// This callback will return a message from python and add to the GraphQL response.
// I need to be able to ultimately await this and use it to set state that will be passed back
println!("Rust: rust_callback");
println!("Rust: Message={}", message);
Ok(())
}
#[pymodule]
#[pyo3(name = "GQLwrapper")]
fn GQLwrapper(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_pyfunction!(init, m)?)?;
m.add_function(wrap_pyfunction!(rust_register_callback, m)?)?;
m.add_class::<Callback>()?;
Ok(())
}
App::new()
.app_data(Data::from(schema.clone()))
.service(graphql)
.wrap(Cors::permissive())
.wrap(middleware::Logger::default())
})
.workers(2)
.bind("127.0.0.1:8080")?
.run()
.await
}
Python main.py:
from lib.wrapper import graphql_wrapper
import time
def runner():
print("Python: doing some work")
graphql_wrapper.call_response_callback()
graphql_wrapper.set_fn_to_call(runner)
time.sleep(5000)
python wrapper.py:
import GQLwrapper
class PythonApi:
def __init__(self):
self.response_callback = None
self.python_callback = None
GQLwrapper.init()
def set_response_callback(self, callback, data):
self.response_callback = callback
print(f'Python: Message={data}')
self.python_callback()
def call_response_callback(self):
assert self.response_callback is not None
self.response_callback("data to add back into response")
def set_fn_to_call(self, callback):
self.python_callback = callback
GQLwrapper.rust_register_callback(self)
graphql_wrapper = PythonApi()

Related

Actix and Inventory crate

I'm trying to make it possible to register an actix route automatically. To do so I found the crate Inventory which seems to answer my need.
I have the following code:
#[get("/hello2/{name}")]
async fn greet2(req: HttpRequest) -> String {
let name = req.match_info().get("name").unwrap_or("World");
let a = format!("Hello2 {}!", &name);
a
}
pub struct TestStruct {
test: fn(HttpRequest) -> dyn Future<Output = String>
}
inventory::collect!(TestStruct);
inventory::submit! {
let mut a = TestStruct {
test: <greet2 as HttpServiceFactory>::register
};
a.test.push(greet2::register::greet2);
a
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(move || {
let mut app = App::new();
for route in inventory::iter::<AutomaticHttpService> {
app = app.service(route);
}
app
})
.bind(("127.0.0.1", 8080))?
.workers(2)
.run()
.await
}
My code does not compile as I cannot access the requested function pointer and I cannot use the HttpServiceFactory trait as it is not declared in the same file. I also tried to use the std::any::Any type and then cast to HttpServiceFactory (maybe using unsafe) but without success because its size was not known at compile time.
Would you have any clues to unblock my situation?

Juniper rust: sharing request data with the resolvers with the FromRequest trait

can anyone help get my head around the FromRequest trait.
As I understand, we can use it to share data with the resolvers.
I'm trying to inject the http headers so I can authenticate users before some actions in the resolvers.
See my code below.
Looks like the from_request isn't even called.
I'm using rocket 0.5.rc1
Thank you for your help
use juniper::{graphql_object, EmptySubscription, RootNode};
use mongodb::Client;
use mongodb::Database;
use rocket::{catch, catchers};
use rocket::{response::content, Rocket, State};
type Schema = RootNode<'static, Query, Mutations, EmptySubscription<AppContext>>;
use rocket::{
http::Status,
request::{FromRequest, Outcome},
Request,
};
struct Mutations;
struct Query;
#[derive(Debug)]
pub struct AppContext {
pub mongodb_pool: Database,
pub user: String,
}
impl juniper::Context for AppContext {}
#[rocket::async_trait]
impl<'r> FromRequest<'r> for AppContext {
type Error = ();
async fn from_request(request: &'r Request<'_>) -> Outcome<AppContext, Self::Error> {
let client = Client::with_uri_str("mongodb://admin:admin#localhost:28017").await;
let db = client.unwrap().database("override");
Outcome::Success(AppContext { mongodb_pool: db, user: "from request".to_string() }, )
}
}
#[graphql_object(context = AppContext)]
impl Query {
fn api_version(db: &AppContext) -> &str {
println!("{:?}", db.user);
"1.0"
}
}
#[graphql_object(context = AppContext)]
impl Mutations {
fn api_version(db: &AppContext) -> String {
"1.0".to_string()
}
}
#[rocket::get("/")]
fn graphiql() -> content::Html<String> {
juniper_rocket::graphiql_source("/graphql", None)
}
#[rocket::get("/graphql?<request>")]
async fn get_graphql_handler(
context: &State<AppContext>,
request: juniper_rocket::GraphQLRequest,
schema: &State<Schema>,
) -> juniper_rocket::GraphQLResponse {
request.execute(&*schema, &*context).await
}
#[rocket::post("/graphql", data = "<request>")]
async fn post_graphql_handler(
context: &State<AppContext>,
request: juniper_rocket::GraphQLRequest,
schema: &State<Schema>,
) -> juniper_rocket::GraphQLResponse {
request.execute(&*schema, &*context).await
}
#[tokio::main]
async fn main() {
let client = Client::with_uri_str("mongodb://admin:admin#localhost:28017").await;
let db = client.unwrap().database("app_db");
Rocket::build()
.manage(AppContext{ mongodb_pool: db, user: "from build".to_string() })
.manage(Schema::new(
Query,
Mutations,
EmptySubscription::<AppContext>::new(),
))
.mount(
"/",
rocket::routes![graphiql, get_graphql_handler, post_graphql_handler],
)
.launch()
.await
.expect("server failed to launch");
}

How to use routes attributes macros for multiple methods in Actix-Web

In the Actix Web Framework, how does one use the route attributes macros (#[http_method("route")]) to bind multiple http methods to one function?
For example, I have this trivial endpoint:
/// Returns a UUID4.
#[get("/uuid")]
async fn uuid_v4() -> impl Responder {
HttpResponse::Ok().json(Uuid {
uuid: uuid::Uuid::new_v4(),
})
}
I would like to have the same endpoint handle HEAD requests, how do I do this?
My initial approach was to just stack up the macros:
/// Returns a UUID4.
#[get("/uuid")]
#[head("/uuid")]
async fn uuid_v4() -> impl Responder {
HttpResponse::Ok().json(Uuid {
uuid: uuid::Uuid::new_v4(),
})
}
But I do get a compilation error:
|
249 | async fn uuid_v4() -> impl Responder {
| ^^^^^^^ the trait `actix_web::handler::Factory<_, _, _>` is not implemented for `<uuid_v4 as actix_web::service::HttpServiceFactory>::register::uuid_v4`
I have gone through the actix-web and actix-web-codegen docs and didn't find anything addressing this
you can do
#[route("/", method="GET", method="POST", method="PUT")]
async fn index() -> impl Responder {
HttpResponse::Ok().body("Hello world!")
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(move || {
App::new()
.service(index)
})
.bind("127.0.0.1:8080")?
.run()
.await
}
An example with multiple path and multiple methods for one resource
async fn index() -> impl Responder {
HttpResponse::Ok().body("Hello world!")
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(move || {
App::new()
.service(
actix_web::web::resource(vec!["/", "/index"])
.route(actix_web::web::get().to(index))
.route(actix_web::web::post().to(index))
)
})
.bind("127.0.0.1:8080")?
.run()
.await
}
I assume you are using actix-web: 2.0.0 with actix-rt: 1.0.0 and this handler you are passing to App.service method like below
HttpServer::new(move || {
App::new()
.wrap(middleware::Logger::default())
.service(index)
})
.bind(("127.0.0.1", self.port))?
.workers(8)
.run()
.await
then you will need to write handler like this ->
/// Returns a UUID4.
#[get("/uuid")]
async fn uuid_v4(req: HttpRequest) -> Result<web::Json<IndexResponse>> {
let uuid_header = req
.headers()
.get("uuid")
.and_then(|v| v.to_str().ok())
.unwrap_or_else(|| "some-id");
//curl -H "uuid: username" localhost:8080
println!("make use of {}", uuid_header);
Ok(web::Json(Uuid {
uuid: uuid::Uuid::new_v4(),
}))
}

Calling an async function synchronously with tokio [duplicate]

I am trying to use hyper to grab the content of an HTML page and would like to synchronously return the output of a future. I realized I could have picked a better example since synchronous HTTP requests already exist, but I am more interested in understanding whether we could return a value from an async calculation.
extern crate futures;
extern crate hyper;
extern crate hyper_tls;
extern crate tokio;
use futures::{future, Future, Stream};
use hyper::Client;
use hyper::Uri;
use hyper_tls::HttpsConnector;
use std::str;
fn scrap() -> Result<String, String> {
let scraped_content = future::lazy(|| {
let https = HttpsConnector::new(4).unwrap();
let client = Client::builder().build::<_, hyper::Body>(https);
client
.get("https://hyper.rs".parse::<Uri>().unwrap())
.and_then(|res| {
res.into_body().concat2().and_then(|body| {
let s_body: String = str::from_utf8(&body).unwrap().to_string();
futures::future::ok(s_body)
})
}).map_err(|err| format!("Error scraping web page: {:?}", &err))
});
scraped_content.wait()
}
fn read() {
let scraped_content = future::lazy(|| {
let https = HttpsConnector::new(4).unwrap();
let client = Client::builder().build::<_, hyper::Body>(https);
client
.get("https://hyper.rs".parse::<Uri>().unwrap())
.and_then(|res| {
res.into_body().concat2().and_then(|body| {
let s_body: String = str::from_utf8(&body).unwrap().to_string();
println!("Reading body: {}", s_body);
Ok(())
})
}).map_err(|err| {
println!("Error reading webpage: {:?}", &err);
})
});
tokio::run(scraped_content);
}
fn main() {
read();
let content = scrap();
println!("Content = {:?}", &content);
}
The example compiles and the call to read() succeeds, but the call to scrap() panics with the following error message:
Content = Err("Error scraping web page: Error { kind: Execute, cause: None }")
I understand that I failed to launch the task properly before calling .wait() on the future but I couldn't find how to properly do it, assuming it's even possible.
Standard library futures
Let's use this as our minimal, reproducible example:
async fn example() -> i32 {
42
}
Call executor::block_on:
use futures::executor; // 0.3.1
fn main() {
let v = executor::block_on(example());
println!("{}", v);
}
Tokio
Use the tokio::main attribute on any function (not just main!) to convert it from an asynchronous function to a synchronous one:
use tokio; // 0.3.5
#[tokio::main]
async fn main() {
let v = example().await;
println!("{}", v);
}
tokio::main is a macro that transforms this
#[tokio::main]
async fn main() {}
Into this:
fn main() {
tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap()
.block_on(async { {} })
}
This uses Runtime::block_on under the hood, so you can also write this as:
use tokio::runtime::Runtime; // 0.3.5
fn main() {
let v = Runtime::new().unwrap().block_on(example());
println!("{}", v);
}
For tests, you can use tokio::test.
async-std
Use the async_std::main attribute on the main function to convert it from an asynchronous function to a synchronous one:
use async_std; // 1.6.5, features = ["attributes"]
#[async_std::main]
async fn main() {
let v = example().await;
println!("{}", v);
}
For tests, you can use async_std::test.
Futures 0.1
Let's use this as our minimal, reproducible example:
use futures::{future, Future}; // 0.1.27
fn example() -> impl Future<Item = i32, Error = ()> {
future::ok(42)
}
For simple cases, you only need to call wait:
fn main() {
let s = example().wait();
println!("{:?}", s);
}
However, this comes with a pretty severe warning:
This method is not appropriate to call on event loops or similar I/O situations because it will prevent the event loop from making progress (this blocks the thread). This method should only be called when it's guaranteed that the blocking work associated with this future will be completed by another thread.
Tokio
If you are using Tokio 0.1, you should use Tokio's Runtime::block_on:
use tokio; // 0.1.21
fn main() {
let mut runtime = tokio::runtime::Runtime::new().expect("Unable to create a runtime");
let s = runtime.block_on(example());
println!("{:?}", s);
}
If you peek in the implementation of block_on, it actually sends the future's result down a channel and then calls wait on that channel! This is fine because Tokio guarantees to run the future to completion.
See also:
How can I efficiently extract the first element of a futures::Stream in a blocking manner?
As this is the top result that come up in search engines by the query "How to call async from sync in Rust", I decided to share my solution here. I think it might be useful.
As #Shepmaster mentioned, back in version 0.1 futures crate had beautiful method .wait() that could be used to call an async function from a sync one. This must-have method, however, was removed from later versions of the crate.
Luckily, it's not that hard to re-implement it:
trait Block {
fn wait(self) -> <Self as futures::Future>::Output
where Self: Sized, Self: futures::Future
{
futures::executor::block_on(self)
}
}
impl<F,T> Block for F
where F: futures::Future<Output = T>
{}
After that, you can just do following:
async fn example() -> i32 {
42
}
fn main() {
let s = example().wait();
println!("{:?}", s);
}
Beware that this comes with all the caveats of original .wait() explained in the #Shepmaster's answer.
This works for me using tokio:
tokio::runtime::Runtime::new()?.block_on(fooAsyncFunction())?;

How do I pass App data to service route handler function in actix-web when using function decorations?

I found in the docs an example of how to create global state, protected by Mutex, shared among processing threads that is made available to all your route handlers. Perfect! However, I prefer to use attributes attached to my functions to wire up my route handlers. I do not know the syntax (if permitted) to use attributed functions and also pass in the global state.
Here is the example from the actix-web docs, from https://docs.rs/actix-web/1.0.2/actix_web/web/struct.Data.html
use std::sync::Mutex;
use actix_web::{web, App};
struct MyData {
counter: usize,
}
/// Use `Data<T>` extractor to access data in handler.
fn index(data: web::Data<Mutex<MyData>>) {
let mut data = data.lock().unwrap();
data.counter += 1;
}
fn main() {
let data = web::Data::new(Mutex::new(MyData{ counter: 0 }));
let app = App::new()
// Store `MyData` in application storage.
.register_data(data.clone())
.service(
web::resource("/index.html").route(
web::get().to(index)));
}
Notice how the route handler named index is being passed the web::Data.
Now here are some snippets of my code.
use actix_web::{get, App, HttpResponse, HttpServer, Responder};
pub mod request;
pub mod routes;
const SERVICE_NAME : &str = "Shy Rules Engine";
const SERVICE_VERSION : &str = "0.1";
#[get("/")]
fn index() -> impl Responder {
HttpResponse::Ok().body(format!("{} version {}", SERVICE_NAME, SERVICE_VERSION))
}
mod expression_execute {
#[post("/expression/execute")]
fn route(req: web::Json<ExpressionExecuteRequest>) -> HttpResponse {
// ... lots of code omitted ...
if response.has_error() {
HttpResponse::Ok().json(response)
}
else {
HttpResponse::BadRequest().json(response)
}
}
}
pub fn shy_service(ip : &str, port : &str) {
HttpServer::new(|| {
App::new()
.service(index)
.service(expression_execute::route)
})
.bind(format!("{}:{}", ip, port))
.unwrap()
.run()
.unwrap();
}
Notice how I am calling method App::service to wire up my route handlers.
Also notice how my route handler does not receive global state (because I have not yet added it to my app). If I used a similar pattern as the docs using register_data to create global App data, what changes do I make to my method signature, the get and post attributes and anything else so that I can pass that global state to the handler?
Or is it not possible using get and post attributes to gain access to global state?
The two cases you listed really don't have much difference:
//# actix-web = "1.0.8"
use actix_web::{get, web, App, HttpResponse, HttpServer, Responder};
use std::sync::Mutex;
const SERVICE_NAME : &str = "Shy Rules Engine";
const SERVICE_VERSION : &str = "0.1";
struct MyData {
counter: usize,
}
#[get("/")]
fn index(data: web::Data<Mutex<MyData>>) -> impl Responder {
let mut data = data.lock().unwrap();
data.counter += 1;
println!("Endpoint visited: {}", data.counter);
HttpResponse::Ok().body(format!("{} version {}", SERVICE_NAME, SERVICE_VERSION))
}
pub fn shy_service(ip : &str, port : &str) {
let data = web::Data::new(Mutex::new(MyData{ counter: 0 }));
HttpServer::new(move || {
App::new()
.register_data(data.clone())
.service(index)
})
.bind(format!("{}:{}", ip, port))
.unwrap()
.run()
.unwrap();
}
fn main() {
shy_service("127.0.0.1", "8080");
}
You can verify that it works by simply curl the http endpoint. For multiple extractors, you'll have to use tuple:
#[post("/expression/execute")]
fn route((req, data): (web::Json<ExpressionExecuteRequest>, web::Data<Mutex<MyData>>)) -> HttpResponse {
unimplemented!()
}

Resources