|
| 1 | +# Running PyScript Offline |
| 2 | + |
| 3 | +Althought users will want to create and share PyScript apps on the internet, there are cases when user want to run PyScript applications offline, in an airgapped fashion. This means that both PyScript core and the interpreter used to run code need to be served with the application itself. In short, the 2 main explicit tasks needed to create an offline PyScript application are: |
| 4 | + |
| 5 | +* download and include PyScript core (`core.js`) |
| 6 | +* download and include the [Python] interpreters you want to use in your Application |
| 7 | + |
| 8 | +## Downloading and Including PyScript's `core.js` |
| 9 | + |
| 10 | +There are at least 2 ways to use PyScript offline: |
| 11 | + |
| 12 | + * by **cloning the repository**, then building and installing dependencies and then run and then reach the `./dist/` folder |
| 13 | + * by **grabbing the npm package** which for simplicity sake will be the method used here at least until we find a better place to *pin* our *dist* folder via our CDN and make the procedure even easier than it is now |
| 14 | + |
| 15 | +In the examples below, we'll assume we are creating a PyScript Application folder called `pyscript-offline` and we'll add all the necessary files to the folder. |
| 16 | + |
| 17 | +First of all, we are going to create a `pyscript-offline` folder as reference. |
| 18 | + |
| 19 | +```sh |
| 20 | +mkdir -p pyscript-offline |
| 21 | +cd pyscript-offline |
| 22 | +``` |
| 23 | + |
| 24 | +### Adding core by Cloning the Repository |
| 25 | + |
| 26 | +You can build all the PyScript Core files by cloning the project repository and building them yourself. To do so, build the files by following the instructions in our [developer guide](/developers) |
| 27 | + |
| 28 | +Once you've run the `build` command, copy the `build` folder that has been created into your `pyscript-offline` folder. |
| 29 | + |
| 30 | +### Adding core by Installing `@pyscript/core` Locally |
| 31 | + |
| 32 | +First of all, ensure you are in the folder you would like to test PyScirpt locally. In this case, the `pyscript-offline` folder we created earlier. |
| 33 | + |
| 34 | +Once within the folder, be sure there is a `package.json` file. Even an empty one with just `{}` as content would work. |
| 35 | +This is needed to be sure the folder will include locally the `npm_modules` folder instead of placing the package in the parent folder, if any. |
| 36 | + |
| 37 | +```sh |
| 38 | +# only if there is no package.json, create one |
| 39 | +echo '{}' > ./package.json |
| 40 | + |
| 41 | +# install @pyscript/core |
| 42 | +npm i @pyscript/core |
| 43 | +``` |
| 44 | + |
| 45 | +At this point the folder should contain a `node_module` in it and we can actually copy its `dist` folder wherever we like. |
| 46 | + |
| 47 | +```sh |
| 48 | +# create a public folder to serve locally |
| 49 | +mkdir -p public |
| 50 | + |
| 51 | +# move @pyscript/core dist into such folder |
| 52 | +cp -R ./node_modules/@pyscript/core/dist ./public/pyscript |
| 53 | +``` |
| 54 | + |
| 55 | +## Setting up your application |
| 56 | + |
| 57 | +Once you've added PyScript code following one of the methods above, that's almost it! We are half way through our goal but we can already create a `./public/index.html` file that loads the project: |
| 58 | + |
| 59 | +```html |
| 60 | +<!DOCTYPE html> |
| 61 | +<html lang="en"> |
| 62 | +<head> |
| 63 | + <meta charset="UTF-8"> |
| 64 | + <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| 65 | + <title>PyScript Offline</title> |
| 66 | + <script type="module" src="/pyscript/core.js"></script> |
| 67 | + <link rel="stylesheet" href="/pyscript/core.css"> |
| 68 | +</head> |
| 69 | +<body> |
| 70 | + <script type="mpy"> |
| 71 | + from pyscript import document |
| 72 | +
|
| 73 | + document.body.append("Hello from PyScript") |
| 74 | + </script> |
| 75 | +</body> |
| 76 | +</html> |
| 77 | +``` |
| 78 | + |
| 79 | +To run this project directly, after being sure that `index.html` file is saved into the `public` folder, you can try: |
| 80 | + |
| 81 | +```sh |
| 82 | +python3 -m http.server -d ./public/ |
| 83 | +``` |
| 84 | + |
| 85 | +Alternatively, if you would like to test also `worker` features, you can try instead: |
| 86 | + |
| 87 | +```sh |
| 88 | +npx static-handler --coi ./public/ |
| 89 | +``` |
| 90 | +## Downloading and Setting up a Local Interpreter |
| 91 | + |
| 92 | +Good news! We are almost there. Now that we've: |
| 93 | + |
| 94 | +* downloaded PyScript locally |
| 95 | +* created the skeleton of an initial PyScript App |
| 96 | + |
| 97 | +we need to download and setup up an interpreter. PyScript officially supports *MicroPython* and *Pyodide* interpreters, so let's see how to do that for each one of them. |
| 98 | + |
| 99 | +### Download MicroPython locally |
| 100 | + |
| 101 | +Similarly to what we did for `@pyscript/core`, we can also install *MicroPython* from *npm*: |
| 102 | + |
| 103 | +```sh |
| 104 | +npm i @micropython/micropython-webassembly-pyscript |
| 105 | +``` |
| 106 | + |
| 107 | +Our `node_modules` folder now should contain a `@micropython` one and from there we can move relevant files into our `public` folder, but let's be sure we have a target for that: |
| 108 | + |
| 109 | +```sh |
| 110 | +# create a folder in our public space |
| 111 | +mkdir -p ./public/micropython |
| 112 | + |
| 113 | +# copy related files into such folder |
| 114 | +cp ./node_modules/@micropython/micropython-webassembly-pyscript/micropython.* ./public/micropython/ |
| 115 | +``` |
| 116 | + |
| 117 | +That folder should contain at least both `micropython.mjs` and `micropython.wasm` files and these are the files we are going to use locally via our dedicated config. |
| 118 | + |
| 119 | +```html |
| 120 | +<!DOCTYPE html> |
| 121 | +<html lang="en"> |
| 122 | +<head> |
| 123 | + <meta charset="UTF-8"> |
| 124 | + <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| 125 | + <title>PyScript Offline</title> |
| 126 | + <script type="module" src="/pyscript/core.js"></script> |
| 127 | + <link rel="stylesheet" href="/pyscript/core.css"> |
| 128 | +</head> |
| 129 | +<body> |
| 130 | + <mpy-config> |
| 131 | + interpreter = "/micropython/micropython.mjs" |
| 132 | + </mpy-config> |
| 133 | + <script type="mpy"> |
| 134 | + from pyscript import document |
| 135 | +
|
| 136 | + document.body.append("Hello from PyScript") |
| 137 | + </script> |
| 138 | +</body> |
| 139 | +</html> |
| 140 | +``` |
| 141 | + |
| 142 | +### Install Pyodide locally |
| 143 | + |
| 144 | +Currently there is a difference between MicroPython and Pyodide: the former does not have (*yet*) a package manager while the latest does, it's called *micropip*. |
| 145 | + |
| 146 | +This is important to remember because while the procedure to have *pyodide* offline is very similar to the one we've just seen, if we want to use also 3rd party packages we also need to have these running locally ... but let's start simple: |
| 147 | + |
| 148 | +```sh |
| 149 | +# install locally the pyodide module |
| 150 | +npm i pyodide |
| 151 | + |
| 152 | +# create a folder in our public space |
| 153 | +mkdir -p ./public/pyodide |
| 154 | + |
| 155 | +# move all necessary files into that folder |
| 156 | +cp ./node_modules/pyodide/pyodide* ./public/pyodide/ |
| 157 | +cp ./node_modules/pyodide/python_stdlib.zip ./public/pyodide/ |
| 158 | +``` |
| 159 | + |
| 160 | +Please **note** that also `pyodide-lock.json` file is needed so please don't change that `cp` operation as all `pyodide*` files need to be moved. |
| 161 | + |
| 162 | +At this point, all we need to do is to change our *HTML* page to use *pyodide* instead: |
| 163 | + |
| 164 | +```html |
| 165 | +<!DOCTYPE html> |
| 166 | +<html lang="en"> |
| 167 | +<head> |
| 168 | + <meta charset="UTF-8"> |
| 169 | + <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| 170 | + <title>PyScript Offline</title> |
| 171 | + <script type="module" src="/pyscript/core.js"></script> |
| 172 | + <link rel="stylesheet" href="/pyscript/core.css"> |
| 173 | +</head> |
| 174 | +<body> |
| 175 | + <py-config> |
| 176 | + interpreter = "/pyodide/pyodide.mjs" |
| 177 | + </py-config> |
| 178 | + <script type="py"> |
| 179 | + from pyscript import document |
| 180 | +
|
| 181 | + document.body.append("Hello from PyScript") |
| 182 | + </script> |
| 183 | +</body> |
| 184 | +</html> |
| 185 | +``` |
| 186 | + |
| 187 | +## Wrapping it up |
| 188 | + |
| 189 | +We are basically done! If we try to disconnect from the internet but we still run our local server, the page will still show that very same *Hello from PyScript* message :partying_face: |
| 190 | + |
| 191 | +We can now drop internet, still keeping the local server running, and everything should be fine :partying_face: |
| 192 | + |
| 193 | +## Local Pyodide Packages |
| 194 | + |
| 195 | +There's one last thing that users are probably going to need: the ability to install Python packages when using Pyodide. |
| 196 | + |
| 197 | +In order to have also 3rd party packages available, we can use the bundle from [pyodide releases](https://github.com/pyodide/pyodide/releases/tag/0.24.1) that contains also packages. |
| 198 | + |
| 199 | +Please note this bundle is more than 200MB: it not downloaded all at once, it contains each package that is required and it loads only related packages when needed. |
| 200 | + |
| 201 | +Once downloaded and extracted, where in this case I am using `0.24.1` as reference bundle, we can literally copy and paste, or even move, all those files and folders inside the `pyodide-0.24.1/pyodide/*` directory into our `./public/pyodide/*` folder. |
| 202 | + |
| 203 | +As the bundle contains files already present, feel free to either skip or replace the content, or even directly move that *pyodide* folder inside our `./public/` one. |
| 204 | + |
| 205 | +Once it's done, we can now use any package we like that is available in *pyodide*. Let's see an example: |
| 206 | + |
| 207 | +```html |
| 208 | +<!DOCTYPE html> |
| 209 | +<html lang="en"> |
| 210 | +<head> |
| 211 | + <meta charset="UTF-8"> |
| 212 | + <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| 213 | + <title>PyScript Offline</title> |
| 214 | + <script type="module" src="/pyscript/core.js"></script> |
| 215 | + <link rel="stylesheet" href="/pyscript/core.css"> |
| 216 | +</head> |
| 217 | +<body> |
| 218 | + <py-config> |
| 219 | + interpreter = "/pyodide/pyodide.mjs" |
| 220 | + packages = ["pandas"] |
| 221 | + </py-config> |
| 222 | + <script type="py"> |
| 223 | + import pandas as pd |
| 224 | + x = pd.Series([1,2,3,4,5,6,7,8,9,10]) |
| 225 | +
|
| 226 | + from pyscript import document |
| 227 | + document.body.append(str([i**2 for i in x])) |
| 228 | + </script> |
| 229 | +</body> |
| 230 | +</html> |
| 231 | +``` |
| 232 | + |
| 233 | +If everything went fine, we should now be able to read `[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]` on the page *even* if we disconnect from the Internet. |
| 234 | + |
| 235 | +And **that's all folks** :wave: |
0 commit comments