10 KiB
How to make HTTP requests using PyScript, in pure Python
Pyodide, the interpreter that underlies PyScript, does not have the requests module
(or other similar modules) available by default, which are traditionally used to make HTTP requests in Python.
However, it is possible to make HTTP requests in Pyodide using the modern JavaScript fetch API
(docs). This example shows how to make common HTTP request
(GET, POST, PUT, DELETE) to an API, using only Python code! We will use asynchronous functions with
async/await syntax, as concurrent code is preferred for HTTP requests.
The purpose of this guide is not to teach the basics of HTTP requests, but to show how to make them
from PyScript using Python, since currently, the common tools such as requests and httpx are not available.
Fetch
The fetch API is a modern way to make HTTP requests. It is available in all modern browsers, and in Pyodide.
Although there are two ways to use fetch:
- using
JavaScriptfromPyScript - using Pyodide's Python wrapper,
pyodide.http.pyfetch
This example will only show how to use the Python wrapper. Still, the
fetch documentation is a useful reference, as its
parameters can be called from Python using the pyfetch wrapper.
Pyodide.http, pyfetch, and FetchResponse
The pyodide.http module is a Python API
for dealing with HTTP requests. It provides the pyfetch function as a wrapper for the fetch API,
which returns a FetchResponse object whenever a request is made. Extra keyword arguments can be passed to pyfetch
which will be passed to the fetch API.
The returned object FetchResponse has familiar methods and properties
for dealing with the response, such as json() or status. See the
FetchResponse documentation
for more information.
Example
We will make async HTTP requests to JSONPlaceholder's fake API using pyfetch.
First we write a helper function in pure Python that makes a request and returns the response. This function
makes it easier to make specific types of requests with the most common parameters.
Python convenience function
from pyodide.http import pyfetch, FetchResponse
from typing import Optional, Any
async def request(url: str, method: str = "GET", body: Optional[str] = None,
headers: Optional[dict[str, str]] = None, **fetch_kwargs: Any) -> FetchResponse:
"""
Async request function. Pass in Method and make sure to await!
Parameters:
url: str = URL to make request to
method: str = {"GET", "POST", "PUT", "DELETE"} from `JavaScript` global fetch())
body: str = body as json string. Example, body=json.dumps(my_dict)
headers: dict[str, str] = header as dict, will be converted to string...
Example, headers=json.dumps({"Content-Type": "application/json"})
fetch_kwargs: Any = any other keyword arguments to pass to `pyfetch` (will be passed to `fetch`)
Return:
response: pyodide.http.FetchResponse = use with .status or await.json(), etc.
"""
kwargs = {"method": method, "mode": "cors"} # CORS: https://en.wikipedia.org/wiki/Cross-origin_resource_sharing
if body and method not in ["GET", "HEAD"]:
kwargs["body"] = body
if headers:
kwargs["headers"] = headers
kwargs.update(fetch_kwargs)
response = await pyfetch(url, **kwargs)
return response
This function is a wrapper for pyfetch, which is a wrapper for the fetch API. It is a coroutine function,
so it must be awaited. It also has type hints, which are not required, but are useful for IDEs and other tools.
The basic idea is that the PyScript will import and call this function, then await the response. Therefore,
the script containing this function must be importable by PyScript.
For this example, we will name the file containing the Python code request.py and place it in the same directory as the file
containing the html code, which is described below.
PyScript HTML code
In this How-to, the HTML code is split into separate code blocks to enable context highlighting (coloring of the Python
code inside the html code block), but in reality it is all in the same file. The first part is a bare bones PyScript
html page, using the community examples set-up. The second part is
the actual Python code for HTTP requests, which is wrapped in <py-script> tags, while the third block has the
concluding html code.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>GET, POST, PUT, DELETE example</title>
<link rel="icon" type="image/png" href="favicon.png" />
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
<py-config>
[[fetch]]
files = ["/request.py"]
</py-config>
</head>
<body><p>
Hello world request example! <br>
Here is the output of your request:
</p>
<py-script>
import asyncio
import json
from request import request # import our request function.
async def main():
baseurl = "https://jsonplaceholder.typicode.com"
# GET
headers = {"Content-type": "application/json"}
response = await request(f"{baseurl}/posts/2", method="GET", headers=headers)
print(f"GET request=> status:{response.status}, json:{await response.json()}")
# POST
body = json.dumps({"title": "test_title", "body": "test body", "userId": 1})
new_post = await request(f"{baseurl}/posts", body=body, method="POST", headers=headers)
print(f"POST request=> status:{new_post.status}, json:{await new_post.json()}")
# PUT
body = json.dumps({"id": 1, "title": "test_title", "body": "test body", "userId": 2})
new_post = await request(f"{baseurl}/posts/1", body=body, method="PUT", headers=headers)
print(f"PUT request=> status:{new_post.status}, json:{await new_post.json()}")
# DELETE
new_post = await request(f"{baseurl}/posts/1", method="DELETE", headers=headers)
print(f"DELETE request=> status:{new_post.status}, json:{await new_post.json()}")
asyncio.ensure_future(main())
</py-script>
<div>
<p>
You can also use other methods. See fetch documentation: <br>
https://developer.mozilla.org/en-US/docs/Web/API/fetch#parameters
</p>
</div>
<div>
<p>
See pyodide documentation for what to do with a FetchResponse object: <br>
https://pyodide.org/en/stable/usage/api/python-api.html#pyodide.http.FetchResponse
</p>
</div>
</body>
</html>
Explanation
py-config tag for importing our Python code
The very first thing to notice is the py-config tag. This tag is used to import Python files into the PyScript.
In this case, we are importing the request.py file, which contains the request function we wrote above.
py-script tag for making async HTTP requests.
Next, the py-script tag contains the actual Python code where we import asyncio and json,
which are required or helpful for the request function.
The # GET, # POST, # PUT, # DELETE blocks show examples of how to use the request function to make basic
HTTP requests. The await keyword is required not only for the request function, but also for certain methods of the
FetchResponse object, such as json(), meaning that the code is asynchronous and slower requests will not block the
faster ones.
HTTP Requests
HTTP requests are a very common way to communicate with a server. They are used for everything from getting data from
a database, to sending emails, to authorization, and more. Due to safety concerns, files loaded from the
local file system are not accessible by PyScript. Therefore, the proper way to load data into PyScript is also
through HTTP requests.
In our example, we show how to pass in a request body, headers, and specify the request method, in order to make
GET, POST, PUT, and DELETE requests, although methods such as PATCH are also available. Additional
parameters for the fetch API are also available, which can be specified as keyword arguments passed to our helper
function or to pyfetch. See the
fetch documentation for more information.
HTTP requests are defined by standards-setting bodies in RFC 1945 and
RFC 9110.
Conclusion
This tutorial demonstrates how to make HTTP requests using pyfetch and the FetchResponse objects. Importing Python
code/files into the PyScript using the py-config tag is also covered.
Although a simple example, the principals here can be used to create complex web applications inside of PyScript,
or load data into PyScript for use by an application, all served as a static HTML page, which is pretty amazing!
API Quick Reference
pyodide.http.pyfetch
Usage
await pyodide.http.pyfetch(url: str, **kwargs: Any) -> FetchResponse
Use pyfetch to make HTTP requests in PyScript. This is a wrapper around the fetch API. Returns a FetchResponse.
pyodide.http.FetchResponse
Usage
response: pyodide.http.FetchResponse = await <pyfetch call>
status = response.status
json = await response.json()
Class for handling HTTP responses. This is a wrapper around the JavaScript fetch Response. Contains common (async)
methods and properties for handling HTTP responses, such as json(), url, status, headers, etc.