-
Notifications
You must be signed in to change notification settings - Fork 1.5k
/
Copy pathhttp-requests.md
213 lines (173 loc) · 10.2 KB
/
http-requests.md
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
# How to make HTTP requests using `PyScript`, in pure Python
[Pyodide](https://pyodide.org), 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](https://developer.mozilla.org/en-US/docs/Web/API/fetch)). 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`:
1) using `JavaScript` from `PyScript`
2) using Pyodide's Python wrapper,
`pyodide.http.pyfetch`
This example will only show how to use the Python wrapper. Still, the
[fetch documentation](https://developer.mozilla.org/en-US/docs/Web/API/fetch#parameters) 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](https://pyodide.org/en/stable/usage/api/python-api/http.html#module-pyodide.http) 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](https://pyodide.org/en/stable/usage/api/python-api/http.html#pyodide.http.FetchResponse)
for more information.
# Example
We will make async HTTP requests to [JSONPlaceholder](https://jsonplaceholder.typicode.com/)'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
```python
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](https://github.com/pyscript/pyscript-collective/) 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.
```html
<!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](https://developer.mozilla.org/en-US/docs/Web/API/fetch#parameters) for more information.
HTTP requests are defined by standards-setting bodies in [RFC 1945](https://www.rfc-editor.org/info/rfc1945) and
[RFC 9110](https://www.rfc-editor.org/info/rfc9110).
# 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
```python
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`.
- [`pyfetch` Docs.](https://pyodide.org/en/stable/usage/api/python-api/http.html#pyodide.http.pyfetch)
## pyodide.http.FetchResponse
### Usage
```python
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.
- [`FetchResponse` Docs.](https://pyodide.org/en/stable/usage/api/python-api/http.html#pyodide.http.FetchResponse)