Skip to content

Commit 098675d

Browse files
Merge pull request RustPython#531 from coolreader18/wasm-fetch-builtin
[WASM] Add a `browser` module
2 parents cbcf7c3 + 9d99f94 commit 098675d

File tree

7 files changed

+188
-21
lines changed

7 files changed

+188
-21
lines changed

vm/src/macros.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ macro_rules! no_kwargs {
111111
};
112112
}
113113

114+
#[macro_export]
114115
macro_rules! py_module {
115116
( $ctx:expr, $module_name:expr, { $($name:expr => $value:expr),* $(,)* }) => {
116117
{

vm/src/pyobject.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -920,6 +920,29 @@ impl PyFuncArgs {
920920
}
921921
None
922922
}
923+
924+
pub fn get_optional_kwarg_with_type(
925+
&self,
926+
key: &str,
927+
ty: PyObjectRef,
928+
vm: &mut VirtualMachine,
929+
) -> Result<Option<PyObjectRef>, PyObjectRef> {
930+
match self.get_optional_kwarg(key) {
931+
Some(kwarg) => {
932+
if objtype::isinstance(&kwarg, &ty) {
933+
Ok(Some(kwarg))
934+
} else {
935+
let expected_ty_name = vm.to_pystr(&ty)?;
936+
let actual_ty_name = vm.to_pystr(&kwarg.typ())?;
937+
Err(vm.new_type_error(format!(
938+
"argument of type {} is required for named parameter `{}` (got: {})",
939+
expected_ty_name, key, actual_ty_name
940+
)))
941+
}
942+
}
943+
None => Ok(None),
944+
}
945+
}
923946
}
924947

925948
/// Rather than determining the type of a python object, this enum is more

wasm/demo/snippets/fetch.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from browser import fetch
2+
3+
def fetch_handler(res):
4+
print(f"headers: {res['headers']}")
5+
6+
fetch(
7+
"https://httpbin.org/get",
8+
fetch_handler,
9+
lambda err: print(f"error: {err}"),
10+
response_format="json",
11+
headers={"X-Header-Thing": "rustpython is neat!"},
12+
)

wasm/lib/src/browser_module.rs

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
use crate::{convert, vm_class::AccessibleVM, wasm_builtins::window};
2+
use futures::{future, Future};
3+
use js_sys::Promise;
4+
use rustpython_vm::obj::objstr;
5+
use rustpython_vm::pyobject::{PyContext, PyFuncArgs, PyObjectRef, PyResult, TypeProtocol};
6+
use rustpython_vm::VirtualMachine;
7+
use wasm_bindgen::{prelude::*, JsCast};
8+
use wasm_bindgen_futures::{future_to_promise, JsFuture};
9+
10+
enum FetchResponseFormat {
11+
Json,
12+
Text,
13+
ArrayBuffer,
14+
}
15+
16+
impl FetchResponseFormat {
17+
fn from_str(vm: &mut VirtualMachine, s: &str) -> Result<Self, PyObjectRef> {
18+
match s {
19+
"json" => Ok(FetchResponseFormat::Json),
20+
"text" => Ok(FetchResponseFormat::Text),
21+
"array_buffer" => Ok(FetchResponseFormat::ArrayBuffer),
22+
_ => Err(vm.new_type_error("Unkown fetch response_format".into())),
23+
}
24+
}
25+
fn get_response(&self, response: &web_sys::Response) -> Result<Promise, JsValue> {
26+
match self {
27+
FetchResponseFormat::Json => response.json(),
28+
FetchResponseFormat::Text => response.text(),
29+
FetchResponseFormat::ArrayBuffer => response.array_buffer(),
30+
}
31+
}
32+
}
33+
34+
fn browser_fetch(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult {
35+
arg_check!(
36+
vm,
37+
args,
38+
required = [
39+
(url, Some(vm.ctx.str_type())),
40+
(handler, Some(vm.ctx.function_type()))
41+
],
42+
optional = [(reject_handler, Some(vm.ctx.function_type()))]
43+
);
44+
let response_format =
45+
args.get_optional_kwarg_with_type("response_format", vm.ctx.str_type(), vm)?;
46+
let method = args.get_optional_kwarg_with_type("method", vm.ctx.str_type(), vm)?;
47+
let headers = args.get_optional_kwarg_with_type("headers", vm.ctx.dict_type(), vm)?;
48+
let body = args.get_optional_kwarg("body");
49+
let content_type = args.get_optional_kwarg_with_type("content_type", vm.ctx.str_type(), vm)?;
50+
51+
let response_format = match response_format {
52+
Some(s) => FetchResponseFormat::from_str(vm, &objstr::get_value(&s))?,
53+
None => FetchResponseFormat::Text,
54+
};
55+
56+
let mut opts = web_sys::RequestInit::new();
57+
58+
match method {
59+
Some(s) => opts.method(&objstr::get_value(&s)),
60+
None => opts.method("GET"),
61+
};
62+
63+
if let Some(body) = body {
64+
opts.body(Some(&convert::py_to_js(vm, body)));
65+
}
66+
67+
let request = web_sys::Request::new_with_str_and_init(&objstr::get_value(url), &opts)
68+
.map_err(|err| convert::js_py_typeerror(vm, err))?;
69+
70+
if let Some(headers) = headers {
71+
let h = request.headers();
72+
for (key, value) in rustpython_vm::obj::objdict::get_key_value_pairs(&headers) {
73+
let key = objstr::get_value(&vm.to_str(&key)?);
74+
let value = objstr::get_value(&vm.to_str(&value)?);
75+
h.set(&key, &value)
76+
.map_err(|err| convert::js_py_typeerror(vm, err))?;
77+
}
78+
}
79+
80+
if let Some(content_type) = content_type {
81+
request
82+
.headers()
83+
.set("Content-Type", &objstr::get_value(&content_type))
84+
.map_err(|err| convert::js_py_typeerror(vm, err))?;
85+
}
86+
87+
let window = window();
88+
let request_prom = window.fetch_with_request(&request);
89+
90+
let handler = handler.clone();
91+
let reject_handler = reject_handler.cloned();
92+
93+
let acc_vm = AccessibleVM::from_vm(vm);
94+
95+
let future = JsFuture::from(request_prom)
96+
.and_then(move |val| {
97+
let response = val
98+
.dyn_into::<web_sys::Response>()
99+
.expect("val to be of type Response");
100+
response_format.get_response(&response)
101+
})
102+
.and_then(JsFuture::from)
103+
.then(move |val| {
104+
let vm = &mut acc_vm
105+
.upgrade()
106+
.expect("that the VM *not* be destroyed while promise is being resolved");
107+
match val {
108+
Ok(val) => {
109+
let val = convert::js_to_py(vm, val);
110+
let args = PyFuncArgs::new(vec![val], vec![]);
111+
let _ = vm.invoke(handler, args);
112+
}
113+
Err(val) => {
114+
if let Some(reject_handler) = reject_handler {
115+
let val = convert::js_to_py(vm, val);
116+
let args = PyFuncArgs::new(vec![val], vec![]);
117+
let _ = vm.invoke(reject_handler, args);
118+
}
119+
}
120+
}
121+
future::ok(JsValue::UNDEFINED)
122+
});
123+
future_to_promise(future);
124+
125+
Ok(vm.get_none())
126+
}
127+
128+
const BROWSER_NAME: &str = "browser";
129+
130+
pub fn mk_module(ctx: &PyContext) -> PyObjectRef {
131+
py_module!(ctx, BROWSER_NAME, {
132+
"fetch" => ctx.new_rustfunc(browser_fetch)
133+
})
134+
}
135+
136+
pub fn setup_browser_module(vm: &mut VirtualMachine) {
137+
vm.stdlib_inits.insert(BROWSER_NAME.to_string(), mk_module);
138+
}

wasm/lib/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
1+
pub mod browser_module;
12
pub mod convert;
23
pub mod vm_class;
34
pub mod wasm_builtins;
45

56
extern crate futures;
67
extern crate js_sys;
8+
#[macro_use]
79
extern crate rustpython_vm;
810
extern crate wasm_bindgen;
11+
extern crate wasm_bindgen_futures;
912
extern crate web_sys;
1013

1114
use js_sys::{Object, Reflect, TypeError};

wasm/lib/src/vm_class.rs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
use crate::browser_module::setup_browser_module;
12
use crate::convert;
2-
use crate::wasm_builtins::{self, setup_wasm_builtins};
3+
use crate::wasm_builtins;
34
use js_sys::{SyntaxError, TypeError};
45
use rustpython_vm::{
56
compile,
@@ -17,12 +18,12 @@ pub(crate) struct StoredVirtualMachine {
1718
}
1819

1920
impl StoredVirtualMachine {
20-
fn new(id: String, inject_builtins: bool) -> StoredVirtualMachine {
21+
fn new(id: String, inject_browser_module: bool) -> StoredVirtualMachine {
2122
let mut vm = VirtualMachine::new();
2223
let builtin = vm.get_builtin_scope();
2324
let scope = vm.context().new_scope(Some(builtin));
24-
if inject_builtins {
25-
setup_wasm_builtins(&mut vm, &scope);
25+
if inject_browser_module {
26+
setup_browser_module(&mut vm);
2627
}
2728
vm.wasm_id = Some(id);
2829
StoredVirtualMachine { vm, scope }
@@ -42,12 +43,12 @@ pub struct VMStore;
4243

4344
#[wasm_bindgen(js_class = vmStore)]
4445
impl VMStore {
45-
pub fn init(id: String, inject_builtins: Option<bool>) -> WASMVirtualMachine {
46+
pub fn init(id: String, inject_browser_module: Option<bool>) -> WASMVirtualMachine {
4647
STORED_VMS.with(|cell| {
4748
let mut vms = cell.borrow_mut();
4849
if !vms.contains_key(&id) {
4950
let stored_vm =
50-
StoredVirtualMachine::new(id.clone(), inject_builtins.unwrap_or(true));
51+
StoredVirtualMachine::new(id.clone(), inject_browser_module.unwrap_or(true));
5152
vms.insert(id.clone(), Rc::new(RefCell::new(stored_vm)));
5253
}
5354
});

wasm/lib/src/wasm_builtins.rs

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,17 @@
22
//!
33
//! This is required because some feature like I/O works differently in the browser comparing to
44
//! desktop.
5-
//! Implements functions listed here: https://docs.python.org/3/library/builtins.html and some
6-
//! others.
7-
8-
extern crate futures;
9-
extern crate js_sys;
10-
extern crate wasm_bindgen;
11-
extern crate web_sys;
5+
//! Implements functions listed here: https://docs.python.org/3/library/builtins.html.
126
137
use crate::convert;
14-
use js_sys::Array;
8+
use js_sys::{self, Array};
159
use rustpython_vm::obj::{objstr, objtype};
1610
use rustpython_vm::pyobject::{IdProtocol, PyFuncArgs, PyObjectRef, PyResult, TypeProtocol};
1711
use rustpython_vm::VirtualMachine;
1812
use wasm_bindgen::{prelude::*, JsCast};
19-
use web_sys::{console, HtmlTextAreaElement};
13+
use web_sys::{self, console, HtmlTextAreaElement};
2014

21-
fn window() -> web_sys::Window {
15+
pub(crate) fn window() -> web_sys::Window {
2216
web_sys::window().expect("Window to be available")
2317
}
2418

@@ -103,8 +97,3 @@ pub fn builtin_print_console(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyRes
10397
console::log(&arr);
10498
Ok(vm.get_none())
10599
}
106-
107-
pub fn setup_wasm_builtins(vm: &mut VirtualMachine, scope: &PyObjectRef) {
108-
let ctx = vm.context();
109-
ctx.set_attr(scope, "print", ctx.new_rustfunc(builtin_print_console));
110-
}

0 commit comments

Comments
 (0)