Interacting with the server
In this tutorial, we'll see the difference between server-side and client-side code in Pret, and how to interact with the server from your app.
Client-side environment
When you build a Pret app, you are building a web application that will run in the browser. By default, in Pret, any function used in a rendered component will be transpiled into javascript (including its scoped variables), then sent to the browser for execution. While this is a powerful feature, this means that this function doesn't have access to your server-side environment, such as your database, filesystem or computing resources. Moreover, the transpilation process has some limitations: you won't be able to import modules beside the ones provided by Pret, and you won't be able to use some Python features such as decorators, context managers, multithreading, etc.
For instance, let's take a look at the following component:
import time
from pret import component
from pret.react import br, div
static_time = str(time.time())
def dynamic_client_time():
return str(time.time())
@component
def ShowCurrentWorkingDirectory():
return div(
f"Current time when this App was built: {static_time}",
br(),
f"Current CLIENT time when this App is rendered: {dynamic_client_time()}",
style={"margin": "1em 0"},
)
ShowCurrentWorkingDirectory()
The "dynamic" displayed time will be the one from the browser's. In fact, these docs are hosted as a static website on GitHub Pages, so there is no server-side environment to access during the rendering process. The "static" time will be the one from the server-side environment, computed when the component is built and sent as a constant to the browser.
Running server-side code
To tell Pret to run a function on the server-side, you can decorate a function with @server_only. In this case, any call to this function from a client-side function will actually trigger a call to the server, and the result will be sent back to the client asynchronously.
Async functions
Any function decorated with @server_only becomes an async function from the client's perspective. This means that you must use await on the result of this function to get the actual return value.
At the moment, it is not possible to directly await the result of the server function in the rendering function. Therefore, in the example below, we combine @server_only with use_effect to update the displayed current server working directory once it has been fetched from the server.
import time
from pret import component, server_only, use_effect, use_state
@server_only
def dynamic_server_time():
time.sleep(4)
return str(time.time())
def dynamic_client_time():
return str(time.time())
static_time = str(time.time())
@component
def ShowCurrentWorkingDirectory():
server_time, set_server_time = use_state(None)
async def on_load():
set_server_time(await dynamic_server_time())
use_effect(on_load, [])
return div(
f"Current time when this App was built: {static_time}",
br(),
f"Current CLIENT time when this App is rendered: {dynamic_client_time()}",
br(),
f"Current SERVER time when this App is rendered: {server_time or 'Waiting for server...'}",
style={"margin": "1em 1em"},
)
ShowCurrentWorkingDirectory()
Synchronizing browser and Python values
The Sharing state example shares a Store between browser components. Without sync=True, a browser edit does not update the Store variable in your notebook.
Add sync=True to keep edits synchronized in both directions. Try clicking the counter, then reading and changing its value from another notebook cell.
from pret_joy import Button
from pret import component, create_store
from pret.hooks import use_store_snapshot
store = create_store(
{
"count": 0,
},
sync=True,
)
@component
def Counter():
tracked = use_store_snapshot(store)
def increment(event):
store["count"] += 1
return Button(
f"Count: {tracked['count']}. Click to increment",
on_click=increment,
sx={"m": 1},
)
Counter()
In your notebook, you can now change the store["count"] variable in another cell, and observe the change in the browser. Conversely, you can click the "Increment" button in the browser, and print the store["count"] variable in your notebook to see the change.
Offline app
We defined our store as synced with a server : since you are likely seing this in a github pages documentation, there is no server to sync to, or said otherwise, the server is currently unreachable. To avoid having users thinking their changes are saved, disconnected apps automatically rollback any changes made to a synchronized store. This is the reason why you cannot change the counter of the above app in the public documentation. To "unlock" it in offline mode, comment "sync=..."
# Show the current count, synchronized with the browser
print(store["count"])
# Change the count from the notebook
store["count"] = 42
Save and reopen the Store
sync=True connects the browser and Python, but does not save a file. To keep the counter across kernel restarts, start it with a file path. The function supplies initial values only when the file is new.
store = create_store(lambda: {"count": 0}, sync="./shared_state.pret")
Pret writes changes to a SQLite database at that path. Other notebooks opening the same file receive its committed changes. For a complete example with editable tables, see Save and reopen your work.
Read the current values without following later edits
For this counter Store, load_store_snapshot returns a plain Python dictionary. It does not subscribe to changes or write edits back to the file.
from pret import load_store_snapshot
snapshot = load_store_snapshot("./shared_state.pret")
print(snapshot, type(snapshot))