|
| 1 | +# Runtime Builds |
| 2 | + |
| 3 | +## Static Exports |
| 4 | + |
| 5 | +In some cases, you might want to export your application as [static HTML](https://en.wikipedia.org/wiki/Static_web_page). This makes it much easier to serve your app somewhere, at the limit of being able to perform actions server-side. You can export your app in view.py via the `view build` command, or by running the `build_app` function: |
| 6 | + |
| 7 | +``` |
| 8 | +$ view build |
| 9 | +* Starting build process! |
| 10 | +* Starting build steps |
| 11 | +* Getting routes |
| 12 | +* Calling GET /... |
| 13 | +* Created ... |
| 14 | +* Created index.html |
| 15 | +* Successfully built app |
| 16 | +``` |
| 17 | + |
| 18 | +This will export your app into a static folder called `build`, which can then be served via something like [http.server](https://docs.python.org/3/library/http.server.html). An exported route cannot contain: |
| 19 | + |
| 20 | +- Route Inputs |
| 21 | +- Path Parameters |
| 22 | +- A method other than `GET` |
| 23 | + |
| 24 | +As stated above, you can also build your app programatically via `build_app`: |
| 25 | + |
| 26 | +```py |
| 27 | +from view import new_app |
| 28 | +from view.build import build_app |
| 29 | + |
| 30 | +app = new_app() |
| 31 | +app.load() # Call the loader manually, since we aren't calling run() |
| 32 | + |
| 33 | +build_app(app) |
| 34 | +``` |
| 35 | + |
| 36 | +::: view.build.build_app |
| 37 | + |
| 38 | +## Build Steps |
| 39 | + |
| 40 | +Instead of exporting static HTML, you might just want to call some build script at runtime for your app to use. For example, this could be something like a [Next.js](https://nextjs.org) app, which you want to use as the UI for your website. Each different build is called a **build step** in View. View's build system does not aim to be a full fledged build system, but instead a bridge to use other package managers or tools to build requirements for your app. It tries to be _extendable_, instead of batteries-included. |
| 41 | + |
| 42 | +To specify a build step, add it under `build.steps` in your configuration. A build step should contain a list of requirements under `requires` and a `command`: |
| 43 | + |
| 44 | +```toml |
| 45 | +# view.toml |
| 46 | +[build.steps.nextjs] |
| 47 | +requires = ["npm"] |
| 48 | +command = "npm run build" |
| 49 | +``` |
| 50 | + |
| 51 | +By default, this will only be run once the app is started. If you would like to run it every time a certain route is called, add the `steps` parameter to a router function. Note that this will make your route much slower (as a build process needs to be started for every request), so it's highly recommended that you [cache](https://view.zintensity.dev/building-projects/responses/#caching) the route. |
| 52 | + |
| 53 | +For example: |
| 54 | + |
| 55 | +```py |
| 56 | +from view import new_app |
| 57 | + |
| 58 | +app = new_app() |
| 59 | + |
| 60 | +@app.get("/", steps=["nextjs"], cache_rate=10000) # Reloads app every 10,000 requests |
| 61 | +async def index(): |
| 62 | + return await app.template("out/index.html") |
| 63 | + |
| 64 | +app.run() |
| 65 | +``` |
| 66 | + |
| 67 | +## Executing Build Scripts |
| 68 | + |
| 69 | +Instead of running a command, you can also run a Python script. To do this, simply specify a `script` value as a path to a file instead of a `command`: |
| 70 | + |
| 71 | +```toml |
| 72 | +# view.toml |
| 73 | +[build.steps.foo] |
| 74 | +requires = [] |
| 75 | +script = "foo.py" |
| 76 | +``` |
| 77 | + |
| 78 | +!!! note |
| 79 | + |
| 80 | + `__name__` is set to `__view_build__` when using a build script. If you want to use the file for other things, you can simply check `if __name__ == "__view_build__"` |
| 81 | + |
| 82 | +You can also specify a list of files or commands for both, to run multiple of either: |
| 83 | + |
| 84 | +```toml |
| 85 | +# view.toml |
| 86 | +[build.steps.foo] |
| 87 | +requires = ["gcc"] |
| 88 | +script = ["foo.py", "bar.py"] |
| 89 | +command = ["gcc -c -Wall -Werror -fpic foo.c", "gcc -shared -o libfoo.so foo.o"] |
| 90 | +``` |
| 91 | + |
| 92 | +If the script needs to run asynchronous code, export a `__view_build__` from the script: |
| 93 | + |
| 94 | +```py |
| 95 | +# build.py |
| 96 | +import aiofiles |
| 97 | + |
| 98 | +# This function will be run by the view.py build system |
| 99 | +async def __view_build__(): |
| 100 | + async with aiofiles.open("something.txt", "w") as f: |
| 101 | + await f.write("...") |
| 102 | +``` |
| 103 | + |
| 104 | +## Default Steps |
| 105 | + |
| 106 | +As said earlier, the default build steps are always run right before the app is started, and then never ran again (unless explicitly needed by a route). If you would like only certain steps to run, specify them with the `build.default_steps` value: |
| 107 | + |
| 108 | +```toml |
| 109 | +# view.toml |
| 110 | +[build] |
| 111 | +default_steps = ["nextjs"] |
| 112 | +# Only NextJS will be built on startup |
| 113 | + |
| 114 | +[build.steps.nextjs] |
| 115 | +requires = ["npm"] |
| 116 | +command = "npm run build" |
| 117 | + |
| 118 | +[build.steps.php] |
| 119 | +requires = ["php"] |
| 120 | +command = "php -f payment.php" |
| 121 | +``` |
| 122 | + |
| 123 | +## Platform-Dependent Steps |
| 124 | + |
| 125 | +Many commands are different based on the platform used. For example, to read from a file on the Windows shell would be `type`, while on Linux and Mac it would be `cat`. If you add multiple step entries (in the form of an [array of tables](https://toml.io/en/v1.0.0-rc.2#array-of-tables)) with `platform` values, view.py will run the entry based on the platform the app was run on. |
| 126 | + |
| 127 | +For example, using the file reading example from above: |
| 128 | + |
| 129 | +Notice the double brackets next to `[[build.steps.read_from_file]]`, specifying an array of tables. |
| 130 | + |
| 131 | +```toml |
| 132 | +# view.toml |
| 133 | + |
| 134 | +[[build.steps.read_from_file]] |
| 135 | +platform = ["mac", "linux"] |
| 136 | +command = "cat whatever.txt" |
| 137 | + |
| 138 | +[[build.steps.read_from_file]] |
| 139 | +platform = "windows" |
| 140 | +command = "type whatever.txt" |
| 141 | +``` |
| 142 | + |
| 143 | +The `platform` value can be one of three things per entry: |
| 144 | + |
| 145 | +- A list of platforms. |
| 146 | +- A string containing a single platform. |
| 147 | +- `None`, meaning to use this entry if no other platforms match. |
| 148 | + |
| 149 | +For example, with a `None` platform set (on multiple entries), the above could be rewritten as: |
| 150 | + |
| 151 | +```toml |
| 152 | +# view.toml |
| 153 | + |
| 154 | +[[build.steps.read_from_file]] |
| 155 | +# Windows ONLY runs this step |
| 156 | +platform = "windows" |
| 157 | +command = "type whatever.txt" |
| 158 | + |
| 159 | +[[build.steps.read_from_file]] |
| 160 | +# All other platforms run this! |
| 161 | +command = "cat whatever.txt" |
| 162 | +``` |
| 163 | + |
| 164 | +Note that only one step entry can have a `None` platform value, otherwise view.py will throw an error. |
| 165 | + |
| 166 | +!!! note |
| 167 | + |
| 168 | + The only recognized operating systems for `platform` are the big three: Windows, Mac, and any Linux based system. If you want more fine-grained control (for example, using `pacman` or `apt` depending on the Linux distro), use a custom build script that knows how to read the Linux distribution. |
| 169 | + |
| 170 | +## Build Requirements |
| 171 | + |
| 172 | +As you've seen above, build requirements are specified via the `requires` value. Out of the box, view.py supports a number of different build tools, compilers, and interpreters. To specify a requirement for one, simply add the name of their executable (_i.e._, how you access their CLI). For example, since `pip` is accessed via using the `pip` command in your terminal, `pip` is the name of the requirement. |
| 173 | + |
| 174 | +However, view.py might not support checking for a command by default (this is the case if you get a `Unknown build requirement` error). If so, you need a custom requirement. If you would like to, you can make an [issue](https://github.com/ZeroIntensity/view.py/issues) requesting support for it as well. |
| 175 | + |
| 176 | +### Custom Requirements |
| 177 | + |
| 178 | +There are four types of custom requirements, which are specified by adding a prefix to the requirement name: |
| 179 | + |
| 180 | +- Importing a Python module (`mod+`) |
| 181 | +- Executing a Python script (`script+`) |
| 182 | +- Checking if a path exists (`path+`) |
| 183 | +- Checking if a command exists (`command+`) |
| 184 | + |
| 185 | +For example, the `command+gcc` would make sure that `gcc --version` return `0`: |
| 186 | + |
| 187 | +```toml |
| 188 | +# view.toml |
| 189 | +[build.steps.c] |
| 190 | +requires = ["command+gcc"] |
| 191 | +command = "gcc *.c -o out" |
| 192 | +``` |
| 193 | + |
| 194 | +### The Requirement Protocol |
| 195 | + |
| 196 | +In a custom requirement specifying a module or script, view.py will attempt to call an asynchronous `__view_requirement__` function (similar to `__view_build__`). This function should return a `bool` value, with `True` indicating that the requirement exists, and `False` otherwise. |
| 197 | + |
| 198 | +!!! note |
| 199 | + |
| 200 | + If no `__view_requirement__` function exists, then all view.py does it check that execution or import was successful, and marks the requirement as passing. |
| 201 | + |
| 202 | +For example, if you were to write a requirement script that checks if the Python version is at least `3.10`, it could look like: |
| 203 | + |
| 204 | +```py |
| 205 | +# check_310.py |
| 206 | +import sys |
| 207 | + |
| 208 | +async def __view_requirement__() -> bool: |
| 209 | + # Make sure we're running on at least Python 3.10 |
| 210 | + return sys.version_info >= (3, 10) |
| 211 | +``` |
| 212 | + |
| 213 | +The above could actually be used via both `script+check_310.py` and `mod+check_310`. |
| 214 | + |
| 215 | +!!! note |
| 216 | + |
| 217 | + Don't use the view.py build system to check the Python version or if a Python package is installed. Instead, use the `dependencies` section of a `pyproject.toml` file, or [PEP 723](https://peps.python.org/pep-0723/) script metadata. |
| 218 | + |
| 219 | +## Review |
| 220 | + |
| 221 | +View can build static HTML with the `view build` command, or via `view.build.build_app`. Build steps in view.py are used to call external build systems, which can then in turn be used to build things your app needs at runtime (such as static HTML generated by [Next.js](https://nextjs.org)). Builds can run commands, Python scripts, or both. |
| 222 | + |
| 223 | +Each build step contains a list of build requirements. View provides several known requirements to specify out of the box, but you may also specify custom requirements, either via a Python script or module, checking a file path, or executing an arbitrary command. |
0 commit comments