Practical Brython: API calls and Async


Practical Brython: API calls and Async

Ajax calls always look a little odd to python programmers, unless are used to dealing with async libraries. All Ajax calls are, by default, non-blocking. This means they don’t wait for a response from the request and you have to define a function that deals with the response, when it arrives.

Brython gives us two modules for dealing with Ajax calls. While they sort of do the same thing, you may find yourself more comfortable with one or the other.

The Ajax module.

This module replicates the jQuery Ajax module in python. When you make a request to an API endpoint, of any form, you need to state the function that will handle the results, when they come back. Here is an example

def read_results(req):
print(req.json)

ajax.get("/my/url/", mode="json", oncomplete=read_results)
# the following code will be run right after the request is sent and not wait for results
print("request sent")

The way this works, the “print” will run before the results are printed. You will notice that the code looks very similar to the Requests library and in fact, just like that, you can add arguments for headers and data, but since the results and the requesting object are separated, there is no such thing as a session.”mode” allows you to define the format the results are available when processed. The choices are “binary”, “document”, “text” and “json”

If your doing a get, data will be attached as query parameters, if a post, set the content type to “application/json” for json data etc.

Practical Hint: Brython does not deal with form data that well especially files. There is a section on sending forms via a form_data() object, and sending files via a different mechanism. I’ve found issues with both. I would advise allowing html to control the sending of forms, especially files and not to use Brython for this. I have raised issues on this and I know fixes are coming, but my advice is to just not go there. if you want to send data from forms, grab the data and convert into json and send the json blob. Its kinda what the form_data() object does anyway.

If you want to send form data, its best to use a longer form of the ajax module code.

from browser import document, ajax

def on_complete(req):
if req.status == 200 or req.status == 0:
document["result"].html = req.text
else:
document["result"].html = "error " + req.text

req = ajax.Ajax()
req.bind('complete', on_complete)
# send a POST request to the url
req.open('POST', url, True)
req.set_header('content-type', 'multipart/form-data')
# send data as a dictionary
form_data = ajax.form_data()
form_data.append("x",1)
form_data.append("upload", document["file_upload"].files[0])
req.send(form_data)

WARNING. Last time I tried this as multipart/form-data, it didn’t work, the code kept resetting to application/x-www-form-urlencoded, so the set_header didn’t do anything. I do know a fix was in for this, but be aware.

The aio Module

The aio module allows for async threading. It’s no anywhere near as powerful as the asyncio module, but it does add a couple of features. The main one is an awaited Ajax call. For those of you used to blocking calls, then this may feel more comfortable.

from browser import document, html, aio

async def main():
# Text file
req = await aio.ajax("GET", "test.html")
print(len(req.data))

aio.run(main())

ok, a few points here. aio.ajax must be run in an async function, thus, in this example, the async function “main()” needs to be run using the “aio.run(main())” command. This starts the function in an async thread.

Next the request is awaited. This means the coroutine passes control to another function while waiting on the results to come back. When the results eventually come pack, they are placed in the “req” variable.

The request above is a get request, but it can be written differently. Here is an example including sending query data and expecting the results back as text

from browser import document, html, aio

async def main():
# Text file
req = await aio.ajax.get("test.html", format="text", data={"x":1, "y":0})
print(len(req.data))

aio.run(main())

“format” is used instead of mode, but the choices are only “text”,“binary” and “dataURL”. For json data, use text and then load the response data using the standard json lib.

With Post and Put the data is sent as raw JSON. No form data option with this version.

Practical Hint: Brython is not really made to be a SPA framework, so I find it best to avoid sending forms via either of these options. I do use validation on forms, for which I use the “change” or “keyup” events to check the changes. I then show or hide the submit button instead of trying to see if the form object is valid or not. Brython makes this sort of validation very easy.

Conclusion

The Ajax module is the one closest to JavaScript, but I found things get very messy with lots of functions in place for dealing with the results. The aio module is slightly tidier but more limited. With form data, I recommend converting the inputs into JSON and sending them as the form_data() object and sending files with the multipart/form-data content type is might not work.

The aio module also has a lot of uses if you want to poll the backend for changes instead of having websockets.